- server / web 均用 setsid 独立进程组,Ctrl-C 时 kill -TERM -PID 整组回收, 避免 tsx watch / vite 派生的 node 子进程残留占用 8000 / 5173 - cleanup 改用 PIDS 数组,跳过空 PID,修正原版 WEB_PID 空串导致 kill 接空参的问题 - server 进程意外退出(如端口占用)时立即失败退出,不再傻等 web - 端口行为保持原样:用户访问 :5173(vite),:8000 为内部 server
119 lines
3.9 KiB
Bash
Executable File
119 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# tts-like 一键启动脚本
|
||
#
|
||
# 用法: ./run.sh
|
||
# 或: bash run.sh
|
||
#
|
||
# 行为:
|
||
# 1. 加载 nvm + 启用 pnpm(每台机器首次跑会自动配好)
|
||
# 2. node_modules 缺失时自动 pnpm install
|
||
# 3. 后台启动 boardgame.io relay server(:8000)
|
||
# 4. 前台启动 vite dev(:5173)
|
||
# 5. Ctrl-C 一起干净退出
|
||
#
|
||
# 启动后打开 http://localhost:5173
|
||
|
||
set -euo pipefail
|
||
|
||
# ---------- 1. 加载 nvm + pnpm ----------
|
||
export NVM_DIR="$HOME/.nvm"
|
||
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
||
# shellcheck disable=SC1091
|
||
. "$NVM_DIR/nvm.sh"
|
||
fi
|
||
|
||
# 没有 node 直接报错(提示装 nvm)
|
||
if ! command -v node >/dev/null 2>&1; then
|
||
echo "✗ 未找到 node。请先装 nvm + Node 22:"
|
||
echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash"
|
||
echo " nvm install 22"
|
||
exit 1
|
||
fi
|
||
|
||
NODE_VERSION=$(node -v)
|
||
echo "✓ node $NODE_VERSION"
|
||
|
||
# 启用 pnpm(corepack 是 Node 自带)
|
||
if ! command -v pnpm >/dev/null 2>&1; then
|
||
echo "→ 启用 pnpm (corepack)..."
|
||
corepack enable >/dev/null 2>&1 || true
|
||
fi
|
||
echo "✓ pnpm $(pnpm --version)"
|
||
|
||
# 切到项目根目录(脚本所在目录)
|
||
cd "$(dirname "$0")"
|
||
|
||
# ---------- 2. 依赖检查 ----------
|
||
if [ ! -d node_modules ]; then
|
||
echo "→ 首次运行,安装依赖(可能要 1-2 分钟)..."
|
||
pnpm install
|
||
fi
|
||
echo "✓ 依赖就绪"
|
||
|
||
# ---------- 3. 清理可能残留的旧进程 ----------
|
||
# 端口 8000 / 5173 若被占用会启动失败,这里只做提示
|
||
for port in 8000 5173; do
|
||
if command -v ss >/dev/null 2>&1 && ss -ltn "sport = :$port" 2>/dev/null | grep -q ":$port"; then
|
||
echo "⚠ 端口 $port 已被占用,可能导致启动失败。如需清理:lsof -ti:$port | xargs kill"
|
||
fi
|
||
done
|
||
|
||
# ---------- 4. 后台启动 server ----------
|
||
echo ""
|
||
echo "→ 启动 boardgame.io server (:8000)..."
|
||
setsid pnpm --filter @tts-like/server dev &
|
||
SERVER_PID=$!
|
||
PIDS=("$SERVER_PID")
|
||
WEB_PID=""
|
||
|
||
# 5. 退出时一起杀掉 server / web
|
||
cleanup() {
|
||
echo ""
|
||
echo "→ 退出,清理子进程..."
|
||
# 用进程组杀法,确保 tsx watch 派生的 node 子进程也一并退出(否则会残留占用端口)
|
||
for pid in "${PIDS[@]:-}"; do
|
||
[ -n "$pid" ] || continue
|
||
kill -TERM -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
|
||
done
|
||
for pid in "${PIDS[@]:-}"; do
|
||
[ -n "$pid" ] && kill -9 "$pid" 2>/dev/null || true
|
||
done
|
||
wait 2>/dev/null || true
|
||
echo "✓ 已退出"
|
||
}
|
||
trap cleanup EXIT INT TERM
|
||
|
||
# 等 server 起来(最多 15 秒,看到 listening 才继续)
|
||
echo "→ 等待 server 就绪..."
|
||
for i in $(seq 1 30); do
|
||
if curl -sf http://localhost:8000/healthz >/dev/null 2>&1; then
|
||
echo "✓ server 就绪"
|
||
break
|
||
fi
|
||
# server 进程已退出(如端口被占用崩溃)→ 直接失败退出,避免傻等
|
||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||
echo "✗ server 进程已退出,启动失败。检查上方日志(常见于 :8000 端口被占用)。"
|
||
exit 1
|
||
fi
|
||
sleep 0.5
|
||
done
|
||
|
||
# ---------- 6. 前台启动 web ----------
|
||
echo ""
|
||
echo "════════════════════════════════════════════════════════════════"
|
||
echo " tts-like 已启动"
|
||
echo " Web: http://localhost:5173"
|
||
echo " Server: http://localhost:8000 (REST /api, WebSocket /socket.io)"
|
||
echo " 健康: http://localhost:8000/healthz"
|
||
echo ""
|
||
echo " 局域网其它设备访问: http://<本机内网IP>:5173/"
|
||
echo " Ctrl-C 一起退出"
|
||
echo "════════════════════════════════════════════════════════════════"
|
||
echo ""
|
||
|
||
# 前台跑 vite(Ctrl-C 会同时打到前台进程组,web 退出后 trap 清理 server)
|
||
setsid pnpm --filter @tts-like/web dev &
|
||
WEB_PID=$!
|
||
PIDS+=("$WEB_PID")
|
||
wait "$WEB_PID" 2>/dev/null || true
|