#!/usr/bin/env bash # ───────────────────────────────────────────── # SPB Drive — Personal Cloud Drive # ───────────────────────────────────────────── # Author : Simon-Pierre Boucher # Contact : contact@spboucher.ai # File : deploy/backup.sh # Purpose : Nightly backup — sqlite snapshot + hardlink-incremental blobs; # retention 14 dailies + 8 weeklies; verify + log # License : MIT © Simon-Pierre Boucher # ───────────────────────────────────────────── # # Restore procedure: see README.md § Backups & restore. set -euo pipefail DATA_DIR="$HOME/srv/drive" BACKUP_ROOT="$DATA_DIR/backups" TODAY="$(date +%Y-%m-%d)" DEST="$BACKUP_ROOT/$TODAY" LAST="$(ls -1d "$BACKUP_ROOT"/20* 2>/dev/null | tail -1 || true)" log() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } mkdir -p "$DEST" # 1 ── SQLite online snapshot (safe under WAL). log "sqlite backup → $DEST/drive.sqlite" sqlite3 "$DATA_DIR/db/drive.sqlite" ".backup '$DEST/drive.sqlite'" # 2 ── Blob store: rsync with hardlinks against the previous backup — # unchanged blobs cost zero extra bytes (content-addressed, immutable). log "files rsync (link-dest: ${LAST:-none})" if [ -n "$LAST" ] && [ -d "$LAST/files" ] && [ "$LAST" != "$DEST" ]; then rsync -a --delete --link-dest="$LAST/files" "$DATA_DIR/files/" "$DEST/files/" else rsync -a --delete "$DATA_DIR/files/" "$DEST/files/" fi # 3 ── Auth + keys (chmod 600 preserved). cp -p "$DATA_DIR/auth.json" "$DATA_DIR/keys.json" "$DEST/" 2>/dev/null || true # 4 ── Verify: DB integrity + blob count matches. log "verifying" sqlite3 "$DEST/drive.sqlite" 'PRAGMA integrity_check;' | grep -q '^ok$' || { log "✗ DB integrity failed"; exit 1; } SRC_COUNT=$(find "$DATA_DIR/files" -type f | wc -l | tr -d ' ') DST_COUNT=$(find "$DEST/files" -type f | wc -l | tr -d ' ') if [ "$SRC_COUNT" != "$DST_COUNT" ]; then log "✗ blob count mismatch (src=$SRC_COUNT dst=$DST_COUNT)"; exit 1 fi # 5 ── Retention: keep 14 dailies; keep Sunday backups 8 weeks. log "pruning old backups" for dir in "$BACKUP_ROOT"/20*; do [ -d "$dir" ] || continue day="$(basename "$dir")" age_days=$(( ( $(date +%s) - $(date -j -f %Y-%m-%d "$day" +%s 2>/dev/null || date -d "$day" +%s) ) / 86400 )) weekday=$(date -j -f %Y-%m-%d "$day" +%u 2>/dev/null || date -d "$day" +%u) if [ "$age_days" -gt 56 ]; then rm -rf "$dir"; log "pruned $day (older than 8 weeks)" elif [ "$age_days" -gt 14 ] && [ "$weekday" != 7 ]; then rm -rf "$dir"; log "pruned $day (daily beyond 14 days)" fi done log "✓ backup complete: $DEST ($SRC_COUNT blobs, $(du -sh "$DEST" | cut -f1))"