Files
iOSBuildServer/deploy/install-service.sh
T
shenleiandClaude Opus 4.8 b2982ae8f7 feat: deploy.sh 支持一键部署与就绪探测
- 新增 up 子命令:build + start + watchdog 一键强制重来
- start 改为轮询 /api/health 就绪才返回,消除启动竞态误报
- start 增加端口占用守卫:非本项目进程占用时明确报错
- watchdog 增加启动宽限(WATCHDOG_STARTUP_GRACE),避开启动窗口误重启
- watchdog 支持 launchd 模式(WATCHDOG_RESTART_MODE),用 launchctl
  kickstart 重启以避免与 KeepAlive 抢端口
- 抽取 _spawn_server/_wait_ready/foreign_port_holder 公共函数

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 15:50:59 +09:00

148 lines
4.1 KiB
Bash
Executable File

#!/bin/bash
# 安装/卸载 macOS launchd 服务(主服务 + watchdog 健康监测)
# 用法: ./deploy/install-service.sh {install|uninstall}
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# 加载 .env
if [ -f "$PROJECT_DIR/.env" ]; then
set -a; source "$PROJECT_DIR/.env"; set +a
fi
PORT="${BACKEND_PORT:-8000}"
VENV_DIR="$PROJECT_DIR/.venv"
LOG_DIR="$PROJECT_DIR/logs"
PLIST_APP="com.readoor.buildserver"
PLIST_WD="com.readoor.buildserver.watchdog"
PLIST_APP_TARGET="$HOME/Library/LaunchAgents/$PLIST_APP.plist"
PLIST_WD_TARGET="$HOME/Library/LaunchAgents/$PLIST_WD.plist"
do_install() {
if [ ! -d "$VENV_DIR" ]; then
echo "请先执行 ./deploy.sh build"
exit 1
fi
if [ ! -d "$PROJECT_DIR/backend/static" ]; then
echo "请先执行 ./deploy.sh build"
exit 1
fi
mkdir -p "$LOG_DIR"
# ---- 主服务 plist ----
cat > "$PLIST_APP_TARGET" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$PLIST_APP</string>
<key>ProgramArguments</key>
<array>
<string>$VENV_DIR/bin/python3</string>
<string>-m</string>
<string>uvicorn</string>
<string>backend.main:app</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>$PORT</string>
<string>--workers</string>
<string>1</string>
</array>
<key>WorkingDirectory</key>
<string>$PROJECT_DIR</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>$VENV_DIR/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>$LOG_DIR/server-stdout.log</string>
<key>StandardErrorPath</key>
<string>$LOG_DIR/server-stderr.log</string>
</dict>
</plist>
EOF
# ---- Watchdog plist ----
cat > "$PLIST_WD_TARGET" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$PLIST_WD</string>
<key>ProgramArguments</key>
<array>
<string>$PROJECT_DIR/deploy.sh</string>
<string>_watchdog_loop</string>
</array>
<key>WorkingDirectory</key>
<string>$PROJECT_DIR</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>$VENV_DIR/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>WATCHDOG_RESTART_MODE</key>
<string>launchd</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>$LOG_DIR/watchdog-stdout.log</string>
<key>StandardErrorPath</key>
<string>$LOG_DIR/watchdog-stderr.log</string>
</dict>
</plist>
EOF
# 加载服务
launchctl unload "$PLIST_WD_TARGET" 2>/dev/null || true
launchctl unload "$PLIST_APP_TARGET" 2>/dev/null || true
launchctl load "$PLIST_APP_TARGET"
launchctl load "$PLIST_WD_TARGET"
echo "服务已安装并启动"
echo " 主服务: http://localhost:$PORT"
echo " Watchdog: 每 ${WATCHDOG_INTERVAL:-30}s 健康检查,故障自动重启"
echo " 日志目录: $LOG_DIR"
echo ""
echo "卸载命令: $0 uninstall"
}
do_uninstall() {
if [ -f "$PLIST_WD_TARGET" ]; then
launchctl unload "$PLIST_WD_TARGET" 2>/dev/null || true
rm -f "$PLIST_WD_TARGET"
echo "Watchdog 已卸载"
fi
if [ -f "$PLIST_APP_TARGET" ]; then
launchctl unload "$PLIST_APP_TARGET" 2>/dev/null || true
rm -f "$PLIST_APP_TARGET"
echo "主服务已卸载"
fi
}
case "${1:-}" in
install) do_install ;;
uninstall) do_uninstall ;;
*)
echo "用法: $0 {install|uninstall}"
exit 1
;;
esac