name: lighthouse-cn-deploy

# ════════════════════════════════════════════════════════════════
# 光湖灯塔 · 国内主服务器手动部署
# Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559
# 守护: 铸渊 · ICE-GL-ZY001
#
# 仅手动触发. 在 Actions 页面 Run workflow:
#   server_target: main / backup
#   stage:         bootstrap / update / rollback
#
# 必需 Secrets (主服务器):
#   ZY_LIGHTHOUSE_HOST / ZY_LIGHTHOUSE_USER / ZY_LIGHTHOUSE_KEY / ZY_LIGHTHOUSE_PORT
#   ZY_LIGHTHOUSE_DOMAIN
# 可选 (备用机):
#   ZY_LIGHTHOUSE_BACKUP_HOST / ZY_LIGHTHOUSE_BACKUP_USER / ZY_LIGHTHOUSE_BACKUP_KEY
# Bootstrap 阶段需要:
#   ZY_GITEA_ADMIN_USER / ZY_GITEA_ADMIN_PASS / ZY_GITEA_ADMIN_EMAIL / ZY_GITEA_DB_PASS
# Update 阶段 (注册 runner) 还需要:
#   ZY_GITEA_RUNNER_TOKEN
# ════════════════════════════════════════════════════════════════

on:
  workflow_dispatch:
    inputs:
      server_target:
        description: '部署目标'
        required: true
        default: 'main'
        type: choice
        options:
          - main
          - backup
      stage:
        description: '部署阶段'
        required: true
        default: 'bootstrap'
        type: choice
        options:
          - bootstrap
          - update
          - rollback
      confirm_phrase:
        description: 'bootstrap 必填『我确认部署』 / rollback 必填『我确认回滚』(也可填『我确认部署』向下兼容); 空 = 自动 dry-run'
        required: false
        default: ''
        type: string
      dry_run:
        description: '只打印计划, 不真的执行 (true/false)'
        required: false
        default: 'false'
        type: choice
        options:
          - 'false'
          - 'true'

permissions:
  contents: read

concurrency:
  group: lighthouse-cn-${{ inputs.server_target }}
  cancel-in-progress: false

jobs:
  preflight:
    name: 启动前预检 · 密钥 + 误触锁
    runs-on: ubuntu-latest
    timeout-minutes: 5
    outputs:
      effective_dry_run: ${{ steps.misclick.outputs.effective_dry_run }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: 启动前最小密钥校验 (中文报告)
        env:
          ZY_LIGHTHOUSE_HOST: ${{ secrets.ZY_LIGHTHOUSE_HOST }}
          ZY_LIGHTHOUSE_USER: ${{ secrets.ZY_LIGHTHOUSE_USER }}
          ZY_LIGHTHOUSE_KEY: ${{ secrets.ZY_LIGHTHOUSE_KEY }}
          ZY_LIGHTHOUSE_PORT: ${{ secrets.ZY_LIGHTHOUSE_PORT }}
          ZY_LIGHTHOUSE_DOMAIN: ${{ secrets.ZY_LIGHTHOUSE_DOMAIN }}
          ZY_GITEA_ADMIN_USER: ${{ secrets.ZY_GITEA_ADMIN_USER }}
          ZY_GITEA_ADMIN_PASS: ${{ secrets.ZY_GITEA_ADMIN_PASS }}
          ZY_GITEA_ADMIN_EMAIL: ${{ secrets.ZY_GITEA_ADMIN_EMAIL }}
          ZY_GITEA_DB_PASS: ${{ secrets.ZY_GITEA_DB_PASS }}
          ZY_GITEA_RUNNER_TOKEN: ${{ secrets.ZY_GITEA_RUNNER_TOKEN }}
          ZY_LIGHTHOUSE_BACKUP_HOST: ${{ secrets.ZY_LIGHTHOUSE_BACKUP_HOST }}
          ZY_LIGHTHOUSE_BACKUP_USER: ${{ secrets.ZY_LIGHTHOUSE_BACKUP_USER }}
          ZY_LIGHTHOUSE_BACKUP_KEY: ${{ secrets.ZY_LIGHTHOUSE_BACKUP_KEY }}
          ZY_COS_SECRET_ID: ${{ secrets.ZY_COS_SECRET_ID }}
          ZY_COS_SECRET_KEY: ${{ secrets.ZY_COS_SECRET_KEY }}
          ZY_COS_BUCKET: ${{ secrets.ZY_COS_BUCKET }}
          ZY_COS_REGION: ${{ secrets.ZY_COS_REGION }}
          ZY_DISPATCH_TOKEN: ${{ secrets.ZY_DISPATCH_TOKEN }}
          ZY_FEISHU_WEBHOOK: ${{ secrets.ZY_FEISHU_WEBHOOK }}
        run: |
          node scripts/preflight/check-secrets.js \
            --workflow lighthouse-cn-deploy \
            --stage "${{ inputs.stage }}" \
            --target "${{ inputs.server_target }}"

      - name: 误触锁 · bootstrap/rollback 必须显式确认
        id: misclick
        env:
          STAGE: ${{ inputs.stage }}
          PHRASE: ${{ inputs.confirm_phrase }}
          DRY: ${{ inputs.dry_run }}
        run: |
          set -e
          EFFECTIVE_DRY="$DRY"
          # 口令兼容: bootstrap 收『我确认部署』; rollback 同时接受
          # 『我确认部署』(向下兼容旧用户记忆) 和『我确认回滚』
          # (与独立的 lighthouse-cn-rollback workflow 对齐).
          REQUIRED=""
          OK="false"
          if [ "$STAGE" = "bootstrap" ]; then
            REQUIRED="我确认部署"
            [ "$PHRASE" = "我确认部署" ] && OK="true"
          elif [ "$STAGE" = "rollback" ]; then
            REQUIRED="我确认回滚 (或我确认部署)"
            { [ "$PHRASE" = "我确认回滚" ] || [ "$PHRASE" = "我确认部署" ]; } && OK="true"
          else
            OK="skip"
          fi
          if [ "$OK" = "false" ]; then
            echo "═══════════════════════════════════════════════"
            echo "  ⚠️  误触保护: stage=$STAGE 必须在 confirm_phrase"
            echo "       字段输入『$REQUIRED』才会真跑."
            echo "       当前值=「$PHRASE」 → 自动切到 dry-run 模式."
            echo "       (这一层是为了防止 Awen / 冰朔不小心点错按钮)"
            echo "═══════════════════════════════════════════════"
            EFFECTIVE_DRY="true"
          elif [ "$OK" = "true" ]; then
            echo "✅ 已显式确认 (stage=$STAGE, phrase=「$PHRASE」)"
          fi
          echo "effective_dry_run=$EFFECTIVE_DRY" >> "$GITHUB_OUTPUT"

  deploy:
    name: ${{ inputs.server_target }} · ${{ inputs.stage }}
    needs: preflight
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Resolve target server
        id: target
        env:
          MAIN_HOST: ${{ secrets.ZY_LIGHTHOUSE_HOST }}
          MAIN_USER: ${{ secrets.ZY_LIGHTHOUSE_USER }}
          MAIN_KEY: ${{ secrets.ZY_LIGHTHOUSE_KEY }}
          MAIN_PORT: ${{ secrets.ZY_LIGHTHOUSE_PORT }}
          BACKUP_HOST: ${{ secrets.ZY_LIGHTHOUSE_BACKUP_HOST }}
          BACKUP_USER: ${{ secrets.ZY_LIGHTHOUSE_BACKUP_USER }}
          BACKUP_KEY: ${{ secrets.ZY_LIGHTHOUSE_BACKUP_KEY }}
          TARGET: ${{ inputs.server_target }}
        run: |
          set -euo pipefail
          if [ "$TARGET" = "main" ]; then
            HOST="$MAIN_HOST"
            USER="$MAIN_USER"
            KEY="$MAIN_KEY"
            PORT="${MAIN_PORT:-22}"
            SERVER_ID="GH-CVM-MAIN-PROD-01"
          else
            HOST="$BACKUP_HOST"
            USER="$BACKUP_USER"
            KEY="$BACKUP_KEY"
            PORT="22"
            SERVER_ID="GH-CVM-BACKUP-01"
          fi
          if [ -z "$HOST" ]; then
            echo "❌ 目标服务器 ($TARGET) 的 HOST secret 未配置, 终止" >&2
            exit 1
          fi
          if [ -z "$KEY" ]; then
            echo "❌ 目标服务器 ($TARGET) 的 KEY secret 未配置, 终止" >&2
            exit 1
          fi
          # 不输出 HOST 到 stdout 以避免被截图泄露
          echo "host=$HOST"      >> "$GITHUB_OUTPUT"
          echo "user=$USER"      >> "$GITHUB_OUTPUT"
          echo "port=$PORT"      >> "$GITHUB_OUTPUT"
          echo "server_id=$SERVER_ID" >> "$GITHUB_OUTPUT"
          # KEY 单独写入文件
          umask 077
          mkdir -p ~/.ssh
          printf '%s\n' "$KEY" > ~/.ssh/lighthouse_key
          chmod 600 ~/.ssh/lighthouse_key
          ssh-keyscan -p "$PORT" -H "$HOST" >> ~/.ssh/known_hosts 2>/dev/null || true
          echo "✅ SSH 凭据就绪 ($SERVER_ID)"

      - name: Sanity check (dry-run)
        if: needs.preflight.outputs.effective_dry_run == 'true'
        env:
          HOST: ${{ steps.target.outputs.host }}
          USER: ${{ steps.target.outputs.user }}
          PORT: ${{ steps.target.outputs.port }}
        run: |
          echo "=== DRY RUN ==="
          echo "would rsync server/setup/lighthouse-cn/ → $USER@<host>:/opt/guanghu/lighthouse-cn/"
          echo "would ssh $USER@<host> bash /opt/guanghu/lighthouse-cn/bootstrap.sh STAGE=${{ inputs.stage }}"
          echo "(real host masked)"

      - name: Rsync template to server
        if: needs.preflight.outputs.effective_dry_run != 'true'
        env:
          HOST: ${{ steps.target.outputs.host }}
          USER: ${{ steps.target.outputs.user }}
          PORT: ${{ steps.target.outputs.port }}
        run: |
          set -euo pipefail
          ssh -i ~/.ssh/lighthouse_key -p "$PORT" \
              -o StrictHostKeyChecking=accept-new \
              "$USER@$HOST" \
              "mkdir -p /opt/guanghu/lighthouse-cn"
          rsync -avz --delete \
            -e "ssh -i ~/.ssh/lighthouse_key -p $PORT -o StrictHostKeyChecking=accept-new" \
            server/setup/lighthouse-cn/ \
            "$USER@$HOST:/opt/guanghu/lighthouse-cn/"
          # 顺带把 restore 脚本 + workflow 转换器也推上去, 后续阶段要用
          rsync -avz \
            -e "ssh -i ~/.ssh/lighthouse_key -p $PORT -o StrictHostKeyChecking=accept-new" \
            scripts/migration/ \
            "$USER@$HOST:/opt/guanghu/lighthouse-cn/migration/"

      - name: Run bootstrap / update / rollback
        if: needs.preflight.outputs.effective_dry_run != 'true'
        env:
          HOST: ${{ steps.target.outputs.host }}
          USER: ${{ steps.target.outputs.user }}
          PORT: ${{ steps.target.outputs.port }}
          SERVER_ID: ${{ steps.target.outputs.server_id }}
          STAGE: ${{ inputs.stage }}
          GITEA_ADMIN_USER: ${{ secrets.ZY_GITEA_ADMIN_USER }}
          GITEA_ADMIN_PASS: ${{ secrets.ZY_GITEA_ADMIN_PASS }}
          GITEA_ADMIN_EMAIL: ${{ secrets.ZY_GITEA_ADMIN_EMAIL }}
          GITEA_DB_PASS: ${{ secrets.ZY_GITEA_DB_PASS }}
          GITEA_RUNNER_TOKEN: ${{ secrets.ZY_GITEA_RUNNER_TOKEN }}
        run: |
          set -euo pipefail
          # 把 secrets 写到一个临时 envfile, scp 上去 source, 不进进程参数
          ENVFILE="$(mktemp)"
          chmod 600 "$ENVFILE"
          {
            printf 'export CN_LIGHTHOUSE_SERVER_ID=%q\n' "$SERVER_ID"
            printf 'export STAGE=%q\n'              "$STAGE"
            printf 'export GITEA_ADMIN_USER=%q\n'   "$GITEA_ADMIN_USER"
            printf 'export GITEA_ADMIN_PASS=%q\n'   "$GITEA_ADMIN_PASS"
            printf 'export GITEA_ADMIN_EMAIL=%q\n'  "$GITEA_ADMIN_EMAIL"
            printf 'export GITEA_DB_PASS=%q\n'      "$GITEA_DB_PASS"
            printf 'export GITEA_RUNNER_TOKEN=%q\n' "$GITEA_RUNNER_TOKEN"
          } > "$ENVFILE"

          REMOTE_ENV="/tmp/lighthouse-bootstrap.env.$$"
          scp -i ~/.ssh/lighthouse_key -P "$PORT" \
              -o StrictHostKeyChecking=accept-new \
              "$ENVFILE" "$USER@$HOST:$REMOTE_ENV"
          rm -f "$ENVFILE"

          # 远程执行: source envfile → 跑 bootstrap → 立刻删 envfile
          # GitHub Actions 自动屏蔽 secrets 在 stdout/stderr 中的明文出现 (***)
          ssh -i ~/.ssh/lighthouse_key -p "$PORT" \
              -o StrictHostKeyChecking=accept-new \
              "$USER@$HOST" \
              "set -e; chmod 600 '$REMOTE_ENV'; \
               . '$REMOTE_ENV'; \
               rm -f '$REMOTE_ENV'; \
               set +x; \
               bash /opt/guanghu/lighthouse-cn/bootstrap.sh" \
              2>&1 | tee bootstrap-output.log

      - name: Pull back latest receipt
        if: needs.preflight.outputs.effective_dry_run != 'true' && always()
        env:
          HOST: ${{ steps.target.outputs.host }}
          USER: ${{ steps.target.outputs.user }}
          PORT: ${{ steps.target.outputs.port }}
        run: |
          set -uo pipefail
          mkdir -p artifacts
          scp -i ~/.ssh/lighthouse_key -P "$PORT" \
              -o StrictHostKeyChecking=accept-new \
              "$USER@$HOST:/opt/guanghu/_logs/lighthouse-bootstrap-*.json" \
              artifacts/ 2>/dev/null || echo "(no receipt yet)"
          ls -la artifacts/ || true

      - name: Upload receipts
        if: needs.preflight.outputs.effective_dry_run != 'true' && always()
        uses: actions/upload-artifact@v4
        with:
          name: lighthouse-bootstrap-receipt-${{ inputs.server_target }}-${{ inputs.stage }}
          path: |
            artifacts/
            bootstrap-output.log
          retention-days: 30
          if-no-files-found: warn

      - name: Cleanup SSH key
        if: always()
        run: |
          rm -f ~/.ssh/lighthouse_key
