name: Deploy Environment & Migrate Data (Data Sync) # ============================================================================= # IaC ↔ 数据流单向迁移流水线 (Site Recovery) # 1. 动态 Provision (基于 Terraform 创建新环境 site-recovery) # 2. 部署基础服务 (Bootstrap 部署,跑空环境) # 3. 生产环境克隆与单向同步 (通过 site_migration Ansible 角色) # ============================================================================= on: workflow_dispatch: inputs: infra_ref: description: "ai-workspace-infra git ref" required: false default: "main" type: string source_host: description: "源环境 IP 或域名 (用于导出数据)" required: true default: "install.svc.plus" type: string source_domain_base: description: "源环境域名后缀 (例如 svc.plus)" required: true default: "svc.plus" type: string target_domain_base: description: "新环境域名后缀 (用于参数化替换,如 onwalk.net)" required: true default: "onwalk.net" type: string run_provision_and_deploy: description: "是否执行 Provision 和基础环境 Deploy? (如果只迁移数据则取消勾选)" required: false default: true type: boolean terraform_action: description: "apply 创建/更新,destroy 销毁" required: false default: "apply" type: choice options: [apply, destroy] permissions: contents: read id-token: write concurrency: group: deploy-env-migration cancel-in-progress: false env: VAULT_ADDR: https://vault.svc.plus VAULT_ROLE: github-actions-site-recovery VAULT_KV: kv/data/CICD VAULT_KV_OPENCLAW: kv/data/openclaw VPS_ROOT: infra/iac_modules/terraform-hcl-standard/vultr-vps # 指向 site-recovery 的 Terraform 目录 ENV_DIR: infra/iac_modules/terraform-hcl-standard/vultr-vps/envs/site-recovery PLAYBOOKS_DIR: infra/playbooks jobs: # --------------------------------------------------------------------------- # 步骤 1: 基础设施配置与启动 (Provision) # --------------------------------------------------------------------------- provision: name: Provision New Environment (Terraform) if: ${{ github.event.inputs.run_provision_and_deploy == 'true' }} runs-on: ubuntu-latest outputs: hosts: ${{ steps.matrix.outputs.hosts }} count: ${{ steps.matrix.outputs.count }} steps: - name: Load Vault secrets id: vault uses: hashicorp/vault-action@v4 with: url: ${{ env.VAULT_ADDR }} method: jwt role: ${{ env.VAULT_ROLE }} jwtGithubAudience: vault ignoreNotFound: true secrets: | ${{ env.VAULT_KV }} VULTR_API_KEY | VULTR_API_KEY ; ${{ env.VAULT_KV }} TF_STATE_ENDPOINT | TF_STATE_ENDPOINT ; ${{ env.VAULT_KV }} TF_STATE_BUCKET | TF_STATE_BUCKET ; ${{ env.VAULT_KV }} TF_STATE_ACCESS_KEY | TF_STATE_ACCESS_KEY ; ${{ env.VAULT_KV }} TF_STATE_SECRET_KEY | TF_STATE_SECRET_KEY ; ${{ env.VAULT_KV }} TF_STATE_REGION | TF_STATE_REGION - name: Checkout iac_modules uses: actions/checkout@v7 with: repository: ai-workspace-infra/iac_modules ref: ${{ github.event.inputs.infra_ref }} path: infra/iac_modules - uses: hashicorp/setup-terraform@v3 with: terraform_version: "1.9.8" - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install render deps run: pip install --quiet pyyaml jinja2 - name: Configure remote backend working-directory: ${{ env.ENV_DIR }} env: TF_STATE_ENDPOINT: ${{ steps.vault.outputs.TF_STATE_ENDPOINT }} TF_STATE_REGION: ${{ steps.vault.outputs.TF_STATE_REGION }} run: python3 $GITHUB_WORKSPACE/${{ env.VPS_ROOT }}/scripts/render_backend_tf.py backend.tf - name: generate.py render working-directory: ${{ env.VPS_ROOT }} # 强制指定 envs/site-recovery 生成 tfvars 和显式 HCL run: python3 scripts/generate.py render --resources config/resources/site-recovery.yaml --workdir envs/site-recovery - name: Terraform init working-directory: ${{ env.ENV_DIR }} env: AWS_ACCESS_KEY_ID: ${{ steps.vault.outputs.TF_STATE_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ steps.vault.outputs.TF_STATE_SECRET_KEY }} TF_STATE_ENDPOINT: ${{ steps.vault.outputs.TF_STATE_ENDPOINT }} TF_STATE_BUCKET: ${{ steps.vault.outputs.TF_STATE_BUCKET }} TF_STATE_REGION: ${{ steps.vault.outputs.TF_STATE_REGION }} run: | terraform init -input=false \ -backend-config="bucket=${TF_STATE_BUCKET}" \ -backend-config="key=site-recovery/terraform.tfstate" \ -backend-config="region=${TF_STATE_REGION}" - name: Terraform Apply / Destroy working-directory: ${{ env.ENV_DIR }} env: AWS_ACCESS_KEY_ID: ${{ steps.vault.outputs.TF_STATE_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ steps.vault.outputs.TF_STATE_SECRET_KEY }} TF_VAR_vultr_api_key: ${{ steps.vault.outputs.VULTR_API_KEY }} run: terraform ${{ github.event.inputs.terraform_action }} -auto-approve -input=false - name: generate.py inventory if: ${{ github.event.inputs.terraform_action == 'apply' }} working-directory: ${{ env.VPS_ROOT }} env: AWS_ACCESS_KEY_ID: ${{ steps.vault.outputs.TF_STATE_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ steps.vault.outputs.TF_STATE_SECRET_KEY }} # 同样指定 envs/site-recovery run: python3 scripts/generate.py inventory --resources config/resources/site-recovery.yaml --workdir envs/site-recovery - name: Build deploy matrix id: matrix if: ${{ github.event.inputs.terraform_action == 'apply' }} working-directory: ${{ env.ENV_DIR }} run: | hosts="$(jq -c 'keys' cmdb.json)" echo "hosts=${hosts}" >> "$GITHUB_OUTPUT" echo "count=$(jq 'length' cmdb.json)" >> "$GITHUB_OUTPUT" - name: Upload CMDB artifact if: ${{ github.event.inputs.terraform_action == 'apply' }} uses: actions/upload-artifact@v7 with: name: site-recovery-cmdb path: | ${{ env.ENV_DIR }}/cmdb.json ${{ env.ENV_DIR }}/inventory.ini # --------------------------------------------------------------------------- # 步骤 2: 基础环境安装 (Bootstrap Deploy) # --------------------------------------------------------------------------- deploy_base: name: Deploy Base Services on ${{ matrix.host }} needs: provision if: ${{ github.event.inputs.run_provision_and_deploy == 'true' && github.event.inputs.terraform_action == 'apply' && needs.provision.outputs.count != '0' }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: host: ${{ fromJSON(needs.provision.outputs.hosts) }} steps: - name: Checkout xworkspace-console helpers uses: actions/checkout@v7 with: repository: ai-workspace-infra/iac_modules ref: ${{ github.event.inputs.infra_ref }} - name: Download CMDB uses: actions/download-artifact@v8 with: name: site-recovery-cmdb path: cmdb - name: Load Vault secrets id: vault uses: hashicorp/vault-action@v4 with: url: ${{ env.VAULT_ADDR }} method: jwt role: ${{ env.VAULT_ROLE }} jwtGithubAudience: vault ignoreNotFound: true secrets: | ${{ env.VAULT_KV }} SSH_PRIVATE_DEPLOY_KEY_B64 | ANSIBLE_SSH_KEY_B64 ; ${{ env.VAULT_KV }} AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN - name: Configure SSH Key run: | mkdir -p ~/.ssh printf '%s' "${{ steps.vault.outputs.ANSIBLE_SSH_KEY_B64 }}" | base64 -d > ~/.ssh/id_deploy chmod 600 ~/.ssh/id_deploy ssh-keygen -y -f ~/.ssh/id_deploy >/dev/null - name: Wait for host SSH run: | ip="$(jq -r '.["${{ matrix.host }}"].ip' cmdb/cmdb.json)" for _ in $(seq 1 60); do if nc -z -w 5 "$ip" 22; then exit 0; fi sleep 10 done exit 1 - name: Run on-host bootstrap env: MATRIX_HOST: ${{ matrix.host }} CMDB_PATH: cmdb/cmdb.json SSH_KEY_PATH: ~/.ssh/id_deploy AI_WORKSPACE_OFFLINE_MODE: 'off' AI_WORKSPACE_AUTH_TOKEN: ${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }} run: bash terraform-hcl-standard/vultr-vps/scripts/run-on-host-bootstrap.sh # --------------------------------------------------------------------------- # 步骤 3: 全站单向数据流迁移与环境恢复 (Data Migration) # --------------------------------------------------------------------------- data_migration: name: Migrate Site Data (Source -> Target) needs: deploy_base if: ${{ always() && github.event.inputs.terraform_action == 'apply' }} runs-on: ubuntu-latest steps: - name: Download CMDB # 只在 provision 运行的情况下下载 if: ${{ github.event.inputs.run_provision_and_deploy == 'true' }} uses: actions/download-artifact@v8 with: name: site-recovery-cmdb path: cmdb - name: Checkout playbooks uses: actions/checkout@v7 with: repository: ai-workspace-infra/playbooks ref: ${{ github.event.inputs.infra_ref }} path: infra/playbooks - name: Install Ansible run: pip install --quiet ansible - name: Load Vault secrets for SSH id: vault uses: hashicorp/vault-action@v4 with: url: ${{ env.VAULT_ADDR }} method: jwt role: ${{ env.VAULT_ROLE }} secrets: | ${{ env.VAULT_KV }} SSH_PRIVATE_DEPLOY_KEY_B64 | ANSIBLE_SSH_KEY_B64 - name: Configure SSH Key run: | mkdir -p ~/.ssh printf '%s' "${{ steps.vault.outputs.ANSIBLE_SSH_KEY_B64 }}" | base64 -d > ~/.ssh/id_deploy chmod 600 ~/.ssh/id_deploy - name: Generate Migration Inventory run: | # 动态解析 target IP,如果是纯数据迁移,则可能需要手动指定,这里以 jq 解析 cmdb 为主 if [ -f "cmdb/cmdb.json" ]; then TARGET_IP=$(jq -r 'to_entries | .[0].value.ip' cmdb/cmdb.json) else TARGET_IP="new_env_ip_placeholder" # 需要补充处理纯迁移模式下的 Target IP fi echo "[migration_source]" > inventory_migration.ini echo "${{ github.event.inputs.source_host }} ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_deploy" >> inventory_migration.ini echo "" >> inventory_migration.ini echo "[migration_target]" >> inventory_migration.ini echo "${TARGET_IP} ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_deploy" >> inventory_migration.ini - name: Run Ansible Site Migration Playbook working-directory: ${{ env.PLAYBOOKS_DIR }} run: | ansible-playbook \ -i ../inventory_migration.ini \ migrate_site.yml \ -e "target_domain=${{ github.event.inputs.target_domain_base }}" \ -e "migration_flow.source.domain_base=${{ github.event.inputs.source_domain_base }}"