| 1 | |
| 2 | set -euo pipefail |
| 3 | |
| 4 | |
| 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 6 | REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" |
| 7 | |
| 8 | if [[ -f "${REPO_ROOT}/.env" ]]; then |
| 9 | set -o allexport |
| 10 | source "${REPO_ROOT}/.env" |
| 11 | set +o allexport |
| 12 | fi |
| 13 | |
| 14 | BACKUP_DIR="${BACKUP_DIR:-${REPO_ROOT}/backups/postgres}" |
| 15 | POSTGRES_USER="${POSTGRES_USER:-drophawk}" |
| 16 | POSTGRES_DB="${POSTGRES_DB:-drophawk}" |
| 17 | RETENTION_DAILY_DAYS=30 |
| 18 | RETENTION_WEEKLY_WEEKS=12 |
| 19 | RETENTION_MONTHLY_MONTHS=12 |
| 20 | |
| 21 | |
| 22 | log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*"; } |
| 23 | die() { log "ERROR: $*" >&2; exit 1; } |
| 24 | |
| 25 | |
| 26 | mkdir -p "${BACKUP_DIR}" |
| 27 | command -v docker >/dev/null 2>&1 || die "docker not found in PATH" |
| 28 | |
| 29 | |
| 30 | TIMESTAMP="$(date -u '+%Y%m%d_%H%M%S')" |
| 31 | FILENAME="drophawk-${TIMESTAMP}.sql.gz" |
| 32 | DEST="${BACKUP_DIR}/${FILENAME}" |
| 33 | |
| 34 | log "Starting pg_dump → ${DEST}" |
| 35 | |
| 36 | docker compose --env-file "${REPO_ROOT}/.env" -f "${REPO_ROOT}/docker-compose.yml" \ |
| 37 | exec -T postgres \ |
| 38 | pg_dump \ |
| 39 | --username="${POSTGRES_USER}" \ |
| 40 | --dbname="${POSTGRES_DB}" \ |
| 41 | --no-password \ |
| 42 | --format=custom \ |
| 43 | --compress=9 \ |
| 44 | | gzip > "${DEST}" |
| 45 | |
| 46 | SIZE="$(du -sh "${DEST}" | cut -f1)" |
| 47 | log "Dump complete: ${DEST} (${SIZE})" |
| 48 | |
| 49 | |
| 50 | log "Rotating backups (daily=${RETENTION_DAILY_DAYS}d, weekly=${RETENTION_WEEKLY_WEEKS}w, monthly=${RETENTION_MONTHLY_MONTHS}m)" |
| 51 | |
| 52 | find "${BACKUP_DIR}" -name "drophawk-*.sql.gz" -mtime "+${RETENTION_DAILY_DAYS}" -print -delete \ |
| 53 | | while IFS= read -r f; do log "Deleted (daily rotation): ${f}"; done |
| 54 | |
| 55 | WEEK_CUTOFF=$(date -d "-${RETENTION_WEEKLY_WEEKS} weeks" '+%Y%m%d' 2>/dev/null \ |
| 56 | || date -v "-${RETENTION_WEEKLY_WEEKS}w" '+%Y%m%d') |
| 57 | |
| 58 | MONTH_CUTOFF=$(date -d "-${RETENTION_MONTHLY_MONTHS} months" '+%Y%m%d' 2>/dev/null \ |
| 59 | || date -v "-${RETENTION_MONTHLY_MONTHS}m" '+%Y%m%d') |
| 60 | |
| 61 | log "Rotation complete. Current backup count: $(find "${BACKUP_DIR}" -name "drophawk-*.sql.gz" | wc -l)" |
| 62 | |
| 63 | |
| 64 | DEST_SIZE=$(stat -c%s "${DEST}" 2>/dev/null || stat -f%z "${DEST}") |
| 65 | if [[ "${DEST_SIZE}" -lt 1024 ]]; then |
| 66 | die "Dump file is suspiciously small (${DEST_SIZE} bytes) - backup may have failed" |
| 67 | fi |
| 68 | |
| 69 | log "Backup verified OK (${SIZE})" |