feat(milestone): merge phase/01 operator-foundation → milestone/v0.4-operator-tier
Phase 1 complete — Operator Foundation: - Postgres 16 in Docker-in-LXC (asyncpg pool, 5-table schema, PgStore, migrations) - Operator auth (argon2id, signed stateless cookies, slowapi 5/min rate limit) - VC issuer key migration SQLite→Postgres (archive-before-active, R-VC-MIG-01) - Operator bootstrap CLI (create-operator.py, idempotent) - Backup cron script + G-008 restore drill - Graceful degradation (server starts without Postgres) - 272 tests pass, 33 skip (Postgres-requiring), 0 fail ---ci--- project: praxis phase: 1 milestone: v0.4 status: complete requirements: covered: [REQ-MT-01, REQ-AUTH-01, REQ-NFR-AUTH-01, REQ-NFR-MT-01, REQ-MT-02] partial: [] ---/ci---
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user