74 lines
2.5 KiB
Bash
74 lines
2.5 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Nightly backup for the key register.
|
|
#
|
|
# SQLite's .backup makes a consistent copy while the app is running — do NOT
|
|
# just cp the .db file, because the WAL may hold recent writes that a plain
|
|
# copy would miss.
|
|
#
|
|
# Install:
|
|
# chmod +x backup.sh
|
|
# sudo crontab -e
|
|
# 20 3 * * * /opt/keyreg/backup.sh >> /var/log/keyreg-backup.log 2>&1
|
|
|
|
set -Eeuo pipefail
|
|
|
|
APP_DIR="/opt/keyreg"
|
|
BACKUP_DIR="/mnt/backup/keyreg" # MUST be a different physical disk
|
|
OFFSITE_DEST="" # e.g. "backup@nas2:/volume1/keyreg"
|
|
KEEP_DAYS=60
|
|
|
|
STAMP="$(date +%Y-%m-%d_%H%M)"
|
|
OUT="${BACKUP_DIR}/keyreg-${STAMP}.db"
|
|
|
|
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*"; }
|
|
trap 'log "FAILED at line $LINENO — backup NOT complete"; exit 1' ERR
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
cd "$APP_DIR"
|
|
|
|
# 1. Consistent snapshot
|
|
log "Backing up database..."
|
|
docker compose exec -T keyreg \
|
|
sqlite3 /data/keyreg.db ".backup '/data/_tmp_backup.db'"
|
|
docker compose cp keyreg:/data/_tmp_backup.db "$OUT"
|
|
docker compose exec -T keyreg rm -f /data/_tmp_backup.db
|
|
|
|
# 2. Verify — an unreadable backup is not a backup
|
|
sqlite3 "$OUT" "PRAGMA integrity_check;" | grep -q '^ok$' \
|
|
|| { log "Integrity check FAILED"; exit 1; }
|
|
ITEMS=$(sqlite3 "$OUT" "SELECT COUNT(*) FROM items;")
|
|
[ "$ITEMS" -gt 0 ] || { log "Backup has zero keys — refusing to call this good"; exit 1; }
|
|
gzip -9 "$OUT"
|
|
log "OK — ${ITEMS} keys, $(stat -c %s "${OUT}.gz") bytes"
|
|
|
|
# 3. A readable copy too. If the app is ever gone, a CSV still tells you
|
|
# who holds which key — which is the whole point of the register.
|
|
curl -fsS "http://127.0.0.1:3000/api/admin/export.csv" \
|
|
-o "${BACKUP_DIR}/keyreg-${STAMP}.csv" || log "WARNING: CSV export failed"
|
|
|
|
# 4. Off site — survives fire and theft, which is when you most need to know
|
|
# who has keys to the building
|
|
if [ -n "$OFFSITE_DEST" ]; then
|
|
rsync -az --delete "${BACKUP_DIR}/" "${OFFSITE_DEST}/"
|
|
log "Off-site sync done"
|
|
else
|
|
log "WARNING: OFFSITE_DEST empty — local copy only"
|
|
fi
|
|
|
|
# 5. Retention
|
|
find "$BACKUP_DIR" -maxdepth 1 -type f -mtime "+${KEEP_DAYS}" -delete
|
|
log "Complete"
|
|
|
|
# ===========================================================================
|
|
# RESTORE — test this once now, not on the day you need it
|
|
#
|
|
# cd /opt/keyreg
|
|
# docker compose down
|
|
# gunzip -c /mnt/backup/keyreg/keyreg-DATE.db.gz > ./data/keyreg.db
|
|
# rm -f ./data/keyreg.db-wal ./data/keyreg.db-shm
|
|
# docker compose up -d
|
|
#
|
|
# That is the whole restore. One file.
|
|
# ===========================================================================
|