mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
chore(backup): env-gated off-disk mirror + restore drill doc (#645)
The pg_dump sidecar wrote its dumps to the same disk it protects — one disk failure lost both. Setting ROBOCO_BACKUP_MIRROR_DIR in .env to a path on a different disk (external/remote mount) arms a mirror step after every successful dump: tmp+rename copy, mirror pruned to the same BACKUP_KEEP, unwritable mirror logs-and-skips without blocking the primary. Unset, the script never attempts a copy — no fake off-disk copies on the same disk. Docs gain the mirror setup and a quarterly restore drill (throwaway pgvector container, pg_restore, row-count sanity check). Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,11 @@ set -u
|
||||
BACKUP_DIR="${BACKUP_DIR:-/backups}"
|
||||
KEEP="${BACKUP_KEEP:-14}"
|
||||
INTERVAL_SECONDS="${BACKUP_INTERVAL_SECONDS:-86400}"
|
||||
# Off-disk mirror: unset (the default) disables it. Compose sets this only
|
||||
# when ROBOCO_BACKUP_MIRROR_DIR is set in .env, and that host path should
|
||||
# live on a DIFFERENT disk (external/remote mount) — a same-disk mirror
|
||||
# protects nothing.
|
||||
MIRROR_DIR="${BACKUP_MIRROR_DIR:-}"
|
||||
|
||||
# pg_dump reads these natively — no need to pass -h/-p/-U/-d by hand.
|
||||
export PGHOST="${POSTGRES_HOST:-roboco-postgres}"
|
||||
@@ -19,14 +24,43 @@ export PGDATABASE="${POSTGRES_DB:-roboco}"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
mirror_backup() {
|
||||
# Copy a fresh dump to the off-disk mirror (tmp+rename, same crash safety
|
||||
# as the primary write) and prune the mirror to the same $KEEP. Best-effort:
|
||||
# an unmounted/unwritable mirror logs and skips — never blocks the primary.
|
||||
local dump="$1" name
|
||||
[ -n "$MIRROR_DIR" ] || return 0
|
||||
name="$(basename "$dump")"
|
||||
if ! mkdir -p "$MIRROR_DIR" 2>/dev/null || [ ! -w "$MIRROR_DIR" ]; then
|
||||
echo "[backup] $(date -u -Iseconds) mirror dir ${MIRROR_DIR} not writable — skipping" >&2
|
||||
return 0
|
||||
fi
|
||||
rm -f "${MIRROR_DIR}"/roboco-*.dump.tmp
|
||||
if cp "$dump" "${MIRROR_DIR}/${name}.tmp" && mv "${MIRROR_DIR}/${name}.tmp" "${MIRROR_DIR}/${name}"; then
|
||||
echo "[backup] $(date -u -Iseconds) mirrored: ${MIRROR_DIR}/${name}"
|
||||
else
|
||||
rm -f "${MIRROR_DIR}/${name}.tmp"
|
||||
echo "[backup] $(date -u -Iseconds) mirror FAILED — this dump stays unmirrored (each cycle mirrors only its own dump)" >&2
|
||||
return 0
|
||||
fi
|
||||
# shellcheck disable=SC2012
|
||||
ls -1t "${MIRROR_DIR}"/roboco-*.dump 2>/dev/null | tail -n "+$((KEEP + 1))" | while IFS= read -r old; do
|
||||
rm -f "$old"
|
||||
done
|
||||
}
|
||||
|
||||
run_backup() {
|
||||
local ts dest
|
||||
# Any .tmp here is a crash orphan (one dump at a time, unique names, and
|
||||
# the prune glob never matches them) — sweep before starting.
|
||||
rm -f "${BACKUP_DIR}"/roboco-*.dump.tmp
|
||||
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
dest="${BACKUP_DIR}/roboco-${ts}.dump"
|
||||
echo "[backup] $(date -u -Iseconds) starting pg_dump -> ${dest}"
|
||||
if pg_dump -Fc -f "${dest}.tmp"; then
|
||||
mv "${dest}.tmp" "${dest}"
|
||||
echo "[backup] $(date -u -Iseconds) OK: ${dest}"
|
||||
mirror_backup "${dest}"
|
||||
else
|
||||
rm -f "${dest}.tmp"
|
||||
echo "[backup] $(date -u -Iseconds) FAILED — will retry next cycle" >&2
|
||||
|
||||
Reference in New Issue
Block a user