From ba1cf3efe258cbcb9a0bdde55ffe7954cd59bfe2 Mon Sep 17 00:00:00 2001 From: alisaza Date: Sun, 13 Sep 2026 15:39:29 +0330 Subject: [PATCH] ci: simplify Gitea admin deploy for act_runner Single-job build/push/deploy plus nginx site install so self-hosted runners do not stall on multi-job graphs. --- .gitea/workflows/deploy-vps.yml | 159 ++++++++------------------------ deploy/nginx/backoffice.conf | 35 +++++++ 2 files changed, 71 insertions(+), 123 deletions(-) create mode 100644 deploy/nginx/backoffice.conf diff --git a/.gitea/workflows/deploy-vps.yml b/.gitea/workflows/deploy-vps.yml index f9d87af..12b66fb 100644 --- a/.gitea/workflows/deploy-vps.yml +++ b/.gitea/workflows/deploy-vps.yml @@ -1,5 +1,7 @@ name: Deploy admin to VPS +# Single-job flow — Gitea act_runner is more reliable without multi-job graphs +# (same lesson as telegrambot deploy). Quality still runs on pull_request. on: push: branches: [main] @@ -9,62 +11,11 @@ concurrency: group: deploy-admin-production cancel-in-progress: true -permissions: - contents: read - packages: write - -# PR merges: build + deploy only (quality ran on pull_request). -# Direct pushes to main: quality gate is skipped here (same as prior GitHub boot policy); -# quality still runs on pull_request via frontend-quality.yml. jobs: - gate: - name: Detect direct push to main + deploy: + name: Build, push, and deploy admin runs-on: ubuntu-latest - outputs: - run_quality: ${{ steps.detect.outputs.run_quality }} - steps: - - id: detect - env: - # Via env — never interpolate commit text into the script body - # (backticks/`$()` in messages would otherwise become shell command substitution). - EVENT_NAME: ${{ github.event_name }} - COMMIT_MSG: ${{ github.event.head_commit.message || '' }} - run: | - set -euo pipefail - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - echo "run_quality=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - msg="$COMMIT_MSG" - if printf '%s' "$msg" | grep -qiE 'merge pull request #[0-9]+'; then - echo "run_quality=false" >> "$GITHUB_OUTPUT" - elif printf '%s' "$msg" | grep -qE '\(#[0-9]+\)[[:space:]]*$'; then - echo "run_quality=false" >> "$GITHUB_OUTPUT" - else - # Admin boot: deploy first; quality runs on pull_request workflow. - echo "Direct push to main — skipping quality gate for deploy." - echo "run_quality=false" >> "$GITHUB_OUTPUT" - fi - - quality: - needs: gate - if: needs.gate.outputs.run_quality == 'true' - uses: ./.gitea/workflows/frontend-quality.yml - permissions: - contents: read - pull-requests: read - - build: - name: Build and push admin image - needs: [gate, quality] - if: >- - always() && - needs.gate.result == 'success' && - (needs.quality.result == 'success' || needs.quality.result == 'skipped') - runs-on: ubuntu-latest - timeout-minutes: 45 - outputs: - image: ${{ steps.meta.outputs.image }} + timeout-minutes: 60 steps: - uses: actions/checkout@v4 @@ -74,7 +25,6 @@ jobs: set -euo pipefail owner="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" repo="$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" - # Gitea container registry (same host as git.ghabilee.ir) echo "image=git.ghabilee.ir/${owner}/${repo}" >> "$GITHUB_OUTPUT" echo "registry=git.ghabilee.ir" >> "$GITHUB_OUTPUT" @@ -94,7 +44,24 @@ jobs: scp -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \ "${VPS_USER}@${VPS_HOST}:${env_path}" .env.production test -s .env.production - rm -f "$HOME/.ssh/vps_key" + + - name: Install nginx site definition on VPS + env: + SSH_KEY: ${{ secrets.VPS_SSH_KEY }} + VPS_HOST: ${{ secrets.VPS_HOST }} + VPS_USER: ${{ secrets.VPS_USER }} + run: | + set -euo pipefail + SSH=(ssh -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new) + scp -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \ + deploy/nginx/backoffice.conf \ + "${VPS_USER}@${VPS_HOST}:/etc/nginx/sites-available/backoffice" + "${SSH[@]}" "${VPS_USER}@${VPS_HOST}" ' + set -eu + ln -sfn /etc/nginx/sites-available/backoffice /etc/nginx/sites-enabled/backoffice + nginx -t + systemctl reload nginx + ' - uses: docker/setup-buildx-action@v3 @@ -112,42 +79,16 @@ jobs: IMAGE_TAG: ${{ github.sha }} run: ./scripts/ci-build-image.sh - - name: Remove production build environment - if: always() - run: rm -f .env.production - - deploy: - name: Stage or deploy admin - needs: build - if: ${{ !cancelled() && needs.build.result == 'success' }} - runs-on: ubuntu-latest - timeout-minutes: 25 - permissions: - contents: read - packages: read - steps: - - uses: actions/checkout@v4 - - - name: Copy Compose definition and deploy + - name: Deploy container on VPS env: REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} REGISTRY_USER: ${{ github.actor }} REGISTRY_HOST: git.ghabilee.ir - ADMIN_IMAGE: ${{ needs.build.outputs.image }}:${{ github.sha }} + ADMIN_IMAGE: ${{ steps.meta.outputs.image }}:${{ github.sha }} VPS_HOST: ${{ secrets.VPS_HOST }} VPS_USER: ${{ secrets.VPS_USER }} - SSH_KEY: ${{ secrets.VPS_SSH_KEY }} run: | set -euo pipefail - install -m 700 -d "$HOME/.ssh" - printf '%s\n' "$SSH_KEY" > "$HOME/.ssh/vps_key" - chmod 600 "$HOME/.ssh/vps_key" - ssh -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \ - "${VPS_USER}@${VPS_HOST}" ' - set -eu - install -d -m 0750 /opt/ghabilee-admin - test -s /opt/ghabilee-admin/.env - ' scp -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \ deploy/docker-compose.production.yml \ "${VPS_USER}@${VPS_HOST}:/opt/ghabilee-admin/docker-compose.yml" @@ -163,7 +104,6 @@ jobs: health="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' ghabilee-admin 2>/dev/null || echo missing)" echo "Admin health ${attempt}/36: ${health}" if [ "$health" = healthy ]; then - # فقط بعد از healthy: ایمیج‌های unused (تگ‌های قبلی) را پاک کن؛ volumeها دست نخورند if [ -x /opt/ghabilee/scripts/docker-prune.sh ]; then /opt/ghabilee/scripts/docker-prune.sh full else @@ -176,50 +116,23 @@ jobs: done exit 1 REMOTE - rm -f "$HOME/.ssh/vps_key" - notify-success: - name: Notify Telegram (success) - needs: deploy - # `quality` is intentionally skipped after PR merges. `success()` treats - # that skipped upstream job as non-success and would skip this job too. - if: ${{ always() && needs.deploy.result == 'success' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Notify ops group of successful admin deploy + - name: Notify Telegram + if: always() env: DEPLOY_SHA: ${{ github.sha }} - DEPLOY_STATUS: success + DEPLOY_STATUS: ${{ job.status }} DEPLOY_COMMIT_SUBJECT: ${{ github.event.head_commit.message }} SSH_KEY: ${{ secrets.VPS_SSH_KEY }} VPS_HOST: ${{ secrets.VPS_HOST }} VPS_USER: ${{ secrets.VPS_USER }} run: | - export DEPLOY_VERSION="$(node -p "require('./package.json').version")" - chmod +x scripts/notify-via-vps.sh scripts/notify-ops-telegram.sh scripts/notify-deploy.sh - ./scripts/notify-via-vps.sh - - notify-failed: - name: Notify Telegram (failed) - needs: [gate, quality, build, deploy] - if: failure() - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Notify ops group of failed admin deploy - env: - DEPLOY_SHA: ${{ github.sha }} - DEPLOY_STATUS: failed - DEPLOY_COMMIT_SUBJECT: ${{ github.event.head_commit.message }} - SSH_KEY: ${{ secrets.VPS_SSH_KEY }} - VPS_HOST: ${{ secrets.VPS_HOST }} - VPS_USER: ${{ secrets.VPS_USER }} - run: | - # Do not gate on -x: notify-via-vps.sh may be 100644 in git; chmod first - # (the old `if [[ -x ... ]]` skipped the whole notify and still exited 0). export DEPLOY_VERSION="$(node -p "require('./package.json').version" 2>/dev/null || echo '?')" - chmod +x scripts/notify-via-vps.sh scripts/notify-ops-telegram.sh scripts/notify-deploy.sh - ./scripts/notify-via-vps.sh + # Map Gitea job.status to notify script expectations + case "${DEPLOY_STATUS}" in + success) export DEPLOY_STATUS=success ;; + *) export DEPLOY_STATUS=failed ;; + esac + chmod +x scripts/notify-via-vps.sh scripts/notify-ops-telegram.sh scripts/notify-deploy.sh || true + ./scripts/notify-via-vps.sh || true + rm -f .env.production "$HOME/.ssh/vps_key" diff --git a/deploy/nginx/backoffice.conf b/deploy/nginx/backoffice.conf new file mode 100644 index 0000000..9a099b0 --- /dev/null +++ b/deploy/nginx/backoffice.conf @@ -0,0 +1,35 @@ +# Admin backoffice — TLS terminated by Nginx, app on 127.0.0.1:3009 +# DNS: point backoffice.ghabilee.ir at this VPS, then: +# certbot --nginx -d backoffice.ghabilee.ir + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream ghabilee_admin { + server 127.0.0.1:3009; + keepalive 16; +} + +server { + listen 80; + listen [::]:80; + server_name backoffice.ghabilee.ir; + + location /.well-known/acme-challenge/ { + root /var/www/html; + } + + location / { + proxy_pass http://ghabilee_admin; + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 60s; + } +}