For years I treated a Mac backup as a copy of the machine, and a copy of the machine is almost entirely software. Backing up the recipe instead of the bytes made the backup small enough to keep.
TL;DR
A Mac backup does not need the bytes of what a guide can rebuild. I back up a manifest when following the guides reconstructs the data, the bytes only when they exist nowhere else, and every secret as one fail-closed encrypted blob.
The backup habit that protected regenerable data
My habit was to back up everything, and it looked responsible. Most of that volume was software I had installed through Homebrew, trees a brew install regenerates in minutes. The price was measurable: ~9GB per snapshot × 5 retained ≈ 45GB on Proton Drive plus hours of daily churn. The constraint was Proton Drive capacity and that churn budget, and the backup still had to cover everything that exists only on this MacBook.
Why byte-for-byte backup fails
The storage bill was the symptom, not the fault. A byte-for-byte backup protects regenerable content in bulk, and the data unique to the machine is lost in the volume. A brew install rebuilds the vendor trees; the files that exist nowhere else sit buried under gigabytes that did not need saving.
Back up the recipe, not the bytes
The rule I settled on has one test: if following the guides rebuilds it, back up a manifest; if it only exists on this MacBook, back up the bytes. Everything cleanly regenerable left the snapshot set, with its measured size noted: the Hermes vendor tree (3.6G), the Open WebUI virtualenv (~2.4G), the agents’ browser profiles (1.2G), the Python virtualenvs (613M), the agent SDK virtualenv (260M), a browser tool (154M), the two photo-editor config trees (114M), and Open WebUI’s vector store (35M, re-indexable).
What replaced them: manifests generated on every run, a Brewfile, per-venv pip freeze output, launchctl-list, launchagents-inventory, vscode-extensions, git-clones with path, remote, and SHA, versions, and a RESTORE.md. I rejected the byte-for-byte backup the habit implied.
What I built
The manifests were the easy half. The secrets were the half that could fail dangerously: credentials, every .env file, and ~/.ssh have to be backed up and must never be readable outside the Mac. Everything ships as one age-encrypted secrets archive in the kit, under three code-enforced rules:
- No plaintext fallback. A missing
age, a missing recipient, or a failed encrypt marks the target FAILED and writes no archive at all. - No partial blob. Any staging failure aborts the whole blob.
- The identity is never in the archive. The
ageidentity lives in Proton Pass, and the scripts read only its public recipient.
The runs are woken on a fixed schedule by launchd, and a Mac that wakes before its network cannot assume a connection. A failed notification is parked, not lost: the next run flushes the queue before it checks the done-marker, oldest first, and removes a parked file only once its send succeeds.
What broke
Two things broke. On the go-live evening, 2026-08-07, age had been installed via Homebrew after the plists were written with a PATH missing /opt/homebrew/bin. The fail-closed rule did exactly what it was built to do: build_secrets_blob refused to write plaintext when command -v age returned false, and mac_restore-kit was marked FAILED, 5 of 6 targets. I added /opt/homebrew/bin to both plists’ PATH and reloaded them.
The second break was quieter. The 07:05 run failed to resolve the Matrix homeserver, the host carrying the alert, because the laptop wakes before the network is up. The backup itself was unaffected; the alert was never retried and was visible only in the job’s local output, the silent green the parking rule closes. The fix touched the backup script only.
The rule’s own record reports Broke: none, and I leave it that way.
The outcome, and what it rests on
The kit is small and recoverable, and the fail-closed rule held. Secrets are recoverable only with the Proton Pass-held identity; because that single identity decrypts both the vault cold-storage bundles and every restore-kit secrets blob, recovery does not multiply the keys I protect. A wake-time notification failure no longer suppresses the alert.
All three records are narrated from a wiki page rather than read off a run, and the recipe record’s outcome figure is superseded, so I assert no total. I claim the design and its status: the rule held, the excluded trees are rebuildable, and the secrets never fell back to plaintext.
A backup you can rebuild from a manifest is worth little until a restore proves it. The same instinct toward a reversible step, rather than a one-shot cutover, shapes the blue-green migration of four WordPress sites onto one virtual private server (VPS).
The operator behind this stack, and its trade-offs, is on the about page. If this is the shape of your problem, let’s talk.
