postgres backup script added
This commit is contained in:
parent
400488efb7
commit
2464e6fb1c
1 changed files with 111 additions and 0 deletions
111
scripts/backup_prop_db.sh
Executable file
111
scripts/backup_prop_db.sh
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Daily binary backup of the `prop` Postgres database.
|
||||
#
|
||||
# Designed to run from cron on the database server itself. pg_dump writes
|
||||
# to a local scratch path (fast NVMe) first so the DB connection isn't
|
||||
# throttled by SMB/NFS throughput, then the completed file is copied
|
||||
# over to /backups/prop (the network share) and atomically renamed into
|
||||
# place. Dumps older than RETENTION_DAYS are pruned.
|
||||
#
|
||||
# Install a crontab entry like:
|
||||
#
|
||||
# 0 3 * * * BACKUP_DIR=/backups/prop /opt/backup_prop_db.sh >> /var/log/prop-backup.log 2>&1
|
||||
#
|
||||
# Restore:
|
||||
#
|
||||
# pg_restore -d prop_restore -Fc /backups/prop/prop_20260417_030000.dump
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DB_NAME="${DB_NAME:-prop}"
|
||||
DB_USER="${DB_USER:-postgres}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-/backups}"
|
||||
LOCAL_STAGE_DIR="${LOCAL_STAGE_DIR:-/var/tmp/prop-backup}"
|
||||
RETENTION_DAYS="${RETENTION_DAYS:-7}"
|
||||
|
||||
timestamp=$(date -u +'%Y%m%d_%H%M%S')
|
||||
filename="${DB_NAME}_${timestamp}.dump"
|
||||
stage_file="${LOCAL_STAGE_DIR}/${filename}"
|
||||
dest_partial="${BACKUP_DIR}/${filename}.partial"
|
||||
dest_final="${BACKUP_DIR}/${filename}"
|
||||
|
||||
log() {
|
||||
printf '%s backup_prop_db: %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup_stage() {
|
||||
# Always remove the local scratch file, whether we succeeded or
|
||||
# crashed mid-run. Keeps /var/tmp from slowly filling up.
|
||||
[[ -n "${stage_file:-}" && -f "$stage_file" ]] && rm -f "$stage_file"
|
||||
}
|
||||
trap cleanup_stage EXIT
|
||||
|
||||
# --- sanity checks ---
|
||||
command -v pg_dump >/dev/null || die "pg_dump not on PATH"
|
||||
|
||||
# Verify BACKUP_DIR lives on a real network mount — not the root filesystem
|
||||
# (which would mean /backups is missing and we'd silently fill the OS disk).
|
||||
# `stat -c %m` returns the mount point of the filesystem holding the path.
|
||||
mount_root=$(stat -c '%m' "$BACKUP_DIR" 2>/dev/null || true)
|
||||
[[ -z "$mount_root" ]] && die "$BACKUP_DIR does not exist"
|
||||
[[ "$mount_root" == "/" ]] && die "$BACKUP_DIR is on the root filesystem — network share not mounted"
|
||||
[[ -w "$BACKUP_DIR" ]] || die "$BACKUP_DIR is not writable"
|
||||
|
||||
# Local staging area — must exist and be writable for pg_dump. Created
|
||||
# lazily so fresh installs don't need a separate provisioning step.
|
||||
mkdir -p "$LOCAL_STAGE_DIR"
|
||||
[[ -w "$LOCAL_STAGE_DIR" ]] || die "$LOCAL_STAGE_DIR is not writable"
|
||||
|
||||
# --- dump to local scratch ---
|
||||
log "dumping $DB_NAME -> $stage_file"
|
||||
# -Fc: custom format (binary, compressed, selective-restore friendly)
|
||||
# -Z 6: balanced compression level
|
||||
# --no-owner / --no-acl: restores cleanly into a target DB with different
|
||||
# role names, which is usually what you want for a DR restore.
|
||||
pg_dump \
|
||||
-U "$DB_USER" \
|
||||
-d "$DB_NAME" \
|
||||
-Fc \
|
||||
-Z 6 \
|
||||
--no-owner \
|
||||
--no-acl \
|
||||
-f "$stage_file"
|
||||
|
||||
size=$(stat -c '%s' "$stage_file" 2>/dev/null || stat -f '%z' "$stage_file")
|
||||
log "dump complete ($(numfmt --to=iec "$size" 2>/dev/null || echo "$size bytes")) — copying to share"
|
||||
|
||||
# --- move to network share ---
|
||||
# Cross-filesystem: cp then rename on the destination so readers never see
|
||||
# a half-written *.dump. We can't use `mv` alone because its temp-file
|
||||
# behavior across filesystems is GNU-specific, and some NFS/SMB clients
|
||||
# don't preserve atomicity.
|
||||
cp "$stage_file" "$dest_partial"
|
||||
sync "$dest_partial" 2>/dev/null || true
|
||||
mv "$dest_partial" "$dest_final"
|
||||
log "wrote $dest_final"
|
||||
|
||||
# --- prune ---
|
||||
log "pruning dumps older than ${RETENTION_DAYS} days"
|
||||
find "$BACKUP_DIR" \
|
||||
-maxdepth 1 \
|
||||
-type f \
|
||||
-name "${DB_NAME}_*.dump" \
|
||||
-mtime +"${RETENTION_DAYS}" \
|
||||
-print -delete
|
||||
|
||||
# Also clear any half-written destination files from a previous crashed
|
||||
# run so they don't accumulate.
|
||||
find "$BACKUP_DIR" \
|
||||
-maxdepth 1 \
|
||||
-type f \
|
||||
-name "${DB_NAME}_*.dump.partial" \
|
||||
-mtime +1 \
|
||||
-print -delete
|
||||
|
||||
log "done"
|
||||
Loading…
Add table
Reference in a new issue