You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
3.5 KiB
119 lines
3.5 KiB
#!/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" |