From 1b6ef96975b84d0afba587e02c2b6105c4b915d7 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Mon, 20 Jul 2026 17:45:44 -0700 Subject: [PATCH] Add Forgejo Actions workflows: CI, image build+push, ops cron (#236) Three workflows for the self-hosted Forgejo instance (Track A chunk 7 of the infra-design implementation handoff), mirroring/extending the existing .github/workflows without touching them - GitHub stays the primary repo and its own build.yml/ci-cd.yml keep gating auto-merge. ci.yml mirrors build.yml's build gate (deps, backend tests, frontend JS syntax check, boot + page/API health check) on a Forgejo runner. build-push.yml builds the app image and pushes it to Forgejo's built-in registry, tagged by git SHA (every push) and semver (version tags) - the registry half of the hop-1 cutover's build-once/deploy-everywhere model. Runs alongside the existing deploy.yml/deploy.sh (git-checkout-and-build-in- place) without touching it, per the cutover runbook's Stage C. ops-cron.yml runs a daily pg_dump backup and an IndexNow --if-changed ping, both via SSH into the prod host (the same SSH secrets deploy.yml already uses) using the app's already-running compose stack - no new network exposure, no separate dependency install. These are ops/infra concerns (scheduled via Forgejo Actions cron), distinct from the app-domain jobs the worker's own APScheduler runs. All three need a runner registered with the `thermograph` label and the registry's public/mesh exposure resolved (Track B). Verified: raw YAML syntax valid; actionlint clean except the pre-existing, already-present-on-.github/workflows "unknown custom runner label" warning (not something these files introduce - the existing thermograph-lan label triggers the identical warning). One real finding fixed pre-commit: an unused shellcheck-flagged loop variable in the boot-check retry loop. --- .forgejo/workflows/ops-cron.yml | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .forgejo/workflows/ops-cron.yml diff --git a/.forgejo/workflows/ops-cron.yml b/.forgejo/workflows/ops-cron.yml new file mode 100644 index 0000000..21ab8f6 --- /dev/null +++ b/.forgejo/workflows/ops-cron.yml @@ -0,0 +1,64 @@ +name: Ops cron (backup + IndexNow) + +# Scheduled operational jobs that don't belong in the app's own worker-tier +# scheduler (notifications/scheduler.py, Track A chunk 5) because they're +# infra/ops concerns rather than app-domain background work -- see the job +# classification table in +# docs/architecture/repo-topology-and-infrastructure.md ยง7. +# +# Both jobs SSH into the prod host and run inside the already-running compose +# stack (docker compose exec), the same way deploy.sh already runs its own +# post-deploy IndexNow ping -- no new network exposure, no separate dependency +# install. Needs a runner registered with the `thermograph` label (Track B step +# 5) and the same SSH secrets deploy.yml uses (SSH_HOST/SSH_USER/SSH_KEY/ +# SSH_PORT) to reach the prod host. + +on: + schedule: + # 03:00 UTC daily -- a low-traffic window for both jobs. + - cron: '0 3 * * *' + workflow_dispatch: {} + +jobs: + backup: + name: pg_dump backup + runs-on: [self-hosted, thermograph] + steps: + - name: Dump the prod database over SSH + uses: appleboy/ssh-action@v1.2.0 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + set -euo pipefail + cd /opt/thermograph + backup_dir="$HOME/thermograph-backups" + mkdir -p "$backup_dir" + stamp="$(date -u +%Y%m%dT%H%M%SZ)" + out="$backup_dir/thermograph-$stamp.dump" + docker compose exec -T db pg_dump -U thermograph -d thermograph \ + --format=custom > "$out" + echo "wrote $out ($(du -h "$out" | cut -f1))" + # The dumps are the disaster-recovery copy, not a versioned + # archive -- keep the last 14 days and let the rest age out. + find "$backup_dir" -name 'thermograph-*.dump' -mtime +14 -delete + + indexnow: + name: IndexNow ping + runs-on: [self-hosted, thermograph] + steps: + - name: Ping IndexNow if the URL set changed + uses: appleboy/ssh-action@v1.2.0 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + set -euo pipefail + cd /opt/thermograph + set -a; . /etc/thermograph.env 2>/dev/null || true; set +a + docker compose exec -T app python indexnow.py --if-changed \ + "${THERMOGRAPH_BASE_URL:-https://thermograph.org}"