b95802efa4
Adds the rsync --link-dest hourly snapshot job (nh3-dev:~/development -> nh3-nas, 48-snapshot retention, secrets/build-dirs excluded) that closes the no-off-box-backup gap exposed by the 2026-07-12 working-dir clobber. Script mirrors the live ~/.config/dev-backup/dev-backup.sh; runbook covers restore.
47 lines
2.1 KiB
Bash
Executable File
47 lines
2.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Hourly off-box snapshot of ~/development -> nh3-nas via rsync --link-dest
|
|
# hardlink snapshots. Penance for the 2026-07-12 soong-lab clobber: uncommitted
|
|
# dev work now has an hourly, versioned, off-box safety net. Secrets + heavy
|
|
# reconstructable dirs are excluded. Snapshots are timestamped dirs on the NAS;
|
|
# unchanged files hardlink to the previous snapshot (space-efficient). Retention:
|
|
# newest 48 hourly snapshots.
|
|
set -uo pipefail
|
|
|
|
SRC="$HOME/development/"
|
|
DEST_HOST="nh3-nas"
|
|
DEST_BASE="/volume1/Backup/nh3-dev-development"
|
|
STAMP="$(date +%Y-%m-%d_%H%M)"
|
|
LOG="$HOME/.config/dev-backup/dev-backup.log"
|
|
|
|
exec >>"$LOG" 2>&1
|
|
echo "=== $(date -Is) snapshot $STAMP start ==="
|
|
|
|
# previous snapshot for hardlink dedup
|
|
PREV="$(ssh -o ConnectTimeout=15 -o BatchMode=yes "$DEST_HOST" "ls -1d $DEST_BASE/20* 2>/dev/null | sort | tail -1" || true)"
|
|
LINKDEST=()
|
|
[ -n "$PREV" ] && LINKDEST=(--link-dest="$PREV")
|
|
echo "link-dest: ${PREV:-<none, first full snapshot>}"
|
|
|
|
ssh -o BatchMode=yes "$DEST_HOST" "mkdir -p '$DEST_BASE/$STAMP'"
|
|
|
|
rsync -a --delete --numeric-ids \
|
|
--exclude='node_modules/' --exclude='.venv/' --exclude='venv/' --exclude='__pycache__/' \
|
|
--exclude='.pytest_cache/' --exclude='.mypy_cache/' --exclude='.ruff_cache/' --exclude='.cache/' \
|
|
--exclude='dist/' --exclude='build/' --exclude='.next/' --exclude='target/' --exclude='*.pyc' \
|
|
--exclude='.env' --exclude='.env.*' --exclude='*.pem' --exclude='*.key' --exclude='id_*' \
|
|
--exclude='*.sqlite' --exclude='*.sqlite3' --exclude='*.db-wal' --exclude='*.db-shm' \
|
|
"${LINKDEST[@]}" \
|
|
"$SRC" "$DEST_HOST:$DEST_BASE/$STAMP/"
|
|
RC=$?
|
|
echo "rsync rc=$RC"
|
|
|
|
# rc 0 = ok; rc 24 = some files vanished mid-transfer (benign for a live tree)
|
|
if [ "$RC" -eq 0 ] || [ "$RC" -eq 24 ]; then
|
|
ssh -o BatchMode=yes "$DEST_HOST" "ln -sfn '$DEST_BASE/$STAMP' '$DEST_BASE/latest'"
|
|
# retention: keep newest 48 hourly snapshots
|
|
ssh -o BatchMode=yes "$DEST_HOST" "ls -1d $DEST_BASE/20* 2>/dev/null | sort | head -n -48 | xargs -r rm -rf"
|
|
echo "=== $(date -Is) snapshot $STAMP OK (rc=$RC) ==="
|
|
else
|
|
echo "=== $(date -Is) snapshot $STAMP FAILED rc=$RC — keeping partial for inspection ==="
|
|
fi
|