name: Deploy # 参考实现:E:\Desktop\CICD\.gitea\workflows\ci.yml # 关键配置:container: dockerreg.eazygame.cn/node-with-docker:22(job 内执行 docker compose) # 部署到服务器 # 触发条件:main 分支 docker.yml 完成后 自动,或手动 workflow_dispatch # 部署方式:Runner 直接执行 docker compose(Runner 跑在服务器上) # # 前置条件(一次性配置): # 1. 服务器上已安装 Gitea Actions Runner,标签为 'ubuntu-latest' # 2. 服务器上已存在 /opt/edu/docker-compose.deploy.yml(由 SRE AI 首次部署) # 3. 服务器上已存在 /opt/edu/.env(含生产 JWT_SECRET 等) # 4. 服务器 Docker 已登录 Gitea Container Registry: # docker login git.eazygame.cn -u -p # 5. MySQL + Redis 已在服务器 Docker 中运行(3306/6379) # 详见 docs/standards/cicd-runbook.md §10、project_rules §15.7 on: workflow_dispatch: inputs: image_tag: description: '镜像 tag(默认 latest)' required: false default: 'latest' rollback: description: '回滚到上一版本(跳过 pull)' type: boolean required: false default: false workflow_run: workflows: ['Docker Build & Push'] types: [completed] branches: [main] permissions: contents: read env: DEPLOY_DIR: /opt/edu REGISTRY: git.eazygame.cn IMAGE_TAG: ${{ github.event.inputs.image_tag || 'latest' }} jobs: deploy: # 仅在 docker.yml 成功后触发,或手动触发 if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest container: dockerreg.eazygame.cn/node-with-docker:22 environment: name: production steps: - name: Checkout uses: actions/checkout@v4 - name: Setup deploy dir run: | sudo mkdir -p ${{ env.DEPLOY_DIR }} sudo chown -R $(id -u):$(id -g) ${{ env.DEPLOY_DIR }} - name: Sync compose file run: | cp infra/docker-compose.deploy.yml ${{ env.DEPLOY_DIR }}/docker-compose.yml # 确保 .env 存在(首次部署需人工创建) if [ ! -f ${{ env.DEPLOY_DIR }}/.env ]; then echo "::error::部署目录缺少 .env 文件,请参考 infra/deploy.env.example 创建" exit 1 fi - name: Login to Gitea Container Registry run: | # 使用预存的 ~/.docker/config.json(首次部署时人工 docker login) # 或使用 secrets 注入 if [ -n "${{ secrets.GITEA_REGISTRY_TOKEN }}" ]; then echo "${{ secrets.GITEA_REGISTRY_TOKEN }}" | \ docker login ${{ env.REGISTRY }} -u ${{ secrets.GITEA_REGISTRY_USER }} --password-stdin fi - name: Pull images if: ${{ github.event.inputs.rollback != 'true' }} run: | cd ${{ env.DEPLOY_DIR }} export IMAGE_TAG=${{ env.IMAGE_TAG }} docker compose pull continue-on-error: true # 首次部署可能无旧镜像 - name: Deploy services run: | cd ${{ env.DEPLOY_DIR }} export IMAGE_TAG=${{ env.IMAGE_TAG }} docker compose up -d --remove-orphans - name: Wait for health run: | echo "等待服务健康..." sleep 10 for i in 1 2 3 4 5 6 7 8 9 10; do FAIL=0 echo "[尝试 $i] 检查 api-gateway..." if ! curl -sf http://localhost:8080/healthz > /dev/null; then echo " api-gateway 未就绪" FAIL=1 fi echo "[尝试 $i] 检查 classes..." if ! curl -sf http://localhost:3001/healthz > /dev/null; then echo " classes 未就绪" FAIL=1 fi echo "[尝试 $i] 检查 teacher-portal..." if ! curl -sf http://localhost:3000/ > /dev/null; then echo " teacher-portal 未就绪" FAIL=1 fi if [ $FAIL -eq 0 ]; then echo "✅ 所有服务健康" exit 0 fi sleep 6 done echo "::error::服务健康检查失败" echo "=== 容器状态 ===" cd ${{ env.DEPLOY_DIR }} && docker compose ps echo "=== api-gateway 日志 ===" docker compose logs --tail=50 api-gateway echo "=== classes 日志 ===" docker compose logs --tail=50 classes exit 1 - name: Summary if: always() run: | echo "### 部署结果" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- 镜像 tag:\`${{ env.IMAGE_TAG }}\`" >> $GITHUB_STEP_SUMMARY echo "- 部署目录:\`${{ env.DEPLOY_DIR }}\`" >> $GITHUB_STEP_SUMMARY echo "- 回滚模式:${{ github.event.inputs.rollback || 'false' }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY cd ${{ env.DEPLOY_DIR }} && docker compose ps >> $GITHUB_STEP_SUMMARY 2>&1 || true