Files
live-hub-py/dev.sh
T

249 lines
7.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ===== 本地调试启动脚本:后端 reload + 前端热更新 =====
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$ROOT_DIR"
if [ -f "$ROOT_DIR/.env" ]; then
set -a
# 调试模式复用 Docker 部署的环境变量,尤其是 JWT 和敏感字段加密密钥。
# shellcheck disable=SC1091
source "$ROOT_DIR/.env"
set +a
fi
BACKEND_HOST="${BACKEND_HOST:-0.0.0.0}"
BACKEND_PORT="${BACKEND_PORT:-8800}"
FRONTEND_HOST="${FRONTEND_HOST:-0.0.0.0}"
FRONTEND_PORT="${FRONTEND_PORT:-5174}"
LOG_LEVEL="${LOG_LEVEL:-DEBUG}"
MYSQL_IMAGE="${MYSQL_IMAGE:-mysql:8.4}"
MYSQL_BIND_HOST="${MYSQL_BIND_HOST:-127.0.0.1}"
MYSQL_HOST_PORT="${MYSQL_HOST_PORT:-3307}"
MYSQL_DATABASE="${MYSQL_DATABASE:-douyu_login}"
MYSQL_USER="${MYSQL_USER:-douyu_login}"
DEV_DB_HOST="${DEV_DB_HOST:-127.0.0.1}"
DEV_DB_PORT="${DEV_DB_PORT:-$MYSQL_HOST_PORT}"
DB_HOST="$DEV_DB_HOST"
DB_PORT="$DEV_DB_PORT"
DB_NAME="${DB_NAME:-$MYSQL_DATABASE}"
DB_USER="${DB_USER:-$MYSQL_USER}"
DB_PASSWORD="${DB_PASSWORD:-${MYSQL_PASSWORD:-}}"
DB_POOL_SIZE="${DB_POOL_SIZE:-20}"
DB_MAX_OVERFLOW="${DB_MAX_OVERFLOW:-20}"
DB_POOL_TIMEOUT="${DB_POOL_TIMEOUT:-30}"
DB_POOL_RECYCLE="${DB_POOL_RECYCLE:-1800}"
YYB_WORKER_PORT="${YYB_WORKER_PORT:-8810}"
YYB_WORKER_IMAGE="${YYB_WORKER_IMAGE:-douyu-yyb-worker:local}"
DEV_YYB_WORKER_URL="${DEV_YYB_WORKER_URL:-http://127.0.0.1:${YYB_WORKER_PORT}}"
YYB_WORKER_CONTAINER="${YYB_WORKER_CONTAINER:-douyu-yyb-worker-dev}"
YYB_WORKER_DATA_DIR="${YYB_WORKER_DATA_DIR:-$ROOT_DIR/data/yyb-worker-dev}"
BACKEND_PID=""
FRONTEND_PID=""
WORKER_STARTED_BY_DEV=false
stop_tree() {
local pid="$1"
local children
if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
return
fi
children="$(pgrep -P "$pid" 2>/dev/null || true)"
for child in $children; do
stop_tree "$child"
done
kill "$pid" 2>/dev/null || true
}
cleanup() {
trap - EXIT INT TERM
stop_tree "$FRONTEND_PID"
stop_tree "$BACKEND_PID"
if [ "$WORKER_STARTED_BY_DEV" = true ]; then
docker stop "$YYB_WORKER_CONTAINER" >/dev/null 2>&1 || true
fi
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "缺少命令: $1"
exit 1
fi
}
require_cmd uv
require_cmd npm
require_cmd docker
detect_compose() {
if docker compose version >/dev/null 2>&1; then
echo "docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
echo "docker-compose"
else
echo ""
fi
}
detect_backend_proxy_host() {
if [ "$BACKEND_HOST" != "0.0.0.0" ] && [ "$BACKEND_HOST" != "::" ]; then
echo "$BACKEND_HOST"
return
fi
# 绑定 0.0.0.0 时,Vite 代理不要固定连 127.0.0.1;某些本地工具会抢占 loopback 端口。
local ip
ip="$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || true)"
if [ -z "$ip" ] && command -v hostname >/dev/null 2>&1; then
ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
fi
echo "${ip:-127.0.0.1}"
}
if [ -n "${DATABASE_URL:-}" ] && [[ "$DATABASE_URL" != mysql+* ]]; then
echo "dev.sh 只支持 MySQL,请移除 SQLite DATABASE_URL: $DATABASE_URL"
exit 1
fi
if [ -z "${MYSQL_PASSWORD:-}" ] || [ -z "${MYSQL_ROOT_PASSWORD:-}" ]; then
echo "请先在 .env 中设置 MYSQL_PASSWORD 和 MYSQL_ROOT_PASSWORD"
exit 1
fi
if [ -z "$DB_PASSWORD" ]; then
echo "请先在 .env 中设置 DB_PASSWORD 或 MYSQL_PASSWORD"
exit 1
fi
if [ -z "${YYB_WORKER_KEY:-}" ]; then
echo "请先在 .env 中设置 YYB_WORKER_KEY"
exit 1
fi
COMPOSE=$(detect_compose)
if [ -z "$COMPOSE" ]; then
echo "缺少 docker compose"
exit 1
fi
BACKEND_PROXY_HOST="${BACKEND_PROXY_HOST:-$(detect_backend_proxy_host)}"
BACKEND_PROXY_TARGET="${VITE_BACKEND_TARGET:-http://${BACKEND_PROXY_HOST}:${BACKEND_PORT}}"
mkdir -p data logs
mkdir -p "$YYB_WORKER_DATA_DIR"
trap cleanup EXIT INT TERM
start_yyb_worker() {
if docker ps --format '{{.Names}}' | grep -qx "$YYB_WORKER_CONTAINER"; then
echo "本地 YYB Worker 已运行: ${DEV_YYB_WORKER_URL}"
return
fi
if docker ps -a --format '{{.Names}}' | grep -qx "$YYB_WORKER_CONTAINER"; then
echo "发现已停止的本地 YYB Worker,正在清理旧容器..."
docker rm "$YYB_WORKER_CONTAINER" >/dev/null
fi
echo "正在构建本地 YYB Worker..."
YYB_WORKER_KEY="$YYB_WORKER_KEY" $COMPOSE build yyb-worker
echo "正在启动本地 YYB Worker..."
docker run -d --rm \
--name "$YYB_WORKER_CONTAINER" \
-p "127.0.0.1:${YYB_WORKER_PORT}:8810" \
-e TZ="${TZ:-Asia/Shanghai}" \
-e YYB_WORKER_KEY="$YYB_WORKER_KEY" \
-v "$YYB_WORKER_DATA_DIR:/app/config/worker-jobs" \
"$YYB_WORKER_IMAGE" >/dev/null
WORKER_STARTED_BY_DEV=true
echo "等待本地 YYB Worker 就绪..."
for i in $(seq 1 15); do
if curl -fsS "${DEV_YYB_WORKER_URL}/health" >/dev/null 2>&1; then
echo "YYB Worker 已就绪"
return
fi
if [ "$i" = "15" ]; then
echo "YYB Worker 启动超时,查看日志:"
docker logs "$YYB_WORKER_CONTAINER" || true
exit 1
fi
sleep 1
done
}
start_yyb_worker
echo "正在启动本地 MySQL..."
MYSQL_IMAGE="$MYSQL_IMAGE" \
MYSQL_BIND_HOST="$MYSQL_BIND_HOST" \
MYSQL_HOST_PORT="$MYSQL_HOST_PORT" \
$COMPOSE up -d mysql
echo "等待 MySQL 就绪..."
for i in $(seq 1 30); do
if $COMPOSE exec -T mysql sh -c 'mysqladmin ping -h localhost -uroot -p"$MYSQL_ROOT_PASSWORD" --silent' >/dev/null 2>&1; then
echo "MySQL 已就绪"
break
fi
if [ "$i" = "30" ]; then
echo "MySQL 启动超时,查看日志:"
$COMPOSE logs --tail 50 mysql
exit 1
fi
sleep 2
done
echo ""
echo "=============================="
echo " 本地调试模式"
echo "=============================="
echo " 后端: http://${BACKEND_HOST}:${BACKEND_PORT}"
echo " 前端: http://localhost:${FRONTEND_PORT}"
echo " API 代理: ${BACKEND_PROXY_TARGET}"
echo " MySQL: ${DB_HOST}:${DB_PORT}/${DB_NAME}"
echo " YYB Worker: ${DEV_YYB_WORKER_URL}"
echo " 退出: Ctrl+C"
echo "=============================="
echo ""
DB_HOST="$DB_HOST" \
DB_PORT="$DB_PORT" \
DB_NAME="$DB_NAME" \
DB_USER="$DB_USER" \
DB_PASSWORD="$DB_PASSWORD" \
DB_POOL_SIZE="$DB_POOL_SIZE" \
DB_MAX_OVERFLOW="$DB_MAX_OVERFLOW" \
DB_POOL_TIMEOUT="$DB_POOL_TIMEOUT" \
DB_POOL_RECYCLE="$DB_POOL_RECYCLE" \
YYB_WORKER_URL="$DEV_YYB_WORKER_URL" \
YYB_WORKER_KEY="$YYB_WORKER_KEY" \
LOG_LEVEL="$LOG_LEVEL" \
uv run uvicorn web.backend.main:app \
--host "$BACKEND_HOST" \
--port "$BACKEND_PORT" \
--reload \
--reload-dir core \
--reload-dir utils \
--reload-dir web/backend &
BACKEND_PID=$!
(
cd web/frontend
VITE_BACKEND_TARGET="$BACKEND_PROXY_TARGET" \
npm run dev -- --host "$FRONTEND_HOST" --port "$FRONTEND_PORT"
) &
FRONTEND_PID=$!
while true; do
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
wait "$BACKEND_PID"
exit $?
fi
if ! kill -0 "$FRONTEND_PID" 2>/dev/null; then
wait "$FRONTEND_PID"
exit $?
fi
sleep 1
done