name: Deploy Server (SSH + Nginx 蓝绿)

# 手动触发：选择部署环境（dev/pro）并输入要部署的镜像 tag
on:
  workflow_dispatch:
    inputs:
      env:
        description: '部署环境'
        type: choice
        required: true
        options:
          - dev
          - pro
      tag:
        description: '要部署的镜像 Tag（例如：v1.2.7）'
        required: true

# 同一环境同一时间只允许一个部署在跑，避免互相覆盖
concurrency:
  group: deploy-server-${{ github.event.inputs.env }}
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      # 统一在 env 中注入变量，减少 runner 差异带来的兼容性问题
      DEPLOY_ENV: ${{ github.event.inputs.env }}
      DEPLOY_TAG: ${{ github.event.inputs.tag }}

      # 镜像名（优先 vars.DOCKER_IMAGE；未配置则步骤里兜底）
      DOCKER_IMAGE: ${{ vars.DOCKER_IMAGE }}

      # 可选：明确 registry（私有仓库用）。未配置会从 DOCKER_IMAGE 推断
      DOCKER_REGISTRY: ${{ vars.DOCKER_REGISTRY }}

      # SSH：dev 用 secrets，pro 用 vars（按你现有用法）
      SSH_HOST: ${{ github.event.inputs.env == 'dev' && secrets.DEV_SSH_HOST || vars.PRO_SSH_HOST }}
      SSH_USER: ${{ github.event.inputs.env == 'dev' && secrets.DEV_SSH_USER || vars.PRO_SSH_USER }}
      SSH_PORT: ${{ github.event.inputs.env == 'dev' && secrets.DEV_SSH_PORT || vars.PRO_SSH_PORT }}
      SSH_KEY: ${{ github.event.inputs.env == 'dev' && secrets.DEV_SSH_KEY || secrets.PRO_SSH_KEY }}
      # 推荐把私钥做成 base64（单行）存到 Secrets，避免多行变量丢换行
      SSH_KEY_B64: ${{ github.event.inputs.env == 'dev' && secrets.DEV_SSH_KEY_B64 || secrets.PRO_SSH_KEY_B64 }}

      # Nginx upstream 配置（建议放到 vars）
      NGINX_UPSTREAM_FILE: ${{ vars.NGINX_UPSTREAM_FILE }}
      NGINX_UPSTREAM_NAME: ${{ vars.NGINX_UPSTREAM_NAME }}

      # 蓝绿端口（宿主机端口，建议放到 vars；未配置则脚本内有默认值）
      BLUE_PORT: ${{ vars.BLUE_PORT }}
      GREEN_PORT: ${{ vars.GREEN_PORT }}

      # 容器内监听端口（FastAPI 常用 8000；未配置则默认 8000）
      CONTAINER_PORT: ${{ vars.CONTAINER_PORT }}

      # 健康检查路径（未配置则默认 /health；如果你没有 health 接口，可改为 /docs 或 /）
      HEALTHCHECK_PATH: ${{ vars.HEALTHCHECK_PATH }}

      # 定时服务健康检查路径（检查 Redis/Worker/Beat；未配置则默认 /v1/push/scheduler/health）
      SCHEDULER_HEALTHCHECK_PATH: ${{ vars.SCHEDULER_HEALTHCHECK_PATH }}

      # 可选：远端 env 文件路径（例如 /opt/mindfulness-server/.env.prod），存在则 docker run --env-file
      REMOTE_ENV_FILE: ${{ vars.REMOTE_ENV_FILE }}

    steps:
      - name: 配置 SSH Key（密钥登陆）
        shell: bash
        run: |
          set -euo pipefail

          # 镜像名兜底（与 .gitea/workflows/server-build.yml 的默认一致）
          if [[ -z "${DOCKER_IMAGE:-}" ]]; then
            DOCKER_IMAGE="docker.damer.fun/damer/mindfulness-server"
          fi
          if [[ -n "${GITHUB_ENV:-}" ]]; then
            echo "DOCKER_IMAGE=${DOCKER_IMAGE}" >> "$GITHUB_ENV"
          fi
          if [[ -n "${GITEA_ENV:-}" ]]; then
            echo "DOCKER_IMAGE=${DOCKER_IMAGE}" >> "$GITEA_ENV"
          fi

          mkdir -p ~/.ssh
          chmod 700 ~/.ssh

          # 从 Secrets 写入私钥（推荐使用 *_SSH_KEY_B64）
          SSH_KEY_PATH="${HOME}/.ssh/id_rsa"
          if [[ -n "${SSH_KEY_B64:-}" ]]; then
            # 兼容两种常见误配置：
            # 1) *_SSH_KEY_B64 里其实粘贴的是“原始私钥”（包含 -----BEGIN ... PRIVATE KEY-----）
            # 2) base64 值在粘贴/保存过程中混入空白或其他无关字符
            if printf '%s' "${SSH_KEY_B64}" | grep -qE 'BEGIN[[:space:]].*PRIVATE[[:space:]]KEY'; then
              echo "提示：检测到 *_SSH_KEY_B64 看起来是原始私钥内容，将按原始私钥写入（建议你改用真正的 base64 单行值）。"
              echo "SSH_KEY_B64 字符数（原始）：${#SSH_KEY_B64}"
              printf '%s' "${SSH_KEY_B64}" | tr -d '\r' > "${SSH_KEY_PATH}"
            else
              CLEAN_B64="$(printf '%s' "${SSH_KEY_B64}" | tr -d '\r\n\t ')"
              echo "SSH_KEY_B64 字符数（原始/清理后）：${#SSH_KEY_B64}/${#CLEAN_B64}"
              if ! printf '%s' "${CLEAN_B64}" | base64 -d -i | tr -d '\r' > "${SSH_KEY_PATH}"; then
                echo "私钥 base64 解码失败："
                echo "- 请确认你在 Gitea Secrets 配置的 *_SSH_KEY_B64 是“单行 base64 字符串”，不要带引号/前后空格。"
                echo "- 推荐用仓库里的脚本生成并复制：node scripts/ssh-key-to-b64.mjs ~/.ssh/你的私钥 --clipboard"
                exit 1
              fi
            fi
          else
            echo "使用 SSH_KEY（原始私钥）写入。SSH_KEY 字符数：${#SSH_KEY}"
            printf '%s' "${SSH_KEY}" | tr -d '\r' > "${SSH_KEY_PATH}"
          fi
          chmod 600 "${SSH_KEY_PATH}"

          # 自检（不输出私钥内容，仅输出元信息，方便定位是否写入了错误文件/被截断）
          echo "SSH_KEY_PATH=${SSH_KEY_PATH}"
          KEY_BYTES="$(wc -c < "${SSH_KEY_PATH}" | tr -d ' ')"
          KEY_LINES="$(wc -l < "${SSH_KEY_PATH}" | tr -d ' ')"
          echo "SSH key 字节数：${KEY_BYTES}"
          echo "SSH key 行数：${KEY_LINES}"
          echo "SSH key 首行：$(head -n 1 "${SSH_KEY_PATH}" | tr -d '\r')"
          echo "SSH key 末行：$(tail -n 1 "${SSH_KEY_PATH}" | tr -d '\r')"
          if command -v stat >/dev/null 2>&1; then
            # Ubuntu runner: stat -c；不同系统做兼容
            if stat -c '%a %U %G %n' "${SSH_KEY_PATH}" >/dev/null 2>&1; then
              echo "SSH key 权限：$(stat -c '%a %U %G %n' "${SSH_KEY_PATH}")"
            else
              echo "SSH key 权限：$(stat -f '%Lp %Su %Sg %N' "${SSH_KEY_PATH}" 2>/dev/null || true)"
            fi
          fi
          if command -v file >/dev/null 2>&1; then
            echo "file(1) 识别：$(file "${SSH_KEY_PATH}")"
          fi
          if command -v sha256sum >/dev/null 2>&1; then
            echo "SSH key sha256(前12位)：$(sha256sum "${SSH_KEY_PATH}" | awk '{print substr($1,1,12)}')"
          fi
          echo "ssh 版本：$(ssh -V 2>&1 || true)"
          echo "ssh-keygen 版本：$(ssh-keygen -V 2>&1 || true)"
          echo "openssl 版本：$(openssl version 2>&1 || true)"

          # 进一步校验：OpenSSH 私钥中间的 base64 块是否可解码（不输出内容）
          if grep -q '^-----BEGIN OPENSSH PRIVATE KEY-----$' "${SSH_KEY_PATH}" && grep -q '^-----END OPENSSH PRIVATE KEY-----$' "${SSH_KEY_PATH}"; then
            # 注意：不要用变量名 in（是 awk 关键字，部分实现会报语法错）
            OPENSSH_B64_LEN="$(
              awk '
                BEGIN{in_block=0; n=0}
                /^-----BEGIN OPENSSH PRIVATE KEY-----$/{in_block=1; next}
                /^-----END OPENSSH PRIVATE KEY-----$/{in_block=0; exit}
                in_block==1{gsub(/\r/,""); n+=length($0)}
                END{print n}
              ' "${SSH_KEY_PATH}" 2>/dev/null || true
            )"
            if [[ -n "${OPENSSH_B64_LEN:-}" ]]; then
              echo "OpenSSH base64 块字符数（合计）：${OPENSSH_B64_LEN}"
            fi

            # 该校验仅用于提示，不应阻断部署流程
            if ! awk '
              BEGIN{in_block=0}
              /^-----BEGIN OPENSSH PRIVATE KEY-----$/{in_block=1; next}
              /^-----END OPENSSH PRIVATE KEY-----$/{in_block=0; exit}
              in_block==1{gsub(/\r/,""); print}
            ' "${SSH_KEY_PATH}" 2>/dev/null | tr -d '\n' | base64 -d >/dev/null 2>&1; then
              echo "提示：OpenSSH 私钥的 base64 块无法解码（疑似内容被截断/损坏），将继续执行 ssh-keygen 校验定位。"
            fi
          fi

          # 额外信息：ssh-keygen -lf 的报错（不输出私钥内容）
          if ! ssh-keygen -lf "${SSH_KEY_PATH}" >/dev/null 2>~/.ssh/ssh_key_fingerprint.err; then
            echo "ssh-keygen -lf 报错："
            cat ~/.ssh/ssh_key_fingerprint.err || true
          fi

          # 快速校验私钥是否可解析（不会输出私钥内容）
          if ! ssh-keygen -y -f "${SSH_KEY_PATH}" >/dev/null 2>~/.ssh/ssh_key_check.err; then
            echo "SSH 私钥无法解析。下面是 ssh-keygen 的报错（不包含私钥内容）："
            cat ~/.ssh/ssh_key_check.err || true
            echo
            echo "常见原因："
            echo "- 你填的是公钥（.pub），不是私钥"
            echo "- 私钥带口令（CI 无法交互输入 passphrase）"
            echo "- 内容复制丢换行/被截断/不是正确的 base64"
            echo
            echo "推荐：生成一把“无口令”的部署专用私钥，并用 base64 存储到 Secrets（*_SSH_KEY_B64）。"
            exit 1
          fi

          SSH_PORT="${SSH_PORT:-22}"
          if [[ -n "${GITHUB_ENV:-}" ]]; then
            echo "SSH_PORT=${SSH_PORT}" >> "$GITHUB_ENV"
          fi
          if [[ -n "${GITEA_ENV:-}" ]]; then
            echo "SSH_PORT=${SSH_PORT}" >> "$GITEA_ENV"
          fi

          # 预写 known_hosts，避免交互
          ssh-keyscan -p "${SSH_PORT}" -H "${SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null

      - name: 验证 SSH 连接（快速定位公钥/用户/端口问题）
        shell: bash
        run: |
          set -euo pipefail
          SSH_PORT="${SSH_PORT:-22}"
          ssh -p "${SSH_PORT}" \
            -i ~/.ssh/id_rsa \
            -o StrictHostKeyChecking=yes \
            -o BatchMode=yes \
            -o IdentitiesOnly=yes \
            "${SSH_USER}@${SSH_HOST}" 'echo "SSH_OK $(whoami)@$(hostname)"'

      - name: 远程登录 Docker Registry（如需私有镜像）
        shell: bash
        env:
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
        run: |
          set -euo pipefail

          # 如果镜像是公开的，可不配 DOCKER_USERNAME/DOCKER_TOKEN；此步骤会自动跳过
          if [[ -z "${DOCKER_USERNAME:-}" || -z "${DOCKER_TOKEN:-}" ]]; then
            echo "未配置 DOCKER_USERNAME/DOCKER_TOKEN，跳过远程 docker login。"
            exit 0
          fi

          SSH_PORT="${SSH_PORT:-22}"

          # 推断 registry：优先使用 DOCKER_REGISTRY；否则从 DOCKER_IMAGE 的第一个段推断
          REG="${DOCKER_REGISTRY:-}"
          if [[ -z "${REG}" ]]; then
            FIRST_SEG="${DOCKER_IMAGE%%/*}"
            if [[ "${FIRST_SEG}" == *.* || "${FIRST_SEG}" == *:* || "${FIRST_SEG}" == "localhost" ]]; then
              REG="${FIRST_SEG}"
            fi
          fi
          if [[ -z "${REG}" ]]; then
            echo "无法推断 registry（看起来像 Docker Hub 公有镜像），跳过 docker login。"
            exit 0
          fi

          ssh -p "${SSH_PORT}" -i ~/.ssh/id_rsa -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes \
            "${SSH_USER}@${SSH_HOST}" bash -s -- "${DOCKER_TOKEN}" "${DOCKER_USERNAME}" "${REG}" <<'REMOTE'
          set -euo pipefail

          TOKEN="$1"
          USERNAME="$2"
          REGISTRY="$3"

          SUDO=""
          if [[ "$(id -u)" -ne 0 ]] && command -v sudo >/dev/null 2>&1; then
            SUDO="sudo"
          fi

          printf '%s' "$TOKEN" | ${SUDO} docker login "${REGISTRY}" -u "$USERNAME" --password-stdin
          REMOTE

      - name: SSH 部署（Nginx 蓝绿切换）
        shell: bash
        run: |
          set -euo pipefail

          SSH_PORT="${SSH_PORT:-22}"
          NGINX_UPSTREAM_NAME="${NGINX_UPSTREAM_NAME:-mindfulness_backend}"
          NGINX_UPSTREAM_FILE="${NGINX_UPSTREAM_FILE:-/etc/nginx/conf.d/api.damer.fun.conf}"
          BLUE_PORT="${BLUE_PORT:-8001}"
          GREEN_PORT="${GREEN_PORT:-8002}"
          CONTAINER_PORT="${CONTAINER_PORT:-8000}"
          HEALTHCHECK_PATH="${HEALTHCHECK_PATH:-/health}"
          SCHEDULER_HEALTHCHECK_PATH="${SCHEDULER_HEALTHCHECK_PATH:-/v1/push/scheduler/health}"

          echo "准备部署：${DOCKER_IMAGE}:${DEPLOY_TAG} -> ${DEPLOY_ENV} (${SSH_USER}@${SSH_HOST}:${SSH_PORT})"

          ssh -p "${SSH_PORT}" -i ~/.ssh/id_rsa -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes "${SSH_USER}@${SSH_HOST}" bash -s -- \
            "${DOCKER_IMAGE}" "${DEPLOY_TAG}" "${NGINX_UPSTREAM_FILE}" "${NGINX_UPSTREAM_NAME}" "${BLUE_PORT}" "${GREEN_PORT}" "${CONTAINER_PORT}" "${HEALTHCHECK_PATH}" "${SCHEDULER_HEALTHCHECK_PATH}" "${REMOTE_ENV_FILE:-}" <<'REMOTE'
          set -euo pipefail

          IMAGE="$1"
          TAG="$2"
          UPSTREAM_FILE="$3"
          UPSTREAM_NAME="$4"
          BLUE_PORT="$5"
          GREEN_PORT="$6"
          CONTAINER_PORT="$7"
          HEALTHCHECK_PATH="$8"
          SCHEDULER_HEALTHCHECK_PATH="$9"
          REMOTE_ENV_FILE="${10}"

          APP_DIR="/opt/mindfulness-server"
          ACTIVE_FILE="${APP_DIR}/active_color"
          mkdir -p "${APP_DIR}"

          # 判断 sudo（如果非 root 且存在 sudo，则使用 sudo）
          SUDO=""
          if [[ "$(id -u)" -ne 0 ]] && command -v sudo >/dev/null 2>&1; then
            SUDO="sudo"
          fi

          ACTIVE_COLOR="blue"
          if [[ -f "${ACTIVE_FILE}" ]]; then
            ACTIVE_COLOR="$(cat "${ACTIVE_FILE}" || echo blue)"
          fi

          if [[ "${ACTIVE_COLOR}" == "blue" ]]; then
            NEW_COLOR="green"
            NEW_PORT="${GREEN_PORT}"
            OLD_COLOR="blue"
            OLD_PORT="${BLUE_PORT}"
          else
            NEW_COLOR="blue"
            NEW_PORT="${BLUE_PORT}"
            OLD_COLOR="green"
            OLD_PORT="${GREEN_PORT}"
          fi

          echo "当前在线：${ACTIVE_COLOR}（${OLD_PORT}），准备发布：${NEW_COLOR}（${NEW_PORT}）"

          # 拉取镜像
          ${SUDO} docker pull "${IMAGE}:${TAG}"

          # 启动新颜色容器（API + Worker + Beat）
          API_NAME="mindfulness-server-api-${NEW_COLOR}"
          WORKER_NAME="mindfulness-server-worker-${NEW_COLOR}"
          BEAT_NAME="mindfulness-server-beat-${NEW_COLOR}"

          # 兼容旧命名：之前可能只有一个 mindfulness-server-blue/green
          ${SUDO} docker rm -f "mindfulness-server-${NEW_COLOR}" >/dev/null 2>&1 || true

          ${SUDO} docker rm -f "${API_NAME}" >/dev/null 2>&1 || true
          ${SUDO} docker rm -f "${WORKER_NAME}" >/dev/null 2>&1 || true
          ${SUDO} docker rm -f "${BEAT_NAME}" >/dev/null 2>&1 || true

          ENV_FILE_ARGS=()
          if [[ -n "${REMOTE_ENV_FILE}" && -f "${REMOTE_ENV_FILE}" ]]; then
            ENV_FILE_ARGS=(--env-file "${REMOTE_ENV_FILE}")
            echo "将使用远端 env 文件：${REMOTE_ENV_FILE}"
          elif [[ -n "${REMOTE_ENV_FILE}" ]]; then
            echo "提示：REMOTE_ENV_FILE 已配置但文件不存在：${REMOTE_ENV_FILE}（将忽略 env-file）"
          fi

          # API（对外暴露端口，仅该容器参与蓝绿切流）
          ${SUDO} docker run -d \
            --name "${API_NAME}" \
            --restart=always \
            -p "${NEW_PORT}:${CONTAINER_PORT}" \
            "${ENV_FILE_ARGS[@]}" \
            -e START_API=1 -e START_WORKER=0 -e START_BEAT=0 \
            "${IMAGE}:${TAG}"

          # Worker（处理异步/ETA 任务，不暴露端口）
          ${SUDO} docker run -d \
            --name "${WORKER_NAME}" \
            --restart=always \
            "${ENV_FILE_ARGS[@]}" \
            -e START_API=0 -e START_WORKER=1 -e START_BEAT=0 \
            "${IMAGE}:${TAG}"

          # Beat（定时调度，不暴露端口）
          ${SUDO} docker run -d \
            --name "${BEAT_NAME}" \
            --restart=always \
            "${ENV_FILE_ARGS[@]}" \
            -e START_API=0 -e START_WORKER=0 -e START_BEAT=1 \
            "${IMAGE}:${TAG}"

          # 健康检查
          if [[ "${HEALTHCHECK_PATH}" != /* ]]; then
            HEALTHCHECK_PATH="/${HEALTHCHECK_PATH}"
          fi
          HEALTH_URL="http://127.0.0.1:${NEW_PORT}${HEALTHCHECK_PATH}"
          echo "健康检查：${HEALTH_URL}"

          for i in $(seq 1 30); do
            if command -v curl >/dev/null 2>&1; then
              if curl -fsS "${HEALTH_URL}" >/dev/null; then
                echo "健康检查通过"
                break
              fi
            elif command -v wget >/dev/null 2>&1; then
              if wget -q -O /dev/null "${HEALTH_URL}"; then
                echo "健康检查通过"
                break
              fi
            else
              echo "远端缺少 curl/wget，跳过 HTTP 健康检查"
              break
            fi

            if [[ "$i" -eq 30 ]]; then
              echo "健康检查失败：新版本未就绪，回滚并退出"
              ${SUDO} docker logs --tail 200 "${API_NAME}" || true
              ${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
              exit 1
            fi
            sleep 2
          done

          # 定时服务健康检查（确保 Redis/Worker/Beat 都 OK，避免“接口正常但定时任务没跑”）
          if [[ "${SCHEDULER_HEALTHCHECK_PATH}" != /* ]]; then
            SCHEDULER_HEALTHCHECK_PATH="/${SCHEDULER_HEALTHCHECK_PATH}"
          fi
          SCHED_URL="http://127.0.0.1:${NEW_PORT}${SCHEDULER_HEALTHCHECK_PATH}"
          echo "定时服务健康检查：${SCHED_URL}"

          # Beat 心跳是按分钟刷新，这里最多等 90 秒（45*2s）
          for i in $(seq 1 45); do
            RES="$(curl -fsS "${SCHED_URL}" 2>/dev/null || true)"
            if [[ -n "${RES}" ]] \
              && echo "${RES}" | grep -q '"redis":{"ok":true' \
              && echo "${RES}" | grep -q '"worker":{"ok":true' \
              && echo "${RES}" | grep -q '"beat":{"ok":true' ; then
              echo "定时服务健康检查通过：${RES}"
              break
            fi

            if [[ "$i" -eq 45 ]]; then
              echo "定时服务健康检查失败：${RES}"
              echo "Worker/Beat 日志（各 120 行）："
              ${SUDO} docker logs --tail 120 "${WORKER_NAME}" || true
              ${SUDO} docker logs --tail 120 "${BEAT_NAME}" || true
              ${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
              exit 1
            fi
            sleep 2
          done

          # 切换 Nginx upstream（在同一个 conf 文件中通过 backup 做主备切换）
          if [[ ! -f "${UPSTREAM_FILE}" ]]; then
            echo "未找到 Nginx upstream 配置文件：${UPSTREAM_FILE}"
            ${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
            exit 1
          fi

          if ! ${SUDO} grep -qE "upstream[[:space:]]+${UPSTREAM_NAME}[[:space:]]*\\{" "${UPSTREAM_FILE}"; then
            echo "在 ${UPSTREAM_FILE} 中未找到 upstream：${UPSTREAM_NAME}"
            ${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
            exit 1
          fi

          if ! ${SUDO} grep -qE "server[[:space:]]+127\\.0\\.0\\.1:${BLUE_PORT}" "${UPSTREAM_FILE}"; then
            echo "在 ${UPSTREAM_FILE} 中未找到 server 127.0.0.1:${BLUE_PORT}（请先按参考配置写入 upstream）"
            ${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
            exit 1
          fi
          if ! ${SUDO} grep -qE "server[[:space:]]+127\\.0\\.0\\.1:${GREEN_PORT}" "${UPSTREAM_FILE}"; then
            echo "在 ${UPSTREAM_FILE} 中未找到 server 127.0.0.1:${GREEN_PORT}（请先按参考配置写入 upstream）"
            ${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
            exit 1
          fi

          # 备份（回滚用）
          BACKUP_FILE="${UPSTREAM_FILE}.bak.$(date +%s)"
          ${SUDO} cp -f "${UPSTREAM_FILE}" "${BACKUP_FILE}"

          echo "切换 upstream：${UPSTREAM_NAME}（${OLD_PORT} -> ${NEW_PORT}）通过 backup 切换"

          TMP_FILE="$(mktemp)"
          ${SUDO} awk -v name="${UPSTREAM_NAME}" -v old="${OLD_PORT}" -v new="${NEW_PORT}" '
            BEGIN { in_up = 0 }
            $0 ~ ("^[[:space:]]*upstream[[:space:]]+" name "[[:space:]]*\\{[[:space:]]*$") { in_up = 1 }
            in_up == 1 {
              # 新端口：主（去掉 backup）
              if ($0 ~ ("server[[:space:]]+127\\.0\\.0\\.1:" new)) {
                gsub(/[[:space:]]+backup[[:space:]]*;/, ";")
              }
              # 旧端口：备（确保有 backup;）
              if ($0 ~ ("server[[:space:]]+127\\.0\\.0\\.1:" old)) {
                gsub(/[[:space:]]+backup[[:space:]]*;/, ";")
                sub(/;[[:space:]]*$/, " backup;")
              }
            }
            in_up == 1 && $0 ~ /^[[:space:]]*\}[[:space:]]*$/ { in_up = 0 }
            { print }
          ' "${UPSTREAM_FILE}" > "${TMP_FILE}"

          ${SUDO} cp -f "${TMP_FILE}" "${UPSTREAM_FILE}"
          rm -f "${TMP_FILE}"

          # 校验并 reload nginx（失败则回滚并退出）
          if ${SUDO} nginx -t; then
            ${SUDO} nginx -s reload
          else
            echo "Nginx 配置校验失败，回滚 upstream 配置并退出"
            ${SUDO} cp -f "${BACKUP_FILE}" "${UPSTREAM_FILE}" || true
            ${SUDO} nginx -t && ${SUDO} nginx -s reload || true
            ${SUDO} docker rm -f "${API_NAME}" "${WORKER_NAME}" "${BEAT_NAME}" >/dev/null 2>&1 || true
            exit 1
          fi

          # 记录当前在线颜色
          echo "${NEW_COLOR}" | ${SUDO} tee "${ACTIVE_FILE}" >/dev/null

          # 下线旧容器（切流后再停旧的）
          OLD_API_NAME="mindfulness-server-api-${OLD_COLOR}"
          OLD_WORKER_NAME="mindfulness-server-worker-${OLD_COLOR}"
          OLD_BEAT_NAME="mindfulness-server-beat-${OLD_COLOR}"
          ${SUDO} docker rm -f "${OLD_API_NAME}" "${OLD_WORKER_NAME}" "${OLD_BEAT_NAME}" >/dev/null 2>&1 || true
          # 兼容旧命名
          ${SUDO} docker rm -f "mindfulness-server-${OLD_COLOR}" >/dev/null 2>&1 || true

          echo "部署完成：${NEW_COLOR} 已上线"
          REMOTE

