#!/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