name: CD / Deploy on: push: branches: [main] paths-ignore: - "docs/**" - "*.md" workflow_dispatch: inputs: environment: description: "部署环境" required: true type: choice default: staging options: - staging - production env: REGISTRY: ghcr.io BACKEND_IMAGE: ${{ github.repository }}/backend FRONTEND_IMAGE: ${{ github.repository }}/frontend jobs: # ============================================================ # Build & Push Docker images # ============================================================ build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta run: | echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Build and push backend uses: docker/build-push-action@v6 with: context: ./backend push: true tags: | ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:${{ steps.meta.outputs.sha_short }} ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:latest cache-from: type=gha cache-to: type=gha,mode=max - name: Build and push frontend uses: docker/build-push-action@v6 with: context: ./frontend push: true tags: | ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:${{ steps.meta.outputs.sha_short }} ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:latest cache-from: type=gha cache-to: type=gha,mode=max # ============================================================ # Deploy to staging (auto on push to main) # ============================================================ deploy-staging: needs: build-and-push runs-on: ubuntu-latest if: github.event_name == 'push' || github.event.inputs.environment == 'staging' environment: staging steps: - uses: actions/checkout@v4 - name: Deploy to staging server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.STAGING_HOST }} username: ${{ secrets.STAGING_USER }} key: ${{ secrets.STAGING_SSH_KEY }} script: | cd /opt/aiagent docker-compose -f docker-compose.staging.yml pull docker-compose -f docker-compose.staging.yml up -d --remove-orphans docker image prune -f - name: Health check (staging) run: | sleep 10 for i in 1 2 3 4 5; do curl -sf ${{ secrets.STAGING_URL }}/health && exit 0 echo "Health check attempt $i failed, retrying..." sleep 5 done echo "Health check failed after 5 attempts" exit 1 # ============================================================ # Deploy to production (manual trigger only) # ============================================================ deploy-production: needs: build-and-push runs-on: ubuntu-latest if: github.event.inputs.environment == 'production' environment: production steps: - uses: actions/checkout@v4 - name: Deploy to production server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.PROD_HOST }} username: ${{ secrets.PROD_USER }} key: ${{ secrets.PROD_SSH_KEY }} script: | cd /opt/aiagent bash upgrade.sh - name: Health check (production) run: | sleep 15 for i in 1 2 3 4 5; do curl -sf ${{ secrets.PROD_URL }}/health && exit 0 echo "Health check attempt $i failed, retrying..." sleep 5 done echo "Health check failed after 5 attempts" exit 1 # ============================================================ # Notify result # ============================================================ notify: needs: [deploy-staging, deploy-production] if: always() runs-on: ubuntu-latest steps: - name: Summary run: | echo "## Deploy Summary" >> $GITHUB_STEP_SUMMARY echo "- **Branch**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY echo "- **Staging**: ${{ needs.deploy-staging.result }}" >> $GITHUB_STEP_SUMMARY echo "- **Production**: ${{ needs.deploy-production.result }}" >> $GITHUB_STEP_SUMMARY