name: domain-server-rollback

# ════════════════════════════════════════════════════════════════
# 国内域名机 · 一键回滚 (单独红按钮)
# Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559
# 守护: 铸渊 · ICE-GL-ZY001
#
# 跟 deploy-domain-server.yml 区分: 这一份给 Awen 一个**显眼的红按钮**,
# 不会跟"重装"在同一界面被误点. 误触锁同样要求输入「重装广州」.
#
# 用法: Actions → domain-server-rollback → Run workflow
#   mode:           list-only (默认, 只列快照) / rollback
#   snapshot_ts:    留空 = 回到最近一个快照 / 填具体 YYYYMMDD-HHMMSS
#   confirm_phrase: rollback 模式必填「重装广州」, 否则降级 list-only
#
# 必需 Secrets (与 deploy-domain-server.yml 相同):
#   ZY_CN_SERVER_HOST / ZY_CN_SERVER_USER / ZY_CN_SERVER_KEY
#   ZY_CN_SERVER_PATH / ZY_CN_SSH_PORT
# ════════════════════════════════════════════════════════════════

on:
  workflow_dispatch:
    inputs:
      mode:
        description: '操作模式'
        required: true
        default: 'list-only'
        type: choice
        options:
          - list-only
          - rollback
      snapshot_ts:
        description: '快照时间戳 (留空 = 回到最近一个; 用 list-only 模式查可用快照)'
        required: false
        default: ''
        type: string
      confirm_phrase:
        description: 'rollback 模式必填 · 输入「重装广州」才会真跑'
        required: false
        default: ''
        type: string

permissions:
  contents: read

concurrency:
  group: domain-server-rollback
  cancel-in-progress: false

