#!/bin/bash # soul-api Docker 蓝绿部署脚本(在服务器上执行) # 用法:./docker-deploy-remote.sh /tmp/soul_api_image.tar.gz [--skip-nginx] # --skip-nginx:跳过 Nginx 切换,由宝塔 API 在本地执行 set -e PROJECT_ROOT="${DEPLOY_DOCKER_PATH:-/www/wwwroot/self/soul-dev}" ACTIVE_FILE="$PROJECT_ROOT/.active" NGINX_CONF="${DEPLOY_NGINX_CONF:-}" IMAGE_TAR="${1:-}" SKIP_NGINX="" if [ "${2:-}" = "--skip-nginx" ]; then SKIP_NGINX=1 fi if [ -z "$IMAGE_TAR" ] || [ ! -f "$IMAGE_TAR" ]; then echo "[ERROR] usage: $0 [--skip-nginx]" exit 1 fi cd "$PROJECT_ROOT" # 兼容 docker-compose / docker compose(不同系统安装不一致) dc() { if command -v docker-compose >/dev/null 2>&1; then docker-compose "$@" else docker compose "$@" fi } # 兼容 curl / wget(健康检查工具不一定都有) health_ok() { url="$1" if command -v curl >/dev/null 2>&1; then curl -sf "$url" >/dev/null 2>&1 else wget -qO- "$url" >/dev/null 2>&1 fi } # 加载新镜像 echo "[1/5] 加载 Docker 镜像 ..." gunzip -c "$IMAGE_TAR" | docker load rm -f "$IMAGE_TAR" # 确定当前活跃实例与待启动实例 CURRENT="blue" if [ -f "$ACTIVE_FILE" ]; then CURRENT=$(cat "$ACTIVE_FILE") fi if [ "$CURRENT" = "blue" ]; then NEW="green" OLD_PORT=9001 NEW_PORT=9002 else NEW="blue" OLD_PORT=9002 NEW_PORT=9001 fi echo "[2/5] 当前活跃: $CURRENT ($OLD_PORT),将启动: $NEW ($NEW_PORT)" # 启动新实例 echo "[3/5] 启动 soul-api-$NEW ..." # --no-deps:线上 Redis 已在跑,不再让 compose 拉起/重建依赖 dc -f docker-compose.bluegreen.yml up -d --no-deps "soul-api-$NEW" # 等待健康检查(镜像已从 tar.gz 加载,无需联网拉取,最多 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 " 健康检查通过 ($((5 + i * 2))s)" break fi sleep 2 if [ $i -eq 58 ]; then echo "[ERROR] 健康检查超时(120s),新实例未就绪。可查看: docker-compose -f docker-compose.bluegreen.yml logs soul-api-$NEW" dc -f docker-compose.bluegreen.yml stop "soul-api-$NEW" exit 1 fi done # 切换 Nginx(若配置了 NGINX_CONF):将 proxy_pass 中的端口改为 NEW_PORT if [ -z "$SKIP_NGINX" ]; then CONF_TO_EDIT="$NGINX_CONF" # 自动兜底:如果未传入 DEPLOY_NGINX_CONF,则尝试在宝塔默认目录中定位 vhost 配置文件 if [ -z "$CONF_TO_EDIT" ] || [ ! -f "$CONF_TO_EDIT" ]; then CONF_DIR="${DEPLOY_NGINX_CONF_DIR:-/www/server/panel/vhost/nginx}" if [ -d "$CONF_DIR" ]; then # 优先匹配旧/新端口对应的 proxy_pass,尽量减少误命中 for p in "$OLD_PORT" "$NEW_PORT"; do # proxy_pass 前可能带空格;用正则增强匹配容错 match="$(grep -rlE "proxy_pass[[:space:]]+http://(127\\.0\\.0\\.1|localhost|0\\.0\\.0\\.0):${p}" "$CONF_DIR" 2>/dev/null | sed -n '1p')" if [ -n "$match" ]; then CONF_TO_EDIT="$match" break fi done # 如果仍未匹配,尝试按域名关键字(可选:DEPLOY_DOMAIN) if [ -z "$CONF_TO_EDIT" ] && [ -n "${DEPLOY_DOMAIN:-}" ]; then match="$(grep -rl "${DEPLOY_DOMAIN}" "$CONF_DIR" 2>/dev/null | head -n 1)" if [ -n "$match" ]; then CONF_TO_EDIT="$match" fi fi fi fi if [ -n "$CONF_TO_EDIT" ] && [ -f "$CONF_TO_EDIT" ]; then echo "[5/5] 切换 Nginx 到 $NEW_PORT ...(编辑: $CONF_TO_EDIT)" # 只在同一个 vhost 配置里替换 proxy_pass 上游端口 sed -i.bak "s|proxy_pass http://127.0.0.1:[0-9]*|proxy_pass http://127.0.0.1:$NEW_PORT|g" "$CONF_TO_EDIT" sed -i.bak "s|proxy_pass http://localhost:[0-9]*|proxy_pass http://127.0.0.1:$NEW_PORT|g" "$CONF_TO_EDIT" sed -i.bak "s|proxy_pass http://0.0.0.0:[0-9]*|proxy_pass http://127.0.0.1:$NEW_PORT|g" "$CONF_TO_EDIT" nginx -t && nginx -s reload echo " Nginx 已重载" else echo "[5/5] 未找到可编辑的 nginx 配置文件,跳过 Nginx 切换。请手动将 proxy_pass 改为 127.0.0.1:$NEW_PORT" fi else echo "[5/5] 已跳过 Nginx 切换(--skip-nginx)" fi # 停止旧实例(首次部署时可能不存在,忽略错误) dc -f docker-compose.bluegreen.yml stop "soul-api-$CURRENT" 2>/dev/null || true echo "$NEW" > "$ACTIVE_FILE" echo "" echo "[SUCCESS] 部署完成,当前活跃: $NEW (端口 $NEW_PORT)"