#!/usr/bin/env bash # MiroSociety 后台启动 (开发模式) # 前置: 请先执行 ./deploy.sh 安装依赖 # 前端: http://localhost:31400 后端: http://localhost:31401 # # 直接 ./start.sh 即可启动;已在跑会自动重启。 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" FRONTEND_PORT=31400 BACKEND_PORT=31401 VENV_DIR="$SCRIPT_DIR/backend/.venv" BACKEND_DIR="$SCRIPT_DIR/backend" FRONTEND_DIR="$SCRIPT_DIR/frontend" RUN_DIR="$SCRIPT_DIR/.run" BACKEND_PID_FILE="$RUN_DIR/backend.pid" FRONTEND_PID_FILE="$RUN_DIR/frontend.pid" BACKEND_LOG="$SCRIPT_DIR/backend.log" FRONTEND_LOG="$SCRIPT_DIR/frontend.log" cd "$SCRIPT_DIR" # --- 颜色 --- RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' log() { echo -e "${GREEN}[start.sh]${NC} $*"; } warn() { echo -e "${YELLOW}[start.sh]${NC} $*"; } err() { echo -e "${RED}[start.sh]${NC} $*" >&2; } # --- 检查依赖 --- for cmd in lsof npm node curl; do command -v "$cmd" >/dev/null 2>&1 || { err "缺少依赖命令: $cmd"; exit 1; } done [[ -x "$VENV_DIR/bin/python3" ]] || { err "未检测到 $VENV_DIR/bin/python3,请先执行 ./deploy.sh"; exit 1; } "$VENV_DIR/bin/python3" -c "import uvicorn" 2>/dev/null || { err "venv 缺少 uvicorn,请先执行 ./deploy.sh"; exit 1; } [[ -d "$FRONTEND_DIR/node_modules" ]] || { err "未检测到 frontend/node_modules,请先执行 ./deploy.sh"; exit 1; } mkdir -p "$RUN_DIR" # --- 已在跑 → 先杀 --- read_pid() { [[ -f "$1" ]] && cat "$1" || echo ""; } kill_pid_file() { local pid_file="$1" label="$2" local pid pid=$(read_pid "$pid_file") if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true for _ in $(seq 1 10); do kill -0 "$pid" 2>/dev/null || break sleep 0.3 done kill -9 "$pid" 2>/dev/null || true log "$label 已停止 (pid=$pid)" fi rm -f "$pid_file" } free_port() { local port="$1" local pids pids=$(lsof -ti tcp:"$port" 2>/dev/null || true) if [[ -n "$pids" ]]; then warn "端口 $port 被占用 (pid: $pids),正在终止..." # shellcheck disable=SC2086 kill -9 $pids 2>/dev/null || true sleep 1 fi } # --- 启动后端 --- free_port "$BACKEND_PORT" kill_pid_file "$BACKEND_PID_FILE" "后端" log "启动后端 FastAPI @ :$BACKEND_PORT (后台)" : >"$BACKEND_LOG" ( cd "$BACKEND_DIR" HOST=0.0.0.0 PORT="$BACKEND_PORT" \ nohup "$VENV_DIR/bin/python3" -m uvicorn app.main:app \ --host 0.0.0.0 --port "$BACKEND_PORT" \ >>"$BACKEND_LOG" 2>&1 & echo $! >"$BACKEND_PID_FILE" disown || true ) # --- 启动前端 --- free_port "$FRONTEND_PORT" kill_pid_file "$FRONTEND_PID_FILE" "前端" log "启动前端 Vite @ :$FRONTEND_PORT (后台)" : >"$FRONTEND_LOG" ( cd "$FRONTEND_DIR" nohup npx vite >>"$FRONTEND_LOG" 2>&1 & echo $! >"$FRONTEND_PID_FILE" disown || true ) # --- 健康检查 --- log "等待服务就绪..." wait_for() { local url="$1" name="$2" for _ in $(seq 1 30); do if curl -sf "$url" >/dev/null 2>&1; then log "$name 已启动 ✓ ($url)" return 0 fi sleep 1 done err "$name 启动超时,请查看 ${name%% *}.log" return 1 } wait_for "http://localhost:$BACKEND_PORT/health" "后端" || true wait_for "http://localhost:$FRONTEND_PORT" "前端" || true echo log "全部就绪 ✓" echo -e " ${GREEN}前端:${NC} http://localhost:$FRONTEND_PORT" echo -e " ${GREEN}后端:${NC} http://localhost:$BACKEND_PORT (健康检查: /health)" echo -e " ${GREEN}日志:${NC} backend.log / frontend.log"