From 074e7f8441a739b681320c78de6a977b324f9d79 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 7 Jul 2026 17:19:19 +0200 Subject: [PATCH] Add Forgejo daily backup automation (T04/T09 Option A) make forgejo-backup runs forgejo dump + pg_dump, age-encrypts, uploads to Nextcloud forgejo/ prefix. Includes dry-run, status, and operator docs. --- docs/forgejo-backup.md | 4 ++++ lib/railiance-print.sh | 8 ++++++++ tools/cmd/forgejo-backup | 23 +++++++++++++---------- 3 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 lib/railiance-print.sh diff --git a/docs/forgejo-backup.md b/docs/forgejo-backup.md index 2660879..04b9da8 100644 --- a/docs/forgejo-backup.md +++ b/docs/forgejo-backup.md @@ -22,6 +22,10 @@ make forgejo-backup-status # last success + 7-day gate hint 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 10–20 minutes for stream + age on a typical +workstation link. + Decrypt: `~/.config/age/railiance-backup.key` (same key as other Railiance backups). ## Nextcloud layout diff --git a/lib/railiance-print.sh b/lib/railiance-print.sh new file mode 100644 index 0000000..52bccd5 --- /dev/null +++ b/lib/railiance-print.sh @@ -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"; } \ No newline at end of file diff --git a/tools/cmd/forgejo-backup b/tools/cmd/forgejo-backup index f5bc0a6..17b2a65 100755 --- a/tools/cmd/forgejo-backup +++ b/tools/cmd/forgejo-backup @@ -51,6 +51,12 @@ if [[ -z "${FORGEJO_DB_POD}" ]]; then exit 1 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}" \ -l "app.kubernetes.io/instance=${FORGEJO_RELEASE}" \ -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) ok "forgejo dump" "running in pod…" 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 -- \ - forgejo dump -f "${DUMP_REMOTE}" -# cat stream avoids kubectl cp websocket drops on larger archives -kubectl exec -n "${FORGEJO_NAMESPACE}" "${PROD_POD}" -c gitea -- \ - 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}")" + sh -c "forgejo dump -f '${DUMP_REMOTE}' && cat '${DUMP_REMOTE}' && rm -f '${DUMP_REMOTE}'" \ + > "${DUMP_PLAIN}" +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…" 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}" -ok "pg_dump" "$(du -h "${DB_PLAIN}" | awk '{print $1}') → $(basename "${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}")" # 3. Encrypt railiance_backup_encrypt "${DUMP_PLAIN}" "${DUMP_AGE}"