Skip to content

history-backup — snapshot the irreplaceable data into the parity-protected repo every night

~/.local/bin/history-backup <mode>, as angel, no root. Nightly backup at 02:50 — after Immich's 02:00 dump and precious-sync's 02:20, before the 03:30 SnapRAID sync so parity covers the new packs the same night. Sunday 05:30 weekly: forget + prune, restic check --read-data-subset=2%, a restore drill that cmps one random file against the source, then the off-box copy if configured.

Input → Movement → Output

Reads the five immich/EmbeddedEra trees plus the out-of-library originals the DB names. Writes one tagged snapshot to the repo; applies the keep policy. Output: the snapshot, a log line backup ok: snapshot <id>, N files, +M MiB new data, last-backup.json, optional Kuma heartbeat.

Why this shape

Same guard-and-verify pattern as precious-sync: refuse rather than silently succeed. Guards (history-backup:guards): /mnt/storage is a mountpoint; both source trees exist; the repo exists; the repo is on mergerfs (or it is not parity-protected); no snapraid-sync/snapraid-scrub unit or process is running. Weekly drill exists because a backup never restored is a hypothesis.

Steps

  1. guards, then extra_originalsdocker exec immich-postgres psql … where "originalPath" not like '/data/library/%' (cached to extra-originals.list for a night the DB is down).
  2. restic backup --files-from-verbatim <list> --tag nightly --exclude '*.db-shm' --exclude '*.db-wal' --json; parse data_added, total_files_processed, snapshot_id.
  3. restic forget --keep-daily 30 --keep-weekly 12 --keep-monthly 24 --keep-yearly 5 (no prune nightly).
  4. Weekly: forget --prune (counts pack files before/after; ntfy if > 400 removed) → check --read-data-subset=2%drill (restore to /var/tmp/history-drill, cmp, clean up on PASS) → copy if HISTORY_OFFBOX_REPO is set.
  5. first (run once, 2026-09-08 16:35 under systemd-run --user --unit=history-initial): full backup → drill → report → ntfy. Result: 27 min, 90 G, 5,399 packs, drill PASS. A first attempt launched from a Claude tool shell was killed on purpose at 16:35:46 and produced one spurious "FAILED" ntfy — the reason for the rule below.

Never run it from a Claude tool shell in the background — the memory supervisor kills long children here. systemd-run --user or cron.

If you change this

  • Hits: the 02:50 slot must stay between the 02:00 dump and the 03:30 sync · retention changes take effect at the next weekly prune · adding a source tree changes repo size and the first-night duration.
  • Does not hit: rsnapshot, precious-sync, the array's own data.

Surfaces

Surface Role
history-backup status one-screen state
systemctl --user status history-initial the initial run, while it lasts
crontab -l (angel) the two lines; the duplicate 02:20 precious-sync line was removed the same day

See