WP-0013/0014: warden credential routing, CoulombCore kubeconfig, apps-pg backup dry-run
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 13s

Document OpenBao/warden paths for the offsite backup lane and flag the sealed
Vault blocker. Point production Makefile targets at CoulombCore, auto-discover
CNPG clusters in backup status, add apps-pg pg_dump dry-run tooling, and
record Core Hub Helm cutover findings (live gitea image, adopt required).
This commit is contained in:
tegwick 2026-07-10 15:46:02 +02:00
parent cd7e7035ce
commit a689270f18
7 changed files with 229 additions and 41 deletions

66
tools/apps-pg-backup-dry-run.sh Executable file
View file

@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Phase 1 logical backup for apps-pg consumer databases (Option A lane).
# Encrypts locally with age; Nextcloud upload requires OpenBao offsite-lane creds.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PLATFORM_REPO="${PLATFORM_REPO:-$HOME/railiance-platform}"
COMMON_SH="${PLATFORM_REPO}/lib/railiance-backup-common.sh"
APPS_PG_NAMESPACE="${APPS_PG_NAMESPACE:-databases}"
APPS_PG_CLUSTER="${APPS_PG_CLUSTER:-apps-pg}"
APPS_PG_POD="${APPS_PG_POD:-}"
APPS_PG_DATABASE="${APPS_PG_DATABASE:-vergabe_db}"
BACKUP_DIR="${BACKUP_DIR:-$HOME/.cache/railiance/backups/apps-pg}"
DRY_RUN="${RAILIANCE_BACKUP_DRY_RUN:-1}"
if [[ ! -f "$COMMON_SH" ]]; then
echo "ERROR: missing ${COMMON_SH}" >&2
exit 1
fi
# shellcheck source=/dev/null
source "$COMMON_SH"
railiance_backup_require_tools
mkdir -p "$BACKUP_DIR"
if [[ -z "$APPS_PG_POD" ]]; then
APPS_PG_POD="$(
kubectl get pods -n "$APPS_PG_NAMESPACE" \
-l "cnpg.io/cluster=${APPS_PG_CLUSTER},role=primary" \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
)"
fi
if [[ -z "$APPS_PG_POD" ]]; then
echo "ERROR: primary pod for ${APPS_PG_CLUSTER} not found in ${APPS_PG_NAMESPACE}" >&2
exit 1
fi
TS="$(date -u +%Y%m%dT%H%M%SZ)"
DB_PLAIN="${BACKUP_DIR}/apps-pg-${APPS_PG_DATABASE}-${TS}.dump"
DB_AGE="${DB_PLAIN}.age"
echo "apps-pg backup dry-run"
echo " cluster: ${APPS_PG_CLUSTER}"
echo " pod: ${APPS_PG_POD}"
echo " database:${APPS_PG_DATABASE}"
echo
kubectl exec -n "$APPS_PG_NAMESPACE" "$APPS_PG_POD" -c postgres -- \
pg_dump -U postgres -d "$APPS_PG_DATABASE" -Fc --no-owner --no-acl > "$DB_PLAIN"
echo "ok: pg_dump $(du -h "$DB_PLAIN" | awk '{print $1}')"
railiance_backup_encrypt "$DB_PLAIN" "$DB_AGE"
echo "ok: age encrypted $(basename "$DB_AGE")"
if [[ "$DRY_RUN" == "1" ]]; then
echo "ok: dry-run — skipping Nextcloud upload (OpenBao offsite-lane required)"
echo " Decrypt with ~/.config/age/railiance-backup.key"
exit 0
fi
railiance_backup_require_openbao_lane
RAILIANCE_BACKUP_NC_PREFIX="${RAILIANCE_BACKUP_NC_PREFIX:-apps-pg}"
railiance_backup_nc_upload "$DB_AGE" "apps-pg-${APPS_PG_DATABASE}-${TS}.dump.age"
railiance_backup_record_success "$BACKUP_DIR" "$TS"
echo "ok: uploaded apps-pg-${APPS_PG_DATABASE}-${TS}.dump.age"

View file

@ -2,12 +2,6 @@
set -euo pipefail
namespace="${CNPG_NAMESPACE:-databases}"
clusters=(
apps-pg
forgejo-db
net-kingdom-pg
state-hub-db
)
if ! kubectl get ns "$namespace" >/dev/null 2>&1; then
echo "ERROR: namespace $namespace is not reachable from the current kubeconfig" >&2
@ -15,9 +9,23 @@ if ! kubectl get ns "$namespace" >/dev/null 2>&1; then
fi
echo "CNPG backup posture in namespace ${namespace}"
context="$(kubectl config current-context 2>/dev/null || echo unknown)"
server="$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || echo unknown)"
echo "kube-context: ${context}"
echo "api-server: ${server}"
echo
mapfile -t clusters < <(
kubectl get cluster -n "$namespace" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null \
| sort
)
if ((${#clusters[@]} == 0)); then
echo "ERROR: no CNPG Cluster resources in ${namespace}" >&2
exit 1
fi
missing=0
logical_backup_note=0
for cluster in "${clusters[@]}"; do
if ! kubectl get cluster "$cluster" -n "$namespace" >/dev/null 2>&1; then
@ -41,6 +49,10 @@ for cluster in "${clusters[@]}"; do
if [[ -z "$scheduled" ]]; then
echo "${cluster}: ScheduledBackup = none"
missing=$((missing + 1))
if [[ "$cluster" == "apps-pg" || "$cluster" == "gitea-db" || "$cluster" == "forgejo-db" ]]; then
echo "${cluster}: Phase 1 fallback = logical pg_dump (see make apps-pg-backup-dry-run or platform forgejo-backup)"
logical_backup_note=1
fi
else
echo "${cluster}: ScheduledBackup = ${scheduled}"
fi
@ -60,6 +72,10 @@ done
if (( missing > 0 )); then
echo "RESULT: degraded — ${missing} cluster(s) lack ScheduledBackup coverage"
echo "See docs/app-data-backup-restore-handoff.md and manifests/cnpg-backup-readiness.yaml"
if (( logical_backup_note )); then
echo "Phase 1: logical pg_dump lane may still run when OpenBao offsite-lane is reachable"
echo "See docs/credential-routing-railiance-apps.md"
fi
exit 1
fi