Deploy #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy | |
| on: | |
| workflow_run: | |
| workflows: ["CD"] | |
| types: [completed] | |
| branches: ['v*'] | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: 'Environment to deploy to' | |
| required: true | |
| default: 'staging' | |
| type: choice | |
| options: | |
| - staging | |
| - production | |
| version: | |
| description: 'Version to deploy (e.g., v1.0.0)' | |
| required: false | |
| jobs: | |
| prepare: | |
| name: Prepare | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| is_tag: ${{ steps.version.outputs.is_tag }} | |
| steps: | |
| - name: Determine version | |
| id: version | |
| env: | |
| INPUT_VERSION: ${{ inputs.version }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| # workflow_run: tag is in head_branch | |
| WR_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} | |
| run: | | |
| IS_TAG="false" | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "$INPUT_VERSION" ]; then | |
| V="$INPUT_VERSION" | |
| IS_TAG="true" | |
| elif [ "$EVENT_NAME" = "workflow_run" ] && [ -n "$WR_HEAD_BRANCH" ]; then | |
| V="$WR_HEAD_BRANCH" | |
| IS_TAG="true" | |
| else | |
| V="latest" | |
| fi | |
| # Strip 'v' prefix — docker tags use semver without it | |
| echo "version=${V#v}" >> "$GITHUB_OUTPUT" | |
| echo "is_tag=${IS_TAG}" >> "$GITHUB_OUTPUT" | |
| deploy-staging: | |
| name: Deploy to Staging | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| if: github.event.inputs.environment == 'staging' | |
| environment: staging | |
| steps: | |
| - name: Deploy to Staging | |
| uses: appleboy/ssh-action@v1.0.0 | |
| with: | |
| host: ${{ secrets.STAGING_HOST }} | |
| username: ${{ secrets.DEPLOY_USER }} | |
| key: ${{ secrets.DEPLOY_SSH_KEY }} | |
| script: | | |
| cd ~/TJudge | |
| export GITHUB_REPOSITORY_OWNER="4rh1t3ct0r7" | |
| ./scripts/deploy.sh staging ${{ needs.prepare.outputs.version }} | |
| - name: Health check | |
| env: | |
| STAGING_URL: ${{ secrets.STAGING_URL }} | |
| run: | | |
| if [ -z "$STAGING_URL" ]; then | |
| echo "STAGING_URL secret not set, skipping health check" | |
| exit 0 | |
| fi | |
| for i in $(seq 1 30); do | |
| if curl -sf "$STAGING_URL/health"; then | |
| echo "Health check passed" | |
| exit 0 | |
| fi | |
| echo "Waiting for health check... ($i/30)" | |
| sleep 10 | |
| done | |
| echo "Health check failed" | |
| exit 1 | |
| deploy-production: | |
| name: Deploy to Production | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| if: github.event.inputs.environment == 'production' || (github.event_name == 'workflow_run' && needs.prepare.outputs.is_tag == 'true') | |
| environment: production | |
| steps: | |
| - name: Deploy to production | |
| uses: appleboy/ssh-action@v1.0.0 | |
| with: | |
| host: ${{ secrets.PROD_HOST }} | |
| username: ${{ secrets.DEPLOY_USER }} | |
| key: ${{ secrets.DEPLOY_SSH_KEY }} | |
| envs: GHCR_TOKEN | |
| script: | | |
| cd ~/TJudge | |
| git fetch origin && git pull origin main | |
| echo "$GHCR_TOKEN" | docker login ghcr.io -u 4rh1t3ct0r7 --password-stdin | |
| export GITHUB_REPOSITORY_OWNER="4rh1t3ct0r7" | |
| export HOST_PROGRAMS_PATH="$(pwd)/data/programs" | |
| mkdir -p data/programs | |
| ./scripts/init-secrets.sh | |
| VERSION=${{ needs.prepare.outputs.version }} docker compose -f docker-compose.prod.yml pull | |
| VERSION=${{ needs.prepare.outputs.version }} docker compose -f docker-compose.prod.yml up -d --remove-orphans | |
| docker image prune -f | |
| docker builder prune -f | |
| env: | |
| GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} | |
| - name: Health check production | |
| env: | |
| PRODUCTION_URL: ${{ secrets.PRODUCTION_URL }} | |
| run: | | |
| if [ -z "$PRODUCTION_URL" ]; then | |
| echo "PRODUCTION_URL secret not set, skipping health check" | |
| exit 0 | |
| fi | |
| for i in $(seq 1 30); do | |
| if curl -sf "$PRODUCTION_URL/health"; then | |
| echo "Health check passed" | |
| exit 0 | |
| fi | |
| echo "Waiting for health check... ($i/30)" | |
| sleep 10 | |
| done | |
| echo "Health check failed" | |
| exit 1 | |
| rollback: | |
| name: Rollback Production | |
| runs-on: ubuntu-latest | |
| if: failure() && needs.deploy-production.result == 'failure' | |
| needs: [prepare, deploy-production] | |
| environment: production | |
| steps: | |
| - name: Rollback to previous version | |
| uses: appleboy/ssh-action@v1.0.0 | |
| with: | |
| host: ${{ secrets.PROD_HOST }} | |
| username: ${{ secrets.DEPLOY_USER }} | |
| key: ${{ secrets.DEPLOY_SSH_KEY }} | |
| script: | | |
| cd ~/TJudge | |
| ./scripts/init-secrets.sh | |
| docker compose -f docker-compose.prod.yml up -d --remove-orphans |