#!/bin/bash # soul-api Runner 容器内红蓝切换脚本 # 用法: /app/deploy.sh /tmp/incoming.tar.gz # 将新版本解压到非活跃目录,健康检查通过后切换 nginx 并停旧实例 set -e INCOMING="${1:-}" APP_ROOT="/app" BLUE="$APP_ROOT/blue" GREEN="$APP_ROOT/green" ACTIVE_FILE="$APP_ROOT/.active" NGINX_CONF="$APP_ROOT/nginx.conf" NGINX_PID="/tmp/nginx.pid" REDIS_PASS="soul-docker-redis" health_ok() { local url="$1" if command -v wget >/dev/null 2>&1; then wget -qO- "$url" >/dev/null 2>&1 else [ -x /usr/bin/wget ] && /usr/bin/wget -qO- "$url" >/dev/null 2>&1 fi } if [ -z "$INCOMING" ] || [ ! -f "$INCOMING" ]; then echo "[ERROR] 用法: $0 " exit 1 fi # 确定当前活跃与待部署目录 CURRENT="blue" [ -f "$ACTIVE_FILE" ] && CURRENT=$(cat "$ACTIVE_FILE") [ "$CURRENT" != "blue" ] && [ "$CURRENT" != "green" ] && CURRENT="blue" if [ "$CURRENT" = "blue" ]; then NEW="green" NEW_PORT=18082 OLD_PORT=18081 else NEW="blue" NEW_PORT=18081 OLD_PORT=18082 fi NEW_DIR="$APP_ROOT/$NEW" echo "[1/5] 当前活跃: $CURRENT ($OLD_PORT),将部署到: $NEW ($NEW_PORT)" # 解压到新目录 echo "[2/5] 解压到 $NEW_DIR ..." rm -rf "$NEW_DIR" mkdir -p "$NEW_DIR" tar -xzf "$INCOMING" -C "$NEW_DIR" rm -f "$INCOMING" # 设置 PORT 和 REDIS_URL ENV_FILE="$NEW_DIR/.env" if [ -f "$ENV_FILE" ]; then sed -i "s/^PORT=.*/PORT=$NEW_PORT/" "$ENV_FILE" grep -q "^REDIS_URL=" "$ENV_FILE" || echo "REDIS_URL=redis://:${REDIS_PASS}@127.0.0.1:6379/0" >> "$ENV_FILE" sed -i "s|^REDIS_URL=.*|REDIS_URL=redis://:${REDIS_PASS}@127.0.0.1:6379/0|" "$ENV_FILE" fi chmod +x "$NEW_DIR/soul-api" 2>/dev/null || true # 启动新实例 echo "[3/5] 启动 soul-api-$NEW (端口 $NEW_PORT) ..." cd "$NEW_DIR" export PORT=$NEW_PORT export REDIS_URL="redis://:${REDIS_PASS}@127.0.0.1:6379/0" nohup ./soul-api >> soul-api.log 2>&1 & NEW_PID=$! echo $NEW_PID > "$APP_ROOT/.pid.$NEW" cd - >/dev/null # 等待健康检查(最多 120 秒) echo "[4/5] 等待健康检查 ..." sleep 5 for i in $(seq 1 58); do if health_ok "http://127.0.0.1:$NEW_PORT/health"; then echo " 健康检查通过" break fi sleep 2 if [ $i -eq 58 ]; then echo "[ERROR] 健康检查超时,新实例未就绪" kill $NEW_PID 2>/dev/null || true exit 1 fi done # 更新 nginx 配置并重载 echo "[5/5] 切换 nginx 到 $NEW_PORT ..." sed "s/__BACKEND_PORT__/$NEW_PORT/g" "$APP_ROOT/nginx.conf.template" > "$NGINX_CONF" nginx -s reload 2>/dev/null || nginx -c "$NGINX_CONF" 2>/dev/null || true # 停止旧实例(通过 PID 文件或端口) OLD_PID_FILE="$APP_ROOT/.pid.$CURRENT" if [ -f "$OLD_PID_FILE" ]; then OLD_PID=$(cat "$OLD_PID_FILE") if kill -0 "$OLD_PID" 2>/dev/null; then echo " 停止旧实例 (PID $OLD_PID)" kill "$OLD_PID" 2>/dev/null || true sleep 2 fi rm -f "$OLD_PID_FILE" fi # 兜底:通过端口杀进程(Alpine 可用 fuser 或 ss) if command -v fuser >/dev/null 2>&1; then fuser -k "$OLD_PORT/tcp" 2>/dev/null || true fi echo "$NEW" > "$ACTIVE_FILE" echo "" echo "[SUCCESS] 部署完成,当前活跃: $NEW (端口 $NEW_PORT)"