Files
esh-pfi-infrastructure/configs/restic/esh-docker-vm/pre-backup.sh
T
vh 25e41d2ab5 fix(restic/esh-docker-vm): drop the uptime-kuma hook block that aborted every backup
Uptime Kuma moved from esh-docker-vm to ana-docker on 2026-09-22. The
pre-backup hook's fallback lookup, `docker ps | grep -E "uptime.kuma"`,
then matched nothing and exited 1. Under set -euo pipefail that aborted the
hook, and resticprofile treats a failed run-before as fatal, so no snapshot
was taken from 2026-09-22 01:00 until this fix (backup-freshness: 54h stale).

The block is removed rather than guarded because there is nothing on this
host left for it to back up. The header now records the invariant the
"blocks only WARN" promise depends on: every optional-service lookup must sit
inside an `if` test or end in `|| true`. The remaining blocks were checked
and all do.

Deployed with playbooks/esh-docker-vm-restic-drop-kuma-block.yaml (the
pre-fix hook is kept in /var/lib/restic/repair-20260923/). The live hook
hash matches the canonical copy (43e6bea8b8569602). The manual backup saved
snapshot 6ec9f74f, and backup-freshness now reports all backups fresh.
2026-09-23 08:05:47 -07:00

126 lines
5.1 KiB
Bash

#!/bin/bash
# pre-backup.sh — esh-docker-vm.
# Runs as root from resticprofile's `run-before`.
#
# Produces consistent DB dumps in /var/lib/restic/stage/ for services
# whose raw volume files risk inconsistency during live restic capture.
#
# Unique approach for this host: most containers don't bundle sqlite3,
# so we run sqlite3 and pg_dump from the HOST against the volume
# bind-mount paths. Requires sqlite3 + postgresql-client installed
# on esh-docker-vm (apt install sqlite3 postgresql-client).
#
# Services handled:
# - paperless-ngx (external Postgres on 10.0.50.60 — pg_dump from host)
# - home-assistant (local SQLite in volume — sqlite3 .backup from host)
# - calibre-web-automated (local SQLite — sqlite3 .backup inside container, has sqlite3)
# - pgadmin (local SQLite in volume — sqlite3 .backup from host)
#
# uptime-kuma was here until it moved to ana-docker (2026-09-22). Its block
# was removed 2026-09-23: with no container left to match, its unguarded
# `docker ps | grep` lookup exited 1 and set -e aborted this script, and
# resticprofile then skipped the WHOLE host backup (stale 09-22 → 09-23).
#
# External DB credentials live in /etc/restic/dbcreds.env (root:600).
# Template: configs/restic/esh-docker-vm/dbcreds.env.example.
#
# Errors in individual blocks log a WARN; whole script doesn't abort.
# That only holds if every lookup for an optional service sits inside an
# `if` test or ends in `|| true` — under set -euo pipefail a bare
# `x=$(… | grep …)` that matches nothing kills the script, and a failed
# run-before hook means NO snapshot at all, not a partial one.
set -euo pipefail
STAGE=/var/lib/restic/stage
install -d -o root -g root -m 0700 "$STAGE"
log() { printf '%s pre-backup(esh-docker-vm): %s\n' "$(date -Is)" "$*"; }
warn() { log "WARN: $*" >&2; }
# Purge previous stage so stale dumps don't pile up in the snapshot.
find "$STAGE" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
# Load external-DB creds
if [ -r /etc/restic/dbcreds.env ]; then
set -a; . /etc/restic/dbcreds.env; set +a
fi
# Helper: host-side sqlite .backup against a volume-bind path.
# $1 = source .db path (host absolute, typically under /var/lib/docker/volumes/.../_data/)
# $2 = stage filename (just the leaf name)
host_sqlite_backup() {
local src="$1" dst="$STAGE/$2"
if ! command -v sqlite3 >/dev/null 2>&1; then
warn "sqlite3 not on host (apt install sqlite3) — skipping $2"
return 1
fi
if [ ! -f "$src" ]; then
warn "source db missing: $src — skipping $2"
return 1
fi
if sqlite3 "$src" ".backup '$dst'" 2>/dev/null; then
log "dumped $2 ($(du -h "$dst" 2>/dev/null | cut -f1))"
else
warn "sqlite3 .backup failed for $src"
rm -f "$dst"
return 1
fi
}
# ---------- paperless-ngx (external Postgres on 10.0.50.60) -------------------
if docker inspect paperless-ngx-webserver-1 >/dev/null 2>&1; then
if [ -z "${PAPERLESS_PGPASS:-}" ]; then
warn "paperless-ngx: PAPERLESS_PGPASS unset in /etc/restic/dbcreds.env — skipping"
elif ! command -v pg_dump >/dev/null 2>&1; then
warn "paperless-ngx: pg_dump not installed — apt install postgresql-client"
else
log "dumping paperless postgres (${PAPERLESS_PGHOST}:${PAPERLESS_PGPORT:-5432})"
PGPASSWORD="$PAPERLESS_PGPASS" pg_dump \
-h "$PAPERLESS_PGHOST" -p "${PAPERLESS_PGPORT:-5432}" \
-U "$PAPERLESS_PGUSER" -d "$PAPERLESS_PGDB" \
-Fc --clean --if-exists \
> "$STAGE/paperless.pg_dump" \
|| warn "paperless pg_dump failed"
fi
else
log "skip paperless: container not present"
fi
# ---------- home-assistant (SQLite in named volume, host-side .backup) --------
# HA's DB is ~50MB and actively written. SQLite .backup is the proper way
# to grab a consistent snapshot while HA is running.
if docker inspect homeassistant >/dev/null 2>&1; then
HA_DB="/var/lib/docker/volumes/homeassistant_homeassistant_data/_data/home-assistant_v2.db"
host_sqlite_backup "$HA_DB" "home-assistant.sqlite3" || true
else
log "skip home-assistant: container not present"
fi
# ---------- calibre-web-automated (SQLite, sqlite3 inside container) ---------
if docker inspect calibre-web-automated >/dev/null 2>&1; then
log "dumping CWA sqlite via in-container .backup"
if docker exec calibre-web-automated sqlite3 /config/app.db ".backup /tmp/cwa-app.db" 2>/dev/null; then
docker cp calibre-web-automated:/tmp/cwa-app.db "$STAGE/calibre-web-automated.app.db" \
&& docker exec calibre-web-automated rm -f /tmp/cwa-app.db \
|| warn "CWA copy/cleanup failed"
else
warn "CWA sqlite .backup failed"
fi
else
log "skip calibre-web-automated: container not present"
fi
# ---------- pgadmin (SQLite in named volume, host-side .backup) --------------
if docker inspect pgadmin4_container >/dev/null 2>&1; then
PG_DB="/var/lib/docker/volumes/pgadmin_pgadmin-data/_data/pgadmin4.db"
host_sqlite_backup "$PG_DB" "pgadmin4.db" || true
else
log "skip pgadmin: container not present"
fi
# ---------- summary -----------------------------------------------------------
size=$(du -sh "$STAGE" 2>/dev/null | awk '{print $1}')
count=$(find "$STAGE" -type f | wc -l)
log "stage ready: $count files, $size total"