name: 数据库迁移 - 回滚(危险) on: workflow_dispatch: inputs: confirm: description: '将删除所有迁移创建的表及其数据,不可恢复。确认请输入 DELETE(大写)' required: true default: '' concurrency: group: migrate cancel-in-progress: false env: REGISTRY: git.lzcwy.cn IMAGE: git.lzcwy.cn/zhengchiliu/zt-closet-backend MIGRATE_TAG: migrate jobs: migrate-down: runs-on: ubuntu-latest steps: - name: 记录开始时间 run: echo "JOB_START=$(date +%s)" >> "$GITHUB_ENV" - name: 校验确认词 env: IN_CONFIRM: ${{ gitea.event.inputs.confirm }} run: | if [ "${IN_CONFIRM:-}" != "DELETE" ]; then echo "确认词不对(收到 '${IN_CONFIRM:-空}')。必须输入 DELETE 才会执行。" >&2 exit 1 fi echo "确认通过" - name: 钉钉通知(危险操作警告) if: success() continue-on-error: true env: WEBHOOK: ${{ secrets.ZT_CLOSET_DINGTALK_WEBHOOK }} REPO: ${{ gitea.repository }} REF: ${{ gitea.ref }} SHA: ${{ gitea.sha }} ACTOR: ${{ gitea.actor }} RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }} run: | set -uo pipefail REF_NAME="${REF#refs/heads/}"; REF_NAME="${REF_NAME#refs/tags/}" SHORT_SHA="$(printf '%s' "$SHA" | cut -c1-7)" TEXT="### ⚠️ 危险操作:数据库回滚\n\n" TEXT="${TEXT}> **zt-closet-backend** · 即将执行全量回滚,不可恢复\n\n" TEXT="${TEXT}- **📦 仓库**:$REPO\n" TEXT="${TEXT}- **🌿 分支/标签**:$REF_NAME\n" TEXT="${TEXT}- **🔖 提交**:$SHORT_SHA\n" TEXT="${TEXT}- **👤 操作人**:$ACTOR\n" TEXT="${TEXT}- **🕐 时间**:$(date '+%Y-%m-%d %H:%M:%S')\n" TEXT="${TEXT}- **❗ 影响**:将删除所有迁移创建的表及其数据\n\n" TEXT="${TEXT}[查看运行详情]($RUN_URL)" printf '%s' "{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"⚠️ 危险操作:数据库回滚 · zt-closet-backend\",\"text\":\"$TEXT\"}}" > /tmp/dingtalk.json RESP="$(curl -sS -X POST "$WEBHOOK" -H 'Content-Type: application/json' -d @/tmp/dingtalk.json)" echo "钉钉响应: $RESP" - name: 拉取代码 uses: https://gitee.com/liu_zhengchi/checkout@v4 - name: 确认 docker 可用 run: | docker version --format 'Server: {{.Server.Version}}' - name: 登录 Gitea 镜像仓库 env: REGISTRY_USER: ${{ secrets.ZT_CLOSET_REGISTRY_USER }} REGISTRY_TOKEN: ${{ secrets.ZT_CLOSET_REGISTRY_TOKEN }} run: | echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin - name: 构建并推送迁移镜像 run: | docker build \ --build-arg GOPROXY=https://goproxy.cn,direct \ -f scripts/dockerfile/Dockerfile.migrate \ -t "$IMAGE:$MIGRATE_TAG" \ . docker push "$IMAGE:$MIGRATE_TAG" - name: 生产机执行回滚(结束即删容器) env: SSH_KEY: ${{ secrets.ZT_CLOSET_PROD_SSH_KEY }} SSH_HOST: ${{ vars.ZT_CLOSET_PROD_SSH_HOST }} SSH_USER: ${{ vars.ZT_CLOSET_PROD_SSH_USER }} run: | mkdir -p ~/.ssh && chmod 700 ~/.ssh printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key SSHOPT="-i ~/.ssh/deploy_key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" ssh $SSHOPT "$SSH_USER@$SSH_HOST" 'bash -s' <<'REMOTE' set -e DIR=/opt/zt-closet IMAGE=git.lzcwy.cn/zhengchiliu/zt-closet-backend:migrate cd "$DIR" || { echo "目录不存在: $DIR"; exit 1; } [ -f configs/config.yaml ] || { echo "缺 configs/config.yaml"; exit 1; } docker pull "$IMAGE" docker run --rm \ --network host \ -v "$DIR/configs:/app/configs:ro" \ -v zt-go-build-cache:/root/.cache/go-build \ -e TZ=Asia/Shanghai \ "$IMAGE" down REMOTE - name: 钉钉通知(成功) if: success() continue-on-error: true env: WEBHOOK: ${{ secrets.ZT_CLOSET_DINGTALK_WEBHOOK }} REPO: ${{ gitea.repository }} REF: ${{ gitea.ref }} SHA: ${{ gitea.sha }} ACTOR: ${{ gitea.actor }} RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }} IMAGE_REF: ${{ env.IMAGE }}:${{ env.MIGRATE_TAG }} run: | set -uo pipefail REF_NAME="${REF#refs/heads/}"; REF_NAME="${REF_NAME#refs/tags/}" SHORT_SHA="$(printf '%s' "$SHA" | cut -c1-7)" COST=$(( $(date +%s) - ${JOB_START:-$(date +%s)} )) TEXT="### ✅ 数据库回滚完成\n\n" TEXT="${TEXT}> **zt-closet-backend** · 数据库迁移-回滚\n\n" TEXT="${TEXT}- **📦 仓库**:$REPO\n" TEXT="${TEXT}- **🌿 分支/标签**:$REF_NAME\n" TEXT="${TEXT}- **🔖 提交**:$SHORT_SHA\n" TEXT="${TEXT}- **👤 操作人**:$ACTOR\n" TEXT="${TEXT}- **🐳 镜像**:$IMAGE_REF\n" TEXT="${TEXT}- **⏱️ 耗时**:$((COST/60)) 分 $((COST%60)) 秒\n" TEXT="${TEXT}- **🕐 时间**:$(date '+%Y-%m-%d %H:%M:%S')\n\n" TEXT="${TEXT}[查看运行详情]($RUN_URL)" printf '%s' "{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"✅ 数据库回滚完成 · zt-closet-backend\",\"text\":\"$TEXT\"}}" > /tmp/dingtalk.json RESP="$(curl -sS -X POST "$WEBHOOK" -H 'Content-Type: application/json' -d @/tmp/dingtalk.json)" echo "钉钉响应: $RESP" - name: 钉钉通知(失败) if: failure() continue-on-error: true env: WEBHOOK: ${{ secrets.ZT_CLOSET_DINGTALK_WEBHOOK }} REPO: ${{ gitea.repository }} REF: ${{ gitea.ref }} SHA: ${{ gitea.sha }} ACTOR: ${{ gitea.actor }} RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }} IMAGE_REF: ${{ env.IMAGE }}:${{ env.MIGRATE_TAG }} run: | set -uo pipefail REF_NAME="${REF#refs/heads/}"; REF_NAME="${REF_NAME#refs/tags/}" SHORT_SHA="$(printf '%s' "$SHA" | cut -c1-7)" COST=$(( $(date +%s) - ${JOB_START:-$(date +%s)} )) TEXT="### ❌ 数据库迁移-回滚失败\n\n" TEXT="${TEXT}> **zt-closet-backend** · 数据库迁移-回滚\n\n" TEXT="${TEXT}- **📦 仓库**:$REPO\n" TEXT="${TEXT}- **🌿 分支/标签**:$REF_NAME\n" TEXT="${TEXT}- **🔖 提交**:$SHORT_SHA\n" TEXT="${TEXT}- **👤 操作人**:$ACTOR\n" TEXT="${TEXT}- **🐳 镜像**:$IMAGE_REF\n" TEXT="${TEXT}- **⏱️ 耗时**:$((COST/60)) 分 $((COST%60)) 秒\n" TEXT="${TEXT}- **🕐 时间**:$(date '+%Y-%m-%d %H:%M:%S')\n" TEXT="${TEXT}- **🧭 失败定位**:请点击下方链接查看失败步骤与日志\n\n" TEXT="${TEXT}[查看失败日志]($RUN_URL)" printf '%s' "{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"❌ 数据库迁移-回滚失败 · zt-closet-backend\",\"text\":\"$TEXT\"}}" > /tmp/dingtalk.json RESP="$(curl -sS -X POST "$WEBHOOK" -H 'Content-Type: application/json' -d @/tmp/dingtalk.json)" echo "钉钉响应: $RESP"