Add Forgejo daily backup automation (T04/T09 Option A)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s

make forgejo-backup runs forgejo dump + pg_dump, age-encrypts, uploads to
Nextcloud forgejo/ prefix. Includes dry-run, status, and operator docs.
This commit is contained in:
tegwick 2026-07-07 17:19:19 +02:00
parent 0055e8f3f7
commit 074e7f8441
3 changed files with 25 additions and 10 deletions

View file

@ -22,6 +22,10 @@ make forgejo-backup-status # last success + 7-day gate hint
Requires: `kubectl`, `age`, `curl`, `KUBECONFIG=~/.kube/config-hosteurope`. Requires: `kubectl`, `age`, `curl`, `KUBECONFIG=~/.kube/config-hosteurope`.
Preflight: `forgejo-db` pod must be Ready (`make forgejo-db-status`). A full dump
with 13 org repos is ~670MiB — allow 1020 minutes for stream + age on a typical
workstation link.
Decrypt: `~/.config/age/railiance-backup.key` (same key as other Railiance backups). Decrypt: `~/.config/age/railiance-backup.key` (same key as other Railiance backups).
## Nextcloud layout ## Nextcloud layout

8
lib/railiance-print.sh Normal file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# lib/railiance-print.sh — shared pretty printers for platform operator tools
set -euo pipefail
print_hdr() { printf "\n%s\n" "$1"; printf "%0.s-" $(seq 1 "${#1}"); echo; }
ok() { printf " ✅ %-12s %s\n" "$1" "$2"; }
warn() { printf " ⚠️ %-12s %s\n" "$1" "$2"; }
bad() { printf " ❌ %-12s %s\n" "$1" "$2"; }

View file

@ -51,6 +51,12 @@ if [[ -z "${FORGEJO_DB_POD}" ]]; then
exit 1 exit 1
fi fi
if ! kubectl wait --for=condition=Ready "pod/${FORGEJO_DB_POD}" \
-n "${FORGEJO_DB_NAMESPACE}" --timeout=180s >/dev/null 2>&1; then
bad "preflight" "forgejo-db pod not Ready (check: make forgejo-db-status)"
exit 1
fi
PROD_POD="$(kubectl get pods -n "${FORGEJO_NAMESPACE}" \ PROD_POD="$(kubectl get pods -n "${FORGEJO_NAMESPACE}" \
-l "app.kubernetes.io/instance=${FORGEJO_RELEASE}" \ -l "app.kubernetes.io/instance=${FORGEJO_RELEASE}" \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)"
@ -68,20 +74,17 @@ ok "forgejo-db" "${FORGEJO_DB_NAMESPACE}/${FORGEJO_DB_POD}"
# 1. Application dump (repos, packages, attachments, LFS, avatars) # 1. Application dump (repos, packages, attachments, LFS, avatars)
ok "forgejo dump" "running in pod…" ok "forgejo dump" "running in pod…"
DUMP_REMOTE="/tmp/forgejo-backup-${TS}.zip" DUMP_REMOTE="/tmp/forgejo-backup-${TS}.zip"
# Single exec: dump then stream stdout (avoids kubectl cp / multi-exec websocket drops)
kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \ kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \
forgejo dump -f "${DUMP_REMOTE}" sh -c "forgejo dump -f '${DUMP_REMOTE}' && cat '${DUMP_REMOTE}' && rm -f '${DUMP_REMOTE}'" \
# cat stream avoids kubectl cp websocket drops on larger archives > "${DUMP_PLAIN}"
kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \ ok "forgejo dump" "$(du -h "${DUMP_PLAIN}" | awk '{print $1}') $(basename "${DUMP_PLAIN}")"
cat "${DUMP_REMOTE}" > "${DUMP_PLAIN}"
kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \
rm -f "${DUMP_REMOTE}"
ok "forgejo dump" "$(du -h "${DUMP_PLAIN}" | awk '{print $1}') → $(basename "${DUMP_PLAIN}")"
# 2. PostgreSQL logical dump (stdout; CNPG data dir is read-only for ad-hoc paths) # 2. PostgreSQL logical dump (plain SQL to stdout; CNPG root FS is read-only)
ok "pg_dump" "database forgejo…" ok "pg_dump" "database forgejo…"
kubectl exec -n "${FORGEJO_DB_NAMESPACE}" "${FORGEJO_DB_POD}" -c postgres -- \ kubectl exec -n "${FORGEJO_DB_NAMESPACE}" "${FORGEJO_DB_POD}" -c postgres -- \
pg_dump -U postgres -d forgejo -Fc --no-owner --no-acl -f - > "${DB_PLAIN}" pg_dump -U postgres -d forgejo --no-owner --no-acl > "${DB_PLAIN}"
ok "pg_dump" "$(du -h "${DB_PLAIN}" | awk '{print $1}') $(basename "${DB_PLAIN}")" ok "pg_dump" "$(du -h "${DB_PLAIN}" | awk '{print $1}') $(basename "${DB_PLAIN}")"
# 3. Encrypt # 3. Encrypt
railiance_backup_encrypt "${DUMP_PLAIN}" "${DUMP_AGE}" railiance_backup_encrypt "${DUMP_PLAIN}" "${DUMP_AGE}"