Add Postgres backup script
scripts/backup.sh runs pg_dump inside the ecosplay-auth-db container, writes a gzipped, timestamped dump to /backup/ (overridable via BACKUP_DIR), and keeps the latest N dumps (RETENTION=14 by default). Only the Postgres volume carries state that isn't reproducible from code (users, credentials, TOTP secrets, sessions, brute- force counters), so the rest of the repo is not bundled here. Intended as a base that can later feed a cron job or be piped to off-site storage (S3, SFTP, borg). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
82
scripts/backup.sh
Executable file
82
scripts/backup.sh
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================
|
||||
# E-Cosplay Keycloak backup
|
||||
# -------------------------------------------------------------
|
||||
# Dumps the Keycloak Postgres database to /backup/ as a
|
||||
# timestamped, gzip-compressed SQL file, then rotates old
|
||||
# dumps (keeps the latest N).
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/backup.sh # default settings
|
||||
# BACKUP_DIR=/mnt/nas RETENTION=30 \
|
||||
# ./scripts/backup.sh # override via env
|
||||
#
|
||||
# Suggested cron (daily at 03:15):
|
||||
# 15 3 * * * /var/www/e-auth/scripts/backup.sh >>/var/log/e-auth-backup.log 2>&1
|
||||
#
|
||||
# Note: the repo itself (compose file, realm JSON, themes,
|
||||
# Ansible, sync.sh, etc.) lives in git and does not need to
|
||||
# be backed up here. Only the Postgres volume carries state
|
||||
# that is not reproducible from code (users, credentials,
|
||||
# TOTP secrets, sessions, brute-force counters, ...).
|
||||
# =============================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_DIR=${BACKUP_DIR:-/backup}
|
||||
RETENTION=${RETENTION:-14}
|
||||
PG_CONTAINER=${PG_CONTAINER:-ecosplay-auth-db}
|
||||
PG_USER=${PG_USER:-keycloak}
|
||||
PG_DB=${PG_DB:-keycloak}
|
||||
|
||||
TS=$(date +%F_%H-%M-%S)
|
||||
OUT="${BACKUP_DIR}/keycloak-${TS}.sql.gz"
|
||||
|
||||
log() { printf '[backup %s] %s\n' "$(date +%T)" "$*"; }
|
||||
fail() { printf '[backup %s] ERROR: %s\n' "$(date +%T)" "$*" >&2; exit 1; }
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Pre-flight checks
|
||||
# -------------------------------------------------------------
|
||||
command -v docker >/dev/null || fail "docker not found in PATH"
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -qx "${PG_CONTAINER}"; then
|
||||
fail "container '${PG_CONTAINER}' is not running"
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Dump
|
||||
# -------------------------------------------------------------
|
||||
log "Preparing ${BACKUP_DIR}"
|
||||
mkdir -p "${BACKUP_DIR}"
|
||||
chmod 700 "${BACKUP_DIR}"
|
||||
|
||||
log "Dumping database '${PG_DB}' from container '${PG_CONTAINER}'"
|
||||
if ! docker exec "${PG_CONTAINER}" \
|
||||
pg_dump -U "${PG_USER}" -d "${PG_DB}" \
|
||||
--no-owner --clean --if-exists \
|
||||
| gzip -9 > "${OUT}.part"; then
|
||||
rm -f "${OUT}.part"
|
||||
fail "pg_dump failed"
|
||||
fi
|
||||
|
||||
mv "${OUT}.part" "${OUT}"
|
||||
chmod 600 "${OUT}"
|
||||
|
||||
SIZE=$(du -h "${OUT}" | cut -f1)
|
||||
log "Wrote ${OUT} (${SIZE})"
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Rotate: keep only the latest ${RETENTION} backups
|
||||
# -------------------------------------------------------------
|
||||
log "Pruning backups older than the last ${RETENTION}"
|
||||
cd "${BACKUP_DIR}"
|
||||
# shellcheck disable=SC2010
|
||||
ls -1t keycloak-*.sql.gz 2>/dev/null \
|
||||
| tail -n +"$((RETENTION + 1))" \
|
||||
| while read -r old; do
|
||||
log " removing ${old}"
|
||||
rm -f "${old}"
|
||||
done
|
||||
|
||||
log "Done."
|
||||
Reference in New Issue
Block a user