ci: simplify Gitea admin deploy for act_runner
Some checks failed
Deploy admin to VPS / Build, push, and deploy admin (push) Has been cancelled

Single-job build/push/deploy plus nginx site install so self-hosted
runners do not stall on multi-job graphs.
This commit is contained in:
alisaza 2026-09-13 15:39:29 +03:30
parent a307400f83
commit ba1cf3efe2
2 changed files with 71 additions and 123 deletions

View File

@ -1,5 +1,7 @@
name: Deploy admin to VPS 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: on:
push: push:
branches: [main] branches: [main]
@ -9,62 +11,11 @@ concurrency:
group: deploy-admin-production group: deploy-admin-production
cancel-in-progress: true 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: jobs:
gate: deploy:
name: Detect direct push to main name: Build, push, and deploy admin
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: timeout-minutes: 60
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 }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -74,7 +25,6 @@ jobs:
set -euo pipefail set -euo pipefail
owner="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" owner="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')"
repo="$(echo '${{ github.event.repository.name }}' | 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 "image=git.ghabilee.ir/${owner}/${repo}" >> "$GITHUB_OUTPUT"
echo "registry=git.ghabilee.ir" >> "$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 \ scp -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
"${VPS_USER}@${VPS_HOST}:${env_path}" .env.production "${VPS_USER}@${VPS_HOST}:${env_path}" .env.production
test -s .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 - uses: docker/setup-buildx-action@v3
@ -112,42 +79,16 @@ jobs:
IMAGE_TAG: ${{ github.sha }} IMAGE_TAG: ${{ github.sha }}
run: ./scripts/ci-build-image.sh run: ./scripts/ci-build-image.sh
- name: Remove production build environment - name: Deploy container on VPS
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
env: env:
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REGISTRY_USER: ${{ github.actor }} REGISTRY_USER: ${{ github.actor }}
REGISTRY_HOST: git.ghabilee.ir 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_HOST: ${{ secrets.VPS_HOST }}
VPS_USER: ${{ secrets.VPS_USER }} VPS_USER: ${{ secrets.VPS_USER }}
SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
run: | run: |
set -euo pipefail 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 \ scp -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
deploy/docker-compose.production.yml \ deploy/docker-compose.production.yml \
"${VPS_USER}@${VPS_HOST}:/opt/ghabilee-admin/docker-compose.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)" 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}" echo "Admin health ${attempt}/36: ${health}"
if [ "$health" = healthy ]; then if [ "$health" = healthy ]; then
# فقط بعد از healthy: ایمیج‌های unused (تگ‌های قبلی) را پاک کن؛ volumeها دست نخورند
if [ -x /opt/ghabilee/scripts/docker-prune.sh ]; then if [ -x /opt/ghabilee/scripts/docker-prune.sh ]; then
/opt/ghabilee/scripts/docker-prune.sh full /opt/ghabilee/scripts/docker-prune.sh full
else else
@ -176,50 +116,23 @@ jobs:
done done
exit 1 exit 1
REMOTE REMOTE
rm -f "$HOME/.ssh/vps_key"
notify-success: - name: Notify Telegram
name: Notify Telegram (success) if: always()
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
env: env:
DEPLOY_SHA: ${{ github.sha }} DEPLOY_SHA: ${{ github.sha }}
DEPLOY_STATUS: success DEPLOY_STATUS: ${{ job.status }}
DEPLOY_COMMIT_SUBJECT: ${{ github.event.head_commit.message }} DEPLOY_COMMIT_SUBJECT: ${{ github.event.head_commit.message }}
SSH_KEY: ${{ secrets.VPS_SSH_KEY }} SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
VPS_HOST: ${{ secrets.VPS_HOST }} VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_USER: ${{ secrets.VPS_USER }} VPS_USER: ${{ secrets.VPS_USER }}
run: | 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 '?')" 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 # Map Gitea job.status to notify script expectations
./scripts/notify-via-vps.sh 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"

View File

@ -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;
}
}