#!/usr/bin/env bash # # PYRE / Prometheus Protocol — PostgreSQL backup script. # # INERT until the `pyre` database exists and has data. Until then a run will # simply fail at pg_dump (no DB), which is harmless — nothing is deleted unless # pg_dump succeeds. # # What it does: # - dumps the `pyre` database (gzip) to /home/pyre/backups/ # - prunes backups older than 14 days # Idempotent and safe to run from cron. # # DATABASE_URL is read from the environment if set, otherwise defaults to the # local pyre DB. Do NOT hardcode real passwords here — for non-trivial passwords # prefer a ~/.pgpass file (chmod 600) so the URL can omit the password. # # Example crontab (run as the `pyre` user, daily at 03:30): # 30 3 * * * /home/pyre/pyre/scripts/backup.sh >> /home/pyre/pyre/logs/backup.log 2>&1 set -euo pipefail DATABASE_URL="${DATABASE_URL:-postgresql://pyre:pyre@localhost:5432/pyre}" BACKUP_DIR="${BACKUP_DIR:-/home/pyre/backups}" RETENTION_DAYS="${RETENTION_DAYS:-14}" TIMESTAMP="$(date +%Y%m%d-%H%M%S)" OUTFILE="${BACKUP_DIR}/pyre-${TIMESTAMP}.sql.gz" mkdir -p "${BACKUP_DIR}" echo "[backup] $(date -Is) dumping database -> ${OUTFILE}" # --no-owner / --no-acl keeps the dump portable across roles when restoring. # Write to a temp file first, then atomically rename, so cron never prunes or # leaves behind a half-written archive. TMPFILE="${OUTFILE}.partial" pg_dump --no-owner --no-acl --dbname="${DATABASE_URL}" | gzip -c > "${TMPFILE}" mv "${TMPFILE}" "${OUTFILE}" echo "[backup] $(date -Is) wrote $(du -h "${OUTFILE}" | cut -f1) backup" # Prune backups older than RETENTION_DAYS (only PYRE dumps). echo "[backup] $(date -Is) pruning backups older than ${RETENTION_DAYS} days" find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'pyre-*.sql.gz' \ -mtime "+${RETENTION_DAYS}" -print -delete || true echo "[backup] $(date -Is) done"