Files
praxis/scripts/backup-pg.sh
T
Praxis CI 131545b70a feat(P01): SLICE-02 devops config + G-008 backup-restore drill
- TASK-02-01 .env.example: v0.4 operator vars (PRAXIS_PG_PASSWORD,
  PRAXIS_PG_DSN, PRAXIS_COOKIE_SECRET, PRAXIS_COOKIE_SECURE,
  PRAXIS_BOOTSTRAP_OPERATOR_USER/PASS, PRAXIS_VC_ISSUER_KEY,
  PRAXIS_ISSUER_URL) with documentation comments. PRAXIS_COOKIE_SECURE
  documents the G-031 reframe: k-anon defense-in-depth is the PRIMARY
  R-AUTH-01 mitigation (sniffed cookie leaks no PII); the secure flag is
  the SECONDARY mitigation. PROXMOX_MEMORY_MB default bumped 4096→6144.
- TASK-02-02 lxc-clone.sh: memory default 4096→6144 (REQ-NFR-MT-01 —
  Postgres ~400MB + praxis ~500MB + Docker ~200MB + build headroom ~1GB).
- TASK-02-03 scripts/backup-pg.sh: POSIX-sh nightly cron script,
  pg_dump -Fc to /backups/praxis-<dow>.dump (rolling 7-file, D-055),
  with restore-drill documentation in comments.
- G-008 tests/test_backup_restore.py: backup-restore drill — seeds all 5
  operator-tier tables, pg_dump, drop schema, pg_restore --clean --if-exists,
  verify 5 tables + row counts match. Skips if PRAXIS_PG_DSN unset.

---ci---
project: praxis
phase: 1
milestone: v0.4
status: execute
persona: devops-engineer
task: 02-01,02-02,02-03,G-008
requirements:
  covered: [REQ-NFR-MT-01]
  grill:
    - G-008 (backup-restore drill)
---/ci---
2026-08-04 00:48:14 +00:00

50 lines
2.0 KiB
Bash
Executable File

#!/bin/sh
# Praxis v0.4 — Nightly Postgres backup (D-055, G-008).
#
# Host-side cron script (decoupled from praxis service uptime —
# RESEARCH-v0.4 §1.5). Runs pg_dump inside the postgres container and
# writes a compressed custom-format dump to the pgbackups volume.
#
# The %u date format = day-of-week 1..7 (Monday=1, Sunday=7) → rolling
# 7-file retention with zero cleanup logic (D-055). Re-running overwrites
# the same day-of-week file.
#
# Cron entry (host, 03:30 CT nightly):
# 30 3 * * * /opt/praxis/scripts/backup-pg.sh
#
# Restore drill (G-008 — run at least once in staging to prove the backup
# is valid; NEVER restore into a live DB without stopping praxis first):
# docker compose stop praxis
# docker compose exec postgres pg_restore -U praxis -d praxis \
# --clean --if-exists /backups/praxis-3.dump
# # verify: \d operators; SELECT count(*) FROM operators; (etc. for all 5 tables)
# docker compose start praxis
#
# POSIX-sh compatible (no bashisms). Exit 0 on success, 1 on failure.
# Args: none. Env: COMPOSE_PROJECT_DIR (default: current dir).
set -eu
PROJECT_DIR="${COMPOSE_PROJECT_DIR:-$(pwd)}"
cd "$PROJECT_DIR"
DOW="$(date +%u)"
DUMP_FILE="/backups/praxis-${DOW}.dump"
echo "backup-pg: dumping praxis DB → ${DUMP_FILE} (day-of-week ${DOW})"
# -Fc = custom compressed format (works with pg_restore --clean --if-exists).
# -T stops the container from streaming while dumping? No — pg_dump is
# consistent within a transaction; the praxis service can stay up.
docker compose exec -T postgres pg_dump -U praxis -Fc praxis -f "$DUMP_FILE"
# Verify the dump is non-empty (sanity — a 0-byte dump means failure).
SIZE=$(docker compose exec -T postgres stat -c '%s' "$DUMP_FILE" 2>/dev/null || echo 0)
if [ "$SIZE" -le 0 ]; then
echo "backup-pg: ERROR — dump file is empty (${DUMP_FILE})" >&2
exit 1
fi
echo "backup-pg: OK — ${DUMP_FILE} is ${SIZE} bytes"
echo "backup-pg: restore drill (G-008): docker compose exec postgres pg_restore -U praxis -d praxis --clean --if-exists ${DUMP_FILE}"
exit 0