jobs:
  preflight:
    name: 启动前预检
    runs-on: ubuntu-latest
    timeout-minutes: 5
    outputs:
      effective_mode: ${{ steps.gate.outputs.effective_mode }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }

      - name: 密钥预检
        env:
          ZY_CN_SERVER_HOST: ${{ secrets.ZY_CN_SERVER_HOST }}
          ZY_CN_SERVER_USER: ${{ secrets.ZY_CN_SERVER_USER }}
          ZY_CN_SERVER_KEY:  ${{ secrets.ZY_CN_SERVER_KEY }}
          ZY_CN_SERVER_PATH: ${{ secrets.ZY_CN_SERVER_PATH }}
          ZY_CN_SSH_PORT:    ${{ secrets.ZY_CN_SSH_PORT }}
        run: |
          node scripts/preflight/check-secrets.js \
            --workflow cn-domain-deploy \
            --stage rollback \
            --target main

      - name: 误触锁
        id: gate
        env:
          MODE: ${{ inputs.mode }}
          PHRASE: ${{ inputs.confirm_phrase }}
        run: |
          set -e
          EFFECTIVE="$MODE"
          if [ "$MODE" = "rollback" ] && [ "$PHRASE" != "重装广州" ]; then
            echo "═══════════════════════════════════════════════"
            echo "  ⚠️  误触保护: 选了 rollback 但 confirm_phrase != 「重装广州」"
            echo "       自动降级 list-only (只列快照, 不真回滚)."
            echo "       要真跑回滚, 在 confirm_phrase 字段精确输入: 重装广州"
            echo "═══════════════════════════════════════════════"
            EFFECTIVE="list-only"
          fi
          echo "effective_mode=$EFFECTIVE" >> "$GITHUB_OUTPUT"

  rollback:
    name: domain-cn · ${{ needs.preflight.outputs.effective_mode }}
    needs: preflight
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: 装载 SSH 凭据
        id: target
        env:
          HOST: ${{ secrets.ZY_CN_SERVER_HOST }}
          USER: ${{ secrets.ZY_CN_SERVER_USER }}
          KEY:  ${{ secrets.ZY_CN_SERVER_KEY }}
          PORT: ${{ secrets.ZY_CN_SSH_PORT }}
          DEPLOY_PATH: ${{ secrets.ZY_CN_SERVER_PATH }}
        run: |
          set -euo pipefail
          if [ -z "$HOST" ] || [ -z "$KEY" ] || [ -z "$USER" ]; then
            echo "❌ ZY_CN_SERVER_{HOST,USER,KEY} 任一为空" >&2; exit 1
          fi
          PORT="${PORT:-22}"
          DEPLOY_PATH="${DEPLOY_PATH:-/data/guanghulab}"
          echo "user=$USER" >> "$GITHUB_OUTPUT"
          echo "port=$PORT" >> "$GITHUB_OUTPUT"
          echo "deploy_path=$DEPLOY_PATH" >> "$GITHUB_OUTPUT"
          umask 077; mkdir -p ~/.ssh
          printf '%s\n' "$KEY" > ~/.ssh/domain_key
          chmod 600 ~/.ssh/domain_key
          ssh-keyscan -p "$PORT" -H "$HOST" >> ~/.ssh/known_hosts 2>/dev/null || true
          printf '%s' "$HOST" > ~/.ssh/domain_host
          chmod 600 ~/.ssh/domain_host

      - name: 列出可用快照
        env:
          USER: ${{ steps.target.outputs.user }}
          PORT: ${{ steps.target.outputs.port }}
          DEPLOY_PATH: ${{ steps.target.outputs.deploy_path }}
        run: |
          set -e
          HOST="$(cat ~/.ssh/domain_host)"
          ssh -i ~/.ssh/domain_key -p "$PORT" \
              -o StrictHostKeyChecking=accept-new \
              "$USER@$HOST" \
              "sudo env DATA_ROOT='$DEPLOY_PATH' bash /opt/guanghulab/domain-cn/rollback.sh --list" \
            2>&1 | tee snapshot-list.log

      - name: 校验 snapshot_ts 格式 (防注入)
        if: needs.preflight.outputs.effective_mode == 'rollback'
        env:
          TS: ${{ inputs.snapshot_ts }}
        run: |
          set -e
          # snapshot_ts 走用户输入, 后面进 ssh 命令字符串. 严格白名单.
          # 合法形式 (来自 bootstrap.sh `$PRE_TS-pre-$STAGE`, 见 bootstrap.sh L161):
          #   YYYYMMDD-HHMMSS-pre-bootstrap
          #   YYYYMMDD-HHMMSS-pre-update
          #   YYYYMMDD-HHMMSS-pre-rollback
          #   空 = 最近一个
          # 注: bootstrap 总是带 -pre-{stage} 后缀, 但兼容裸时间戳 (理论上不会出现)
          # 也不放过, 避免万一手动建快照只用了时间戳的情况误闯.
          if [ -n "$TS" ] && ! printf '%s' "$TS" | grep -Eq '^[0-9]{8}-[0-9]{6}-pre-(bootstrap|update|rollback)$'; then
            echo "❌ snapshot_ts 格式不合法: 「$TS」" >&2
            echo "   合法形式示例: 20260509-163700-pre-bootstrap" >&2
            echo "   或留空 (= 回到最近一次快照)" >&2
            echo "   提示: 用 list-only 模式查实际可用快照名" >&2
            exit 1
          fi
          echo "✅ snapshot_ts 格式校验通过${TS:+: $TS}"

      - name: 执行回滚
        if: needs.preflight.outputs.effective_mode == 'rollback'
        env:
          USER: ${{ steps.target.outputs.user }}
          PORT: ${{ steps.target.outputs.port }}
          DEPLOY_PATH: ${{ steps.target.outputs.deploy_path }}
          TS: ${{ inputs.snapshot_ts }}
        run: |
          set -e
          HOST="$(cat ~/.ssh/domain_host)"
          # 双层防御: TS 已在上一步白名单校验, 这里走 sh -c argv 传入而非
          # 命令字符串拼接, 即使 TS 含特殊字符也不会被本地/远程 shell 二次解释.
          if [ -n "$TS" ]; then
            ssh -i ~/.ssh/domain_key -p "$PORT" \
                -o StrictHostKeyChecking=accept-new \
                "$USER@$HOST" \
                "sudo env DATA_ROOT='$DEPLOY_PATH' sh -c 'bash /opt/guanghulab/domain-cn/rollback.sh --to \"\$1\"' _ \"$TS\"" \
              2>&1 | tee rollback-output.log
          else
            ssh -i ~/.ssh/domain_key -p "$PORT" \
                -o StrictHostKeyChecking=accept-new \
                "$USER@$HOST" \
                "sudo env DATA_ROOT='$DEPLOY_PATH' bash /opt/guanghulab/domain-cn/rollback.sh" \
              2>&1 | tee rollback-output.log
          fi

      - name: 拉回中文回执
        if: always()
        env:
          USER: ${{ steps.target.outputs.user }}
          PORT: ${{ steps.target.outputs.port }}
        run: |
          set -uo pipefail
          HOST="$(cat ~/.ssh/domain_host)"
          mkdir -p artifacts
          scp -i ~/.ssh/domain_key -P "$PORT" \
              -o StrictHostKeyChecking=accept-new \
              "$USER@$HOST:/opt/guanghulab/_logs/rollback-*.json" \
              artifacts/ 2>/dev/null || echo "(no rollback receipt — 可能 list-only 模式 / 无快照)"
          scp -i ~/.ssh/domain_key -P "$PORT" \
              -o StrictHostKeyChecking=accept-new \
              "$USER@$HOST:/opt/guanghulab/_logs/deploy-report.md" \
              artifacts/deploy-report.md 2>/dev/null || true
          ls -la artifacts/ || true

      - name: 把回执贴到 GitHub Actions Summary
        if: always()
        run: |
          set -uo pipefail
          {
            echo "# 国内域名机回滚"
            echo ""
            echo "模式: \`${{ needs.preflight.outputs.effective_mode }}\`"
            echo ""
            echo "## 可用快照"
            echo ""
            echo '```'
            cat snapshot-list.log 2>/dev/null || echo "(no snapshot list)"
            echo '```'
            if [ -f rollback-output.log ]; then
              echo ""
              echo "## 回滚执行日志"
              echo ""
              echo '```'
              tail -40 rollback-output.log
              echo '```'
            fi
            if [ -f artifacts/deploy-report.md ]; then
              echo ""
              echo "## 中文回执 (deploy-report.md)"
              echo ""
              cat artifacts/deploy-report.md
            fi
          } >> "$GITHUB_STEP_SUMMARY"

      - name: 上传 artifact
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: domain-rollback-${{ github.run_id }}
          path: |
            artifacts/
            snapshot-list.log
            rollback-output.log
          retention-days: 30
          if-no-files-found: warn

      - name: 清理 SSH 私钥
        if: always()
        run: rm -f ~/.ssh/domain_key ~/.ssh/domain_host
