FLEET BACKUP & DISASTER RECOVERY — Proposal (v2, post-critique)

For: Byron · Machine: Mia (Mac Mini) · Targets: NAS + immutable cloud + air-gapped drive · Threat model: ransomware (encryption and exfiltration/extortion), runaway process, disk failure, fire/theft


Executive summary

Where you are. A real backup system already exists (~/fleet-backup/): encrypted snapshots of every agent's config + workspace + crons, the OB1 Postgres dump, Docker image digest pins, secrets sealed in secrets.enc, 3-deep rotation, and a 15KB DR runbook. Good work — but it writes only to Mia's own disk, so any disk failure, theft, or ransomware destroys the backups with the machine. Each snapshot is only ~1.6 GB (the irreplaceable set is small; the 160 GB is re-downloadable Ollama models), which makes a much stronger posture cheap.

The proposal, and what a hard review changed. A 3-2-1-1-0 design — but built on three corrected principles the first draft got wrong:

  1. Assume the repository is readable by an attacker. restic is symmetric: to write hourly backups, the key is on Mia — so a compromise can read every secret you own. Backups defend integrity and availability, not confidentiality. The plan therefore centers a credential-rotation inventory + post-incident "rotate everything" procedure, and treats the real modern ransomware move (steal-then-encrypt) as in-scope.
  2. Nothing on Mia may be trusted to verify itself. Integrity checks, the ransomware canary, retention/prune, and alerting run in an independent trust domain (the NAS and a cloud dead-man's-switch), never as a process on the machine they're protecting. A compromised Mia going silent must itself trigger the alarm.
  3. "Offsite" means off-premises. The NAS is in the same room as Mia — fire/theft takes both. The true immutable offsite is cloud object storage with Object Lock (Backblaze B2 / S3 compliance mode) — a few GB of hot data for pennies a month, unerasable for a fixed window even by the account owner. The NAS and an air-gapped USB are the fast and offline layers around it.

Decision sought. Approve the approach; grant a least-privilege NAS repo path and a B2/S3 bucket with Object Lock. Then I migrate the existing local backup into an offsite, immutable, independently-verified system — reusing the runbook and encryption already built.

Target-state architecture

Backup architecture


1. What has to be saved — three tiers (measured)

Tier Contents Size Policy
1 · Irreplaceable Docker volumes (agent sessions/credentials/data/agent-state), OB1 Postgres dump, openclaw-gia/config (memory, skills, project source, data, .env), ~/.claude, gitea repos+config, ~/.ssh, all keys/tokens, LaunchDaemons/Agents plists, ~/.zprofile, Homebrew Brewfile ~1.6 GB (dedup ⇒ small increments once capture is consistent — see §5) Hourly, encrypted → NAS and cloud-immutable.
2 · Re-derivable, slow Avatar model envs (~9 GB), jlens 27B model, Python venvs ~15 GB Cold: snapshot once; documented re-fetch is the real recovery path.
3 · Re-downloadable ~/.ollama (160 GB), Homebrew binaries, node_modules, Docker images (digest-pinned) ~180 GB Never backed up — restore = re-run installers, ollama pull, docker pull @digest.

Routine backup moves single-digit GB, which is what makes hourly + offsite + immutable affordable.

2. Consistent capture (the fix for torn backups)

Hourly file-copies of live databases produce corrupt restores. So capture is quiesced, not naive:

3. Rotation & the honest RPO (GFS + 3-2-1-1-0)

4. Ransomware & runaway hardening (corrected)

  1. Append-only is a protocol mode, not an ACL. Achieved via restic rest-server --append-only on the NAS and Object Lock in the cloud — a compromised client can add but cannot rewrite/delete. (Borg is not offered — its append-only defers deletes into a log a later prune executes: a footgun here.)
  2. Confidentiality is handled by rotation, not by pretending the key is safe. Because the repo is readable if Mia falls, §6 is a rotate-everything inventory; the offsite copy's value is a clean restore point, and the assumption is all backed-up secrets are burned on compromise.
  3. Cloud Object Lock (B2/S3 compliance) — the real immutable offsite; unerasable for the lock window even with account credentials.
  4. Air-gapped USB — the only defense certain against a threat reaching every online system; offline except during a verified sync.
  5. NAS is itself a target (SynoLocker/eCh0raix): so — DSM 7.2+, firmware current, no QuickConnect / no exposed DSM / no SMB1, dedicated least-privilege backup account, 2FA on admin, immutable Snapshot Replication enabled. (I will not take DSM admin as a standing credential; provisioning is one-time, then that access is removed.)
  6. Independent verification + dead-man's switch. restic check, the canary evaluation, and prune run off Mia. Alerting uses an external dead-man's switch (e.g. healthchecks.io): the backup pings it on success; if the ping is missing — the exact thing a compromised, silent Mia produces — the switch alerts you. Mia is never trusted to report its own health.
  7. Runaway containment: retention by count/age (not size) so a disk-filling process can't evict real history; disk/rate alerts fire first.

5. Validation — safe, and actually meaningful

A · Continuous, zero production risk (run from the NAS/independent domain): restic check; restore the OB1 dump into a throwaway Postgres and run a real query (not just pg_restore --list, which a truncated dump passes); sqlite3 integrity_check on restored SQLite; decrypt-test secrets.enc. Proves restorability, touches nothing in production.

B · Deep rehearsal on isolated hardware — under three iron rules that make a live clone impossible:

  1. Air-gapped — no tailnet, LAN, or internet route; a restored fleet must not join as a second "Gia."
  2. Dry-run — message-sending and outbound API calls disabled.
  3. Neutered credentials — the clone runs with rotated/fake tokens so even a slipped isolation can't act as production. Prefer a bare-metal second Mac Mini (also tests the Homebrew/TCC/Colima bootstrap a nested VM skips). A rented cloud Mac means shipping every secret to third-party hardware — acceptable only with the neutered-credential rule, and noted that it then doesn't test the real secret chain. Pick explicitly.

⚠️ Cardinal rule (red in the runbook): never boot a restored clone on the live network with live credentials. Two agents sharing one identity = double-processing, WhatsApp session theft, duplicate deploys.

6. Post-incident: rotate everything (new — the missing half of ransomware DR)

If Mia is compromised, assume the repo was read. The runbook includes a credential inventory and rotation order: gog Google OAuth (Gmail/Calendar/Contacts) · gitea token · Vercel token · GoDaddy API key/secret · Anthropic · Gemini · xAI · LinkedIn access token · OWNER_KEYs (branding/city) · OB1 Postgres URL/password · Tailscale auth/machine keys · all ~/.ssh keys (regenerate + re-authorize) · the restic repo key (rotate + re-encrypt). Each with where it's used and how to rotate. A backup you can restore into a world where the stolen keys still work is only half a recovery.

7. Restore to another device (runbook shape)

  1. Choose the snapshot — never restore latest. Ransomware persistence (workspace source, plists, crons) lives in Tier 1. Identify last-known-good before the incident using the independent timeline; forensic pause; scan restored content before executing anything.
  2. Offline bootstrap-credential chain (all in your password manager, none only-on-dead-Mia): NAS address + rest-server creds · B2 keys · restic repo password · secrets.enc key. Missing one deadlocks the recovery.
  3. Fresh macOS → Homebrew → brew bundle (Brewfile) → Colima (saved profile) → grant TCC/FDA.
  4. restic restore <good-snap> → config, ~/.claude, gitea, ~/.ssh, keys, plists.
  5. Docker volumes injected into the VM (docker run -v … tar -x), not dropped in as macOS files; OB1 restored from dump (mind Postgres major-version match); gitea restored.
  6. Re-pull images by digest — with the caveat that a registry can GC an untagged digest and gitea (the mirror candidate) is inside the fleet being restored: so a small external registry mirror or a docker save of pinned images is kept in Tier 2 to break the circular dependency.
  7. Re-install LaunchDaemons; re-enroll Tailscale (new machine identity); rotate per §6; Tier-3 re-fetch last, in parallel, while the fleet is already live on Tier 1. RTO honest estimate: a few hours to a working fleet, which the first rehearsal will correct — that's what rehearsals are for.

8. backup.arnao.ai — hardened console (tailnet-only)

→ See the static mockup of the console (sample data; the real one ships in Phase 3, tailnet-gated).

Entire console is Tailscale-gated — not just a "detail view." A public page showing retention depth, canary state, and air-gap-drive-last-connected is a targeting package for an attacker (tells them how long to dwell and when your offline copy is stalest); it never ships publicly. Read-only, no mutating endpoints, no secrets rendered, minimal publish pipeline (to shrink the Vercel-token blast radius). It shows: last backup per tier · size · retention ladder · last independent restic check + scratch-restore result (one 0-errors light) · canary status · cloud Object-Lock + air-gap freshness · RPO/RTO · restore-runbook link. It reports; it cannot act.

9. Phases

Phase Scope Effort
0 NAS rest-server append-only repo (confirm this Synology supports it + Object-Lock-equivalent immutable snapshots) · B2/S3 Object Lock bucket · consistent-capture harness (APFS snapshot + quiescing) · throughput/quota math 1 day
1 Offsite the essentials: Tier-1 hourly → NAS and cloud-immutable; retention; keep local as the fast tier 1 day
2 Hardening: append-only, Object Lock, NAS lockdown, canary, least-priv account, external dead-man's switch 1–2 days
3 Independent validation (off-Mia check + scratch-Postgres restore) · credential-rotation runbook · tailnet-only console 2 days
4 Cold + air-gap (Tier-2 once; monthly A/B USB rotation) · first deep rehearsal on isolated hardware 1 day + rehearsal

10. What I need from you

  1. Least-privilege NAS repo for restic rest-server append-only (or DSM admin once to self-provision, then that access is removed).
  2. A Backblaze B2 (or S3) bucket with Object Lock — I'll size it (~a few GB hot; pennies/month).
  3. Two external SSDs for the monthly air-gap A/B rotation.
  4. Agree the restic repo password + secrets.enc key live only in your password manager (I generate; you store; never plaintext on Mia).
  5. For the deep rehearsal: a spare/second Mac Mini quarterly (best), else I design around a cloud Mac with neutered credentials.

11. Self-critique — the Fable adversarial pass

Draft 1 was run through Claude Fable 5 as a hostile senior-SRE review. It found real, load-bearing errors — all fixed above. The disposition:

Fable's verdict on v1 was "not credible yet — fix 1–7." v2 addresses all seven. Next step on your word: Phase 0 (needs the NAS repo path + a B2/S3 Object Lock bucket), after which I migrate the existing local backup offsite and immutable, then build the tailnet-gated console.