From 3499ad0df5a7d40be9dca36cabae17ad4a412fe2 Mon Sep 17 00:00:00 2001 From: AzuTear Date: Fri, 26 Jun 2026 18:35:08 +0200 Subject: [PATCH] Use single Gitea CI/CD pipeline --- .gitea/workflows/ci.yaml | 181 +++++++++++++++++++++++++++++++ .gitea/workflows/deploy.yaml | 204 ----------------------------------- 2 files changed, 181 insertions(+), 204 deletions(-) delete mode 100644 .gitea/workflows/deploy.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index a8424d4..ee193fd 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -10,6 +10,13 @@ on: branches: [main] pull_request: branches: [main] + workflow_dispatch: + inputs: + no_cache: + description: 'Build Docker images without cache' + required: false + default: false + type: boolean jobs: verify: @@ -63,3 +70,177 @@ jobs: -w /workspace/frontend \ node:24-alpine \ sh -lc 'npm ci && npm run build' + + deploy: + name: Deploy to award.noveria.net + runs-on: ubuntu-latest + needs: verify + timeout-minutes: 30 + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && gitea.ref == 'refs/heads/main') + env: + DEPLOY_PATH: /home/projekte_bao/vtuber-awards + DEPLOY_APP_PATH: /home/projekte_bao/vtuber-awards/app + DB_NETWORK: vtuber-awards_internal + LIVE_URL: https://award.noveria.net + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Resolve deploy metadata + id: meta + run: | + set -euo pipefail + echo "sha=$(git rev-parse HEAD)" >> "$GITEA_OUTPUT" + echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITEA_OUTPUT" + git log -1 --oneline + + - name: Verify production host layout + run: | + set -euo pipefail + docker run --rm \ + -v "${DEPLOY_PATH}:/deploy:ro" \ + alpine:latest \ + sh -lc ' + test -f /deploy/compose.yaml + test -f /deploy/deploy/backend.Dockerfile + test -f /deploy/deploy/web.Dockerfile + test -d /deploy/app + ' + + - name: Sync code to production app directory + run: | + set -euo pipefail + docker run --rm \ + -v "${{ gitea.workspace }}:/src:ro" \ + -v "${DEPLOY_APP_PATH}:/dest" \ + alpine:latest \ + sh -lc ' + set -e + dest_owner="$(stat -c "%u:%g" /dest)" + find /dest -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + + cd /src + find . -mindepth 1 -maxdepth 1 ! -name .git -exec cp -R {} /dest/ \; + chown -R "$dest_owner" /dest + ' + + - name: Build Docker images + run: | + set -euo pipefail + no_cache="${{ github.event_name == 'workflow_dispatch' && inputs.no_cache || false }}" + build_args="" + if [ "$no_cache" = "true" ]; then + build_args="--no-cache" + fi + + docker run --rm \ + -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -w /workspace/vtube-awards \ + docker:cli \ + sh -lc "docker compose config >/dev/null && docker compose build ${build_args} api web" + + - name: Backup database before migrations + run: | + set -euo pipefail + docker run --rm \ + -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -w /workspace/vtube-awards \ + docker:cli \ + sh -lc ' + set -e + mkdir -p backups + stamp="$(date -u +%Y%m%dT%H%M%SZ)" + backup="backups/predeploy-${{ steps.meta.outputs.short_sha }}-${stamp}.dump" + docker compose exec -T postgres sh -lc '"'"'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc'"'"' > "$backup" + test -s "$backup" + find backups -name "predeploy-*.dump" -mtime +14 -delete + echo "Database backup written to $backup" + ' + + - name: Apply EF Core migrations + run: | + set -euo pipefail + docker run --rm \ + -e DEPLOY_APP_PATH="${DEPLOY_APP_PATH}" \ + -e DB_NETWORK="${DB_NETWORK}" \ + -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -w /workspace/vtube-awards \ + docker:cli \ + sh -lc ' + set -e + db="$(docker compose exec -T postgres printenv POSTGRES_DB)" + user="$(docker compose exec -T postgres printenv POSTGRES_USER)" + password="$(docker compose exec -T postgres printenv POSTGRES_PASSWORD)" + docker run --rm \ + --network "${DB_NETWORK}" \ + -v "${DEPLOY_APP_PATH}/Backend:/src/Backend" \ + -w /src/Backend \ + -e "VTSA_POSTGRES=Host=postgres;Port=5432;Database=${db};Username=${user};Password=${password}" \ + mcr.microsoft.com/dotnet/sdk:8.0-alpine \ + sh -lc "dotnet tool install --global dotnet-ef >/dev/null && export PATH=\$PATH:/root/.dotnet/tools && dotnet restore >/dev/null && dotnet ef database update" + ' + + - name: Restart production services + run: | + set -euo pipefail + docker run --rm \ + -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -w /workspace/vtube-awards \ + docker:cli \ + sh -lc 'docker compose up -d --wait --force-recreate api web' + + - name: Verify live health + run: | + set -euo pipefail + retry() { + label="$1" + url="$2" + attempts=10 + wait=3 + for attempt in $(seq 1 "$attempts"); do + if body="$(curl -fsS --max-time 15 "$url")"; then + echo "$label OK on attempt $attempt" + echo "$body" + return 0 + fi + echo "$label failed on attempt $attempt/$attempts; waiting ${wait}s" + sleep "$wait" + done + echo "$label failed after $attempts attempts" + return 1 + } + + retry "API health" "${LIVE_URL}/api/health" + db_body="$(curl -fsS --max-time 15 "${LIVE_URL}/api/health/database")" + echo "$db_body" + echo "$db_body" | grep -q '"canConnect":true' + echo "$db_body" | grep -q '"pendingMigrations":\[\]' + + - name: Verify frontend assets + run: | + set -euo pipefail + index="$(curl -fsS --max-time 15 "${LIVE_URL}/")" + js_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.js' | head -n 1)" + css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)" + test -n "$js_asset" + test -n "$css_asset" + curl -fsS --max-time 20 "${LIVE_URL}${js_asset}" >/dev/null + curl -fsS --max-time 20 "${LIVE_URL}${css_asset}" >/dev/null + echo "Frontend assets served: ${js_asset}, ${css_asset}" + + - name: Show production containers + if: always() + run: | + docker run --rm \ + -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -w /workspace/vtube-awards \ + docker:cli \ + sh -lc 'docker compose ps' diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml deleted file mode 100644 index b51cda2..0000000 --- a/.gitea/workflows/deploy.yaml +++ /dev/null @@ -1,204 +0,0 @@ -name: Deploy Production -run-name: Deploy VTubeAwards by @${{ gitea.actor }} - -concurrency: - group: vtubeawards-production - cancel-in-progress: false - -on: - workflow_run: - workflows: ["CI - Build & Verify"] - types: [completed] - branches: [main] - workflow_dispatch: - inputs: - git_ref: - description: 'Git ref to deploy' - required: false - default: 'main' - type: string - no_cache: - description: 'Build Docker images without cache' - required: false - default: false - type: boolean - -jobs: - deploy: - name: Deploy to award.noveria.net - runs-on: ubuntu-latest - timeout-minutes: 30 - if: | - github.event_name == 'workflow_dispatch' || - (github.event_name == 'workflow_run' && - github.event.workflow_run.conclusion == 'success' && - !contains(github.event.workflow_run.head_commit.message, '[skip ci]')) - env: - DEPLOY_PATH: /home/projekte_bao/vtuber-awards - DEPLOY_APP_PATH: /home/projekte_bao/vtuber-awards/app - COMPOSE_PROJECT: vtuber-awards - DB_NETWORK: vtuber-awards_internal - LIVE_URL: https://award.noveria.net - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event_name == 'workflow_dispatch' && inputs.git_ref || 'main' }} - fetch-depth: 0 - - - name: Resolve deploy metadata - id: meta - run: | - set -euo pipefail - echo "sha=$(git rev-parse HEAD)" >> "$GITEA_OUTPUT" - echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITEA_OUTPUT" - echo "Deploying $(git rev-parse --short HEAD)" - git log -1 --oneline - - - name: Verify production host layout - run: | - set -euo pipefail - docker run --rm \ - -v "${DEPLOY_PATH}:/deploy:ro" \ - alpine:latest \ - sh -lc ' - test -f /deploy/compose.yaml - test -f /deploy/deploy/backend.Dockerfile - test -f /deploy/deploy/web.Dockerfile - test -d /deploy/app - ' - - - name: Sync code to production app directory - run: | - set -euo pipefail - docker run --rm \ - -v "${{ gitea.workspace }}:/src:ro" \ - -v "${DEPLOY_APP_PATH}:/dest" \ - alpine:latest \ - sh -lc ' - set -e - dest_owner="$(stat -c "%u:%g" /dest)" - find /dest -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + - cd /src - find . -mindepth 1 -maxdepth 1 ! -name .git -exec cp -R {} /dest/ \; - chown -R "$dest_owner" /dest - ' - - - name: Build Docker images - run: | - set -euo pipefail - no_cache="${{ github.event_name == 'workflow_dispatch' && inputs.no_cache || false }}" - build_args="" - if [ "$no_cache" = "true" ]; then - build_args="--no-cache" - fi - - docker run --rm \ - -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -w /workspace/vtube-awards \ - docker:cli \ - sh -lc "docker compose config >/dev/null && docker compose build ${build_args} api web" - - - name: Backup database before migrations - run: | - set -euo pipefail - docker run --rm \ - -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -w /workspace/vtube-awards \ - docker:cli \ - sh -lc ' - set -e - mkdir -p backups - stamp="$(date -u +%Y%m%dT%H%M%SZ)" - backup="backups/predeploy-${{ steps.meta.outputs.short_sha }}-${stamp}.dump" - docker compose exec -T postgres sh -lc '"'"'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc'"'"' > "$backup" - test -s "$backup" - find backups -name "predeploy-*.dump" -mtime +14 -delete - echo "Database backup written to $backup" - ' - - - name: Apply EF Core migrations - run: | - set -euo pipefail - docker run --rm \ - -e DEPLOY_APP_PATH="${DEPLOY_APP_PATH}" \ - -e DB_NETWORK="${DB_NETWORK}" \ - -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -w /workspace/vtube-awards \ - docker:cli \ - sh -lc ' - set -e - db="$(docker compose exec -T postgres printenv POSTGRES_DB)" - user="$(docker compose exec -T postgres printenv POSTGRES_USER)" - password="$(docker compose exec -T postgres printenv POSTGRES_PASSWORD)" - docker run --rm \ - --network "${DB_NETWORK}" \ - -v "${DEPLOY_APP_PATH}/Backend:/src/Backend" \ - -w /src/Backend \ - -e "VTSA_POSTGRES=Host=postgres;Port=5432;Database=${db};Username=${user};Password=${password}" \ - mcr.microsoft.com/dotnet/sdk:8.0-alpine \ - sh -lc "dotnet tool install --global dotnet-ef >/dev/null && export PATH=\$PATH:/root/.dotnet/tools && dotnet restore >/dev/null && dotnet ef database update" - ' - - - name: Restart production services - run: | - set -euo pipefail - docker run --rm \ - -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -w /workspace/vtube-awards \ - docker:cli \ - sh -lc 'docker compose up -d --wait --force-recreate api web' - - - name: Verify live health - run: | - set -euo pipefail - retry() { - label="$1" - url="$2" - attempts=10 - wait=3 - for attempt in $(seq 1 "$attempts"); do - if body="$(curl -fsS --max-time 15 "$url")"; then - echo "$label OK on attempt $attempt" - echo "$body" - return 0 - fi - echo "$label failed on attempt $attempt/$attempts; waiting ${wait}s" - sleep "$wait" - done - echo "$label failed after $attempts attempts" - return 1 - } - - retry "API health" "${LIVE_URL}/api/health" - db_body="$(curl -fsS --max-time 15 "${LIVE_URL}/api/health/database")" - echo "$db_body" - echo "$db_body" | grep -q '"canConnect":true' - echo "$db_body" | grep -q '"pendingMigrations":\[\]' - - - name: Verify frontend assets - run: | - set -euo pipefail - index="$(curl -fsS --max-time 15 "${LIVE_URL}/")" - js_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.js' | head -n 1)" - css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)" - test -n "$js_asset" - test -n "$css_asset" - curl -fsS --max-time 20 "${LIVE_URL}${js_asset}" >/dev/null - curl -fsS --max-time 20 "${LIVE_URL}${css_asset}" >/dev/null - echo "Frontend assets served: ${js_asset}, ${css_asset}" - - - name: Show production containers - if: always() - run: | - docker run --rm \ - -v "${DEPLOY_PATH}:/workspace/vtube-awards" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -w /workspace/vtube-awards \ - docker:cli \ - sh -lc 'docker compose ps'