name: chaos / migration drill

# M4 of chaos engineering: 每月开一台按量付费 ECS → provision + deploy
# + smoke test → 销毁。验证 "从零装一台新机器" 整个流程没腐。
# 设计见 brain/issues/005-chaos-engineering-backup-migration.md

on:
  schedule:
    - cron: "0 5 1 * *"   # 每月 1 号 05:00 UTC（= 北京 13:00）
  workflow_dispatch:
    inputs:
      keep_on_failure:
        description: "失败时保留 ECS 让人 debug（默认销毁）"
        required: false
        type: boolean
        default: false
      instance_type:
        description: "按量 ECS 实例规格（默认和 alpha 同 t6 1C1G；验证迁移目标 U1 时填 ecs.u1-c1m2.large）"
        required: false
        type: string
        default: "ecs.t6-c1m1.large"

jobs:
  drill:
    name: 开按量 ECS + provision + deploy + destroy
    runs-on: ubuntu-latest
    timeout-minutes: 30
    env:
      REGION: cn-hangzhou
      ZONE: cn-hangzhou-h
      # 复用 alpha 的 VPC/VSwitch/SG/Image（都是 Ubuntu 22.04 小机型）
      IMAGE_ID: ubuntu_22_04_x64_20G_alibase_20260316.vhd
      VSWITCH_ID: vsw-bp1jtmzb0pmhmpezhh5h4
      SG_ID: sg-bp1b0ke3ei1y5n3k2qs1
      INSTANCE_TYPE: ${{ inputs.instance_type || 'ecs.t6-c1m1.large' }}
    steps:
      - uses: actions/checkout@v4

      - name: 装 aliyun CLI
        run: |
          ARCH=$(uname -m | sed s/x86_64/amd64/ | sed s/aarch64/arm64/)
          cd /tmp
          curl -fsSL "https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-${ARCH}.tgz" -o aliyun-cli.tgz
          tar xzf aliyun-cli.tgz
          sudo mv aliyun /usr/local/bin/
          aliyun --version

      - name: 配 aliyun profile（drill）
        env:
          AK_ID: ${{ secrets.ALIYUN_OSS_AK_ID }}
          AK_SECRET: ${{ secrets.ALIYUN_OSS_AK_SECRET }}
        run: |
          aliyun configure set --profile drill --mode AK \
            --access-key-id "$AK_ID" \
            --access-key-secret "$AK_SECRET" \
            --region "$REGION"

      - name: 生成临时 SSH key + 上传 aliyun keypair
        id: keypair
        run: |
          set -euo pipefail
          TS=$(date +%Y%m%d%H%M%S)
          KEY_NAME="xq-drill-${TS}"
          ssh-keygen -t ed25519 -N "" -f "$HOME/.ssh/drill" -C "$KEY_NAME" -q
          chmod 600 "$HOME/.ssh/drill"
          # 上传 pubkey 到 aliyun
          aliyun --profile drill ecs ImportKeyPair \
            --RegionId "$REGION" \
            --KeyPairName "$KEY_NAME" \
            --PublicKeyBody "$(cat $HOME/.ssh/drill.pub)"
          echo "key_name=$KEY_NAME" >> "$GITHUB_OUTPUT"
          echo "✓ keypair $KEY_NAME uploaded"

      - name: RunInstances 开按量 ECS
        id: create
        run: |
          set -euo pipefail
          KEY_NAME="${{ steps.keypair.outputs.key_name }}"
          RESP=$(aliyun --profile drill ecs RunInstances \
            --RegionId "$REGION" \
            --ZoneId "$ZONE" \
            --ImageId "$IMAGE_ID" \
            --InstanceType "$INSTANCE_TYPE" \
            --SecurityGroupId "$SG_ID" \
            --VSwitchId "$VSWITCH_ID" \
            --InstanceChargeType PostPaid \
            --InternetChargeType PayByTraffic \
            --InternetMaxBandwidthOut 5 \
            --SystemDisk.Size 20 \
            --Amount 1 \
            --KeyPairName "$KEY_NAME" \
            --InstanceName "$KEY_NAME" \
            --Tag.1.Key purpose --Tag.1.Value chaos-migration-drill)
          INSTANCE_ID=$(echo "$RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['InstanceIdSets']['InstanceIdSet'][0])")
          echo "instance_id=$INSTANCE_ID" >> "$GITHUB_OUTPUT"
          echo "✓ 按量 ECS 开了: $INSTANCE_ID"

      - name: 等实例 Running + 拿公网 IP
        id: wait
        run: |
          set -euo pipefail
          INSTANCE_ID="${{ steps.create.outputs.instance_id }}"
          # 轮询上限 60 × 10s = 10 min（和 M6 full drill 一致）。#008 观察：
          # aliyun RunInstances → Running 正常 15-60s，极端情况 2-5 分钟。
          for i in $(seq 1 60); do
            RESP=$(aliyun --profile drill ecs DescribeInstances \
                --RegionId "$REGION" \
                --InstanceIds "[\"$INSTANCE_ID\"]")
            STATUS=$(echo "$RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['Instances']['Instance'][0]['Status'])")
            IP=$(echo "$RESP" | python3 -c "import json,sys; d=json.load(sys.stdin)['Instances']['Instance'][0]; print((d.get('PublicIpAddress') or {}).get('IpAddress',[''])[0] if (d.get('PublicIpAddress') or {}).get('IpAddress') else '')")
            echo "[$i/60] status=$STATUS ip=$IP"
            if [ "$STATUS" = "Running" ] && [ -n "$IP" ]; then
              echo "public_ip=$IP" >> "$GITHUB_OUTPUT"
              echo "✓ ECS Running at $IP"
              break
            fi
            sleep 10
          done
          if [ -z "${IP:-}" ]; then
            echo "::error::ECS 10 min 内未 Running 或没拿到 IP（aliyun 极端抖动，下次 drill 再试）"
            exit 1
          fi

      - name: 等 SSH port 22 可达
        run: |
          set -euo pipefail
          IP="${{ steps.wait.outputs.public_ip }}"
          for i in $(seq 1 30); do
            if nc -zw3 "$IP" 22 2>/dev/null; then
              echo "✓ SSH 22 open at $IP"
              break
            fi
            echo "[$i/30] waiting for SSH..."
            sleep 5
          done

      - name: 配 ~/.ssh/config 让 rsync/ssh 用 drill key
        run: |
          IP="${{ steps.wait.outputs.public_ip }}"
          mkdir -p ~/.ssh
          # ServerAliveInterval: drill 机器 1C1G 跑 uv sync 构建 wheel 期间静默几分钟，
          # 保活包防止 GHA runner ↔ ECS 之间的 idle timeout 断开
          cat >> ~/.ssh/config <<EOF
          Host $IP
              User root
              IdentityFile ~/.ssh/drill
              StrictHostKeyChecking accept-new
              UserKnownHostsFile ~/.ssh/known_hosts
              ServerAliveInterval 30
              ServerAliveCountMax 20
          EOF
          chmod 600 ~/.ssh/config
          # 预 scan host key 避免首次 prompt
          for i in $(seq 1 10); do
            ssh-keyscan "$IP" >> ~/.ssh/known_hosts 2>/dev/null && break || sleep 3
          done

      - name: 跑 provision.sh <drill-IP>
        run: |
          IP="${{ steps.wait.outputs.public_ip }}"
          # provision.sh 支持任意 IP（不是 alpha/epsilon alias 时不会写 systemd override）
          bash scripts/provision.sh "$IP"

      - name: 跑 deploy.sh <drill-IP>
        run: |
          IP="${{ steps.wait.outputs.public_ip }}"
          bash scripts/deploy.sh "$IP"

      - name: smoke test
        run: |
          IP="${{ steps.wait.outputs.public_ip }}"
          curl -sf -m 10 "http://$IP/health"
          echo "✓ drill machine serving: http://$IP/health"

      - name: cleanup —— 销毁 ECS + keypair（always）
        if: always() && (success() || !inputs.keep_on_failure)
        run: |
          set +e
          INSTANCE_ID="${{ steps.create.outputs.instance_id }}"
          KEY_NAME="${{ steps.keypair.outputs.key_name }}"
          if [ -n "$INSTANCE_ID" ]; then
            echo "销毁 ECS $INSTANCE_ID"
            aliyun --profile drill ecs DeleteInstance \
              --RegionId "$REGION" \
              --InstanceId "$INSTANCE_ID" \
              --Force true
          fi
          if [ -n "$KEY_NAME" ]; then
            echo "删除 keypair $KEY_NAME"
            aliyun --profile drill ecs DeleteKeyPairs \
              --RegionId "$REGION" \
              --KeyPairNames "[\"$KEY_NAME\"]"
          fi

      - name: 失败时保留 debug
        if: failure() && inputs.keep_on_failure
        run: |
          INSTANCE_ID="${{ steps.create.outputs.instance_id }}"
          IP="${{ steps.wait.outputs.public_ip }}"
          echo "::warning::ECS $INSTANCE_ID ($IP) 保留未销毁，debug 完手动删：aliyun --profile drill ecs DeleteInstance --InstanceId $INSTANCE_ID --Force true"
