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] workflow_dispatch: concurrency: group: deploy-admin-production cancel-in-progress: true jobs: deploy: name: Build, push, and deploy admin runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v4 - name: Image metadata id: meta run: | set -euo pipefail owner="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" repo="$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" echo "image=git.ghabilee.ir/${owner}/${repo}" >> "$GITHUB_OUTPUT" echo "registry=git.ghabilee.ir" >> "$GITHUB_OUTPUT" - name: Fetch production build environment env: SSH_KEY: ${{ secrets.VPS_SSH_KEY }} VPS_HOST: ${{ secrets.VPS_HOST }} VPS_USER: ${{ secrets.VPS_USER }} 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" env_path="$(ssh -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \ "${VPS_USER}@${VPS_HOST}" \ 'test -s /opt/ghabilee-admin/.env && printf %s /opt/ghabilee-admin/.env')" 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 - 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 - name: Log in to Gitea container registry uses: docker/login-action@v3 with: registry: ${{ steps.meta.outputs.registry }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push env: ADMIN_ENV_FILE: .env.production IMAGE_REPO: ${{ steps.meta.outputs.image }} IMAGE_TAG: ${{ github.sha }} run: ./scripts/ci-build-image.sh - name: Deploy container on VPS env: REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} REGISTRY_USER: ${{ github.actor }} REGISTRY_HOST: git.ghabilee.ir ADMIN_IMAGE: ${{ steps.meta.outputs.image }}:${{ github.sha }} VPS_HOST: ${{ secrets.VPS_HOST }} VPS_USER: ${{ secrets.VPS_USER }} run: | set -euo pipefail 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" ssh -i "$HOME/.ssh/vps_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \ "${VPS_USER}@${VPS_HOST}" \ "REGISTRY_TOKEN='${REGISTRY_TOKEN}' REGISTRY_USER='${REGISTRY_USER}' REGISTRY_HOST='${REGISTRY_HOST}' ADMIN_IMAGE='${ADMIN_IMAGE}' sh -s" <<'REMOTE' set -eu echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin docker pull "$ADMIN_IMAGE" cd /opt/ghabilee-admin ADMIN_IMAGE="$ADMIN_IMAGE" docker compose -f docker-compose.yml up -d --no-deps ghabilee-admin for attempt in $(seq 1 36); do 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 if [ -x /opt/ghabilee/scripts/docker-prune.sh ]; then /opt/ghabilee/scripts/docker-prune.sh full else docker image prune -af fi exit 0 fi case "$health" in unhealthy|exited|dead|missing) exit 1;; esac sleep 5 done exit 1 REMOTE - name: Notify Telegram if: always() env: DEPLOY_SHA: ${{ github.sha }} 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" 2>/dev/null || echo '?')" # 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"