Establish railiance backup credentials in OpenBao (CCR-2026-0004).
Add workload KV lane for Nextcloud WebDAV token, URL, and age recovery escrow at platform/workloads/railiance/backup/offsite-lane. Apply read policy and OIDC role railiance-backup-workload-kv-read; wire forgejo-backup to load credentials from OpenBao when env is unset.
This commit is contained in:
parent
f428940da8
commit
0055e8f3f7
8 changed files with 430 additions and 1 deletions
123
tools/cmd/forgejo-backup
Executable file
123
tools/cmd/forgejo-backup
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env bash
|
||||
# tools/cmd/forgejo-backup — Forgejo Option A backup (RAIL-HO-WP-0005 T04/T09)
|
||||
#
|
||||
# Daily: forgejo dump (blob state) + pg_dump (forgejo-db) → age → Nextcloud forgejo/
|
||||
# Retention: 14 daily + 4 weekly uploads (Sunday); local cache keep 7 per type.
|
||||
#
|
||||
# Env:
|
||||
# KUBECONFIG (default ~/.kube/config-hosteurope)
|
||||
# RAILIANCE_BACKUP_NC_TOKEN Nextcloud WebDAV token
|
||||
# RAILIANCE_BACKUP_DRY_RUN=1 skip upload and remote side effects
|
||||
set -euo pipefail
|
||||
|
||||
: "${RAILIANCE_BACKUP_NC_TOKEN:=MfTBEjcJTGS8Ywo}"
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
# shellcheck source=lib/railiance-print.sh
|
||||
source "${ROOT}/lib/railiance-print.sh"
|
||||
# shellcheck source=lib/railiance-backup-common.sh
|
||||
source "${ROOT}/lib/railiance-backup-common.sh"
|
||||
|
||||
KUBECONFIG="${KUBECONFIG:-${HOME}/.kube/config-hosteurope}"
|
||||
export KUBECONFIG
|
||||
DRY_RUN="${RAILIANCE_BACKUP_DRY_RUN:-0}"
|
||||
|
||||
FORGEJO_NAMESPACE="${FORGEJO_NAMESPACE:-forgejo}"
|
||||
FORGEJO_RELEASE="${FORGEJO_RELEASE:-forgejo}"
|
||||
FORGEJO_DB_NAMESPACE="${FORGEJO_DB_NAMESPACE:-databases}"
|
||||
FORGEJO_DB_CLUSTER="${FORGEJO_DB_CLUSTER:-forgejo-db}"
|
||||
FORGEJO_DB_POD="${FORGEJO_DB_POD:-$(kubectl get pods -n "${FORGEJO_DB_NAMESPACE}" \
|
||||
-l "cnpg.io/cluster=${FORGEJO_DB_CLUSTER}" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)}"
|
||||
|
||||
BACKUP_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/railiance/backups/forgejo"
|
||||
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
DOW="$(date -u +%u)" # 7 = Sunday
|
||||
|
||||
DUMP_PLAIN="${BACKUP_DIR}/forgejo-dump-${TS}.zip"
|
||||
DUMP_AGE="${DUMP_PLAIN}.age"
|
||||
DB_PLAIN="${BACKUP_DIR}/forgejo-db-${TS}.sql"
|
||||
DB_AGE="${DB_PLAIN}.age"
|
||||
|
||||
cleanup_plain() {
|
||||
rm -f "${DUMP_PLAIN}" "${DB_PLAIN}"
|
||||
}
|
||||
trap cleanup_plain EXIT
|
||||
|
||||
railiance_backup_require_tools
|
||||
railiance_backup_require_openbao_lane
|
||||
|
||||
if [[ -z "${FORGEJO_DB_POD}" ]]; then
|
||||
bad "preflight" "forgejo-db pod not found (cluster ${FORGEJO_DB_CLUSTER})"
|
||||
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)"
|
||||
if [[ -z "${PROD_POD}" ]]; then
|
||||
bad "preflight" "Forgejo app pod not found in ${FORGEJO_NAMESPACE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${BACKUP_DIR}"
|
||||
print_hdr "Forgejo backup — ${TS}"
|
||||
ok "kubeconfig" "${KUBECONFIG}"
|
||||
ok "forgejo pod" "${FORGEJO_NAMESPACE}/${PROD_POD}"
|
||||
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"
|
||||
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}")"
|
||||
|
||||
# 2. PostgreSQL logical dump (stdout; CNPG data dir is read-only for ad-hoc paths)
|
||||
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}")"
|
||||
|
||||
# 3. Encrypt
|
||||
railiance_backup_encrypt "${DUMP_PLAIN}" "${DUMP_AGE}"
|
||||
railiance_backup_encrypt "${DB_PLAIN}" "${DB_AGE}"
|
||||
ok "age" "encrypted dump + db"
|
||||
|
||||
if [[ "${DRY_RUN}" == "1" ]]; then
|
||||
warn "dry-run" "skipping Nextcloud upload"
|
||||
else
|
||||
railiance_backup_nc_upload "${DUMP_AGE}" "forgejo-dump-${TS}.zip.age"
|
||||
ok "upload" "forgejo-dump-${TS}.zip.age"
|
||||
railiance_backup_nc_upload "${DB_AGE}" "forgejo-db-${TS}.sql.age"
|
||||
ok "upload" "forgejo-db-${TS}.sql.age"
|
||||
|
||||
if [[ "${DOW}" == "7" ]]; then
|
||||
railiance_backup_nc_upload "${DUMP_AGE}" "forgejo-dump-weekly-${TS}.zip.age"
|
||||
railiance_backup_nc_upload "${DB_AGE}" "forgejo-db-weekly-${TS}.sql.age"
|
||||
ok "weekly" "Sunday copies uploaded"
|
||||
fi
|
||||
|
||||
railiance_backup_record_success "${BACKUP_DIR}" "${TS}"
|
||||
fi
|
||||
|
||||
# 4. Local cache prune (keep 7 per type)
|
||||
railiance_backup_prune_local "${BACKUP_DIR}" 'forgejo-dump-*.zip.age' 7
|
||||
railiance_backup_prune_local "${BACKUP_DIR}" 'forgejo-db-*.sql.age' 7
|
||||
railiance_backup_prune_local "${BACKUP_DIR}" 'forgejo-dump-*.zip' 3
|
||||
railiance_backup_prune_local "${BACKUP_DIR}" 'forgejo-db-*.sql' 3
|
||||
ok "cache" "local copies pruned"
|
||||
|
||||
echo
|
||||
ok "done" "Forgejo backup complete — ${TS}"
|
||||
if [[ "${DRY_RUN}" != "1" ]]; then
|
||||
echo " Dump: ${RAILIANCE_BACKUP_NC_PREFIX}/forgejo-dump-${TS}.zip.age"
|
||||
echo " DB: ${RAILIANCE_BACKUP_NC_PREFIX}/forgejo-db-${TS}.sql.age"
|
||||
echo
|
||||
echo " Decrypt with ~/.config/age/railiance-backup.key"
|
||||
echo " Restore drill: railiance-infra tools/forgejo-restore-drill.sh"
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue