Migration runbook
This is the end-to-end procedure for moving a development setup to a new machine.
It is built around the hybrid model: dothaven handles discovery, audit, and
export, while chezmoi handles storage, age-encryption, and applying configs
on the destination. dothaven never stores or transmits your files — it plans the
hand-off and chezmoi add does the rest.
The runbook has two halves: everything you do on the old machine before you wipe or hand it back, and everything you do on the new machine to restore parity. Each half is an ordered checklist — work top to bottom.
chezmoi-export --apply path and the new-machine restore actually invoke them.Before you start
Install dothaven on the old machine if it is not already present.
brew install doguyilmaz/tap/dothavenYou will also need chezmoi and age. The next step checks for both.
Part 1 — Old machine
Step 1: Verify the chezmoi + age prerequisites
dothaven init is a read-only preflight. It probes whether chezmoi is
installed, whether your ~/.config/chezmoi/chezmoi.toml declares
encryption = "age", whether the chezmoi source directory is a git repo, and
your GitHub login (via gh api user, if available). It prints a checklist and
the exact command for each unmet step — it does not change anything.
dothaven initdothaven init — chezmoi + age bootstrap
✓ chezmoi installed
✓ age encryption configured
→ initialize a chezmoi source repo
chezmoi init
Run the commands above, then re-run `dothaven init`.Work through any → items and re-run until everything reads ✓. When the
prerequisites are met, init prints the next two commands for you:
✓ Setup complete. Next:
dothaven chezmoi-export # dry-run — review the plan
dothaven chezmoi-export --apply # executeStep 2: Collect a snapshot and audit it
Inventory the machine into a timestamped JSON snapshot. By default collect
redacts secrets before anything touches disk and prints an inline sensitivity
report.
dothaven collectReport saved to: /Users/you/projects/dotfiles/reports/old-host-20260604093000.json
⚠ Sensitivity report:
HIGH /Users/you/.npmrc npm auth token — redacted
HIGH /Users/you/.ssh/id_ed25519 private key — skipped
1 items redacted, 1 skipped.The output directory is resolved in this order: an explicit -o / --output
wins; otherwise, if the working directory is a git repo the file lands in
<cwd>/reports; otherwise it falls back to ~/.local/share/dothaven. The filename is
<hostname>-<UTC timestamp>.json. Keep this file — you will copy it to the
new machine and feed it to dothaven doctor to verify parity in Part 2.
Before you export anything, review what is sensitive on disk with the standalone
scanners. scan prints findings to the console; security writes a grouped
Markdown report (default SECURITY.md).
dothaven scan ~ # console: L<line> [SEVERITY] <label>: <match>
dothaven security ~ # writes SECURITY.mdSecurity report written to: SECURITY.md
142 scanned, 3 with findings.Read the report. Anything HIGH is what the export will encrypt or skip — confirm nothing surprising is about to be carried over.
Step 3: Plan the chezmoi export (dry-run)
chezmoi-export builds the hand-off plan: plain chezmoi add for ordinary
configs and chezmoi add --encrypt for anything high-sensitivity. It is a
dry-run by default — nothing changes until you pass --apply.
dothaven chezmoi-exportchezmoi-export plan — 4 path(s), 1 encrypted:
add /Users/you/.config/ghostty/config (plain)
add /Users/you/.gitconfig (plain)
add /Users/you/.zshrc (plain)
🔒 add --encrypt /Users/you/.ssh/id_ed25519 (ssh private key)
+ run_onchange install script (brew, packages)
Dry-run. Re-run with --apply to execute (requires chezmoi + a configured age key).The plan also lists a run_onchange install script line when brew or package
groups are selected — that is the script chezmoi will run on the new machine to
reinstall everything. Useful flags:
--pin— pin global packages to the version captured on this machine (otherwise the fresh machine installs the current release).--only/--skip— comma-separated categories or groups to include or exclude (for example--only ssh,brewor--skip vscode). Skip wins over only.
Iterate on the plan with these flags until it lists exactly what you want to carry over.
Step 4: Apply the export
When the plan looks right, run it. --apply requires chezmoi and a configured
age key; if chezmoi is not found it stops with exit code 1 and tells you to
install it.
dothaven chezmoi-export --apply ✔ .chezmoiignore (gnupg runtime cruft)
✔ /Users/you/.config/ghostty/config
✔ /Users/you/.gitconfig
✔ /Users/you/.zshrc
✔ encrypted /Users/you/.ssh/id_ed25519
✔ run_onchange_install-packages.sh
Done. Review with `chezmoi diff`, then commit your private chezmoi source repo.What --apply writes into your chezmoi source directory:
- One
chezmoi add(oradd --encrypt) per planned path. run_onchange_install-packages.sh— a command-guarded bash script that reinstalls brew formulae/casks, fnm node versions, and global packages (bun,pnpm,npm,cargo) on the nextchezmoi apply. Every step is|| trueand the script ends inexit 0, so a missing tool never aborts the apply. Deno global bins are recorded as a comment (the original module URL is not recoverable from a bin name) — reinstall those by hand..chezmoiignoreentries for GnuPG runtime cruft (sockets, locks, the RNG seed) when a real GnuPG key is present. Key material itself is not ignored.
run_onchange_install-packages.sh is unencrypted: the Brewfile
is embedded verbatim. dothaven redacts inline credentials it can detect (such as
a private tap’s https://user:pass@host), but review the script before you
commit it to make sure no secret slipped in.Step 5: Commit the private chezmoi source repo
Review the staged changes, then commit and push the chezmoi source repository. This repo holds your configs and the age-encrypted secrets — keep it private.
chezmoi diff
chezmoi cd
git add -A
git commit -m "Sync configs from old-host"
git push
exitAt this point the old machine’s work is done. Make sure you still have:
- The snapshot JSON from Step 2 (for the parity check on the new machine).
- A backup of your age private key — see the warning below.
init use
~/.config/chezmoi/key.txt; check ~/.config/chezmoi/chezmoi.toml for the exact
path your setup uses.Part 2 — New machine
Step 6: Install chezmoi and restore the age key
On the fresh machine, install chezmoi and age, then restore your age private
key first — before any chezmoi apply. chezmoi needs the key to decrypt the
encrypted files in your source repo; without it the apply will fail on the first
encrypted entry.
brew install chezmoi age
mkdir -p ~/.config/chezmoi
# Restore the key you backed up in Step 5, e.g.:
cp /Volumes/backup/key.txt ~/.config/chezmoi/key.txt
chmod 600 ~/.config/chezmoi/key.txtchezmoi.toml expects, then continue.Step 7: Initialize chezmoi from your private repo
Point chezmoi at the private source repo you pushed in Step 5 and apply it. This clones the repo, decrypts the encrypted files with your age key, and writes every config into place.
chezmoi init --apply git@github.com:you/dotfiles-private.gitStep 8: Let the run_onchange script reinstall packages
Because the source repo contains run_onchange_install-packages.sh, chezmoi
runs it as part of the apply (and again whenever its contents change). The script
installs:
- Homebrew formulae and casks (via
brew bundlefrom the embedded Brewfile) - fnm node versions
- global
bun,pnpm,npm, andcargopackages
Each block is guarded by command -v <tool> and every install is || true, so
the script keeps going even if a manager is missing. The first run can take a
while as Homebrew downloads everything. Deno global bins, recorded only as a
comment, must be reinstalled manually.
To install dothaven itself on the new machine so you can verify parity:
brew install doguyilmaz/tap/dothavenStep 9: Verify parity with the snapshot
Copy the snapshot JSON from Step 2 to the new machine, then run dothaven doctor against it. doctor re-inventories the live machine and lists everything
that was present in the snapshot but is missing here. It only checks
installable inventory — packages, runtimes, brew formulae/casks, macOS apps,
fonts, and editor extensions — and matches on name, so version drift is ignored.
dothaven doctor old-host-20260604093000.jsonWhen everything lines up:
✅ Parity — everything installable in the snapshot is present on this machine.When something is still missing, doctor groups it by section, prints a count, and exits non-zero (CI-friendly):
Missing on this machine (present in the snapshot):
apps.brew.formulae (2)
- jq
- ripgrep
packages.npm.global (1)
- typescript
3 item(s) missing across 1 section(s).A non-zero exit here is a normal outcome, not an error — it is the to-do list of
what to install. Re-run chezmoi apply (to re-trigger the install script) or
install the listed items by hand, then run doctor again until it reports
parity.
Manual checklist (out of scope)
dothaven and chezmoi cover discovery, secrets, configs, and reinstallable packages. The following are deliberately not captured — handle them by hand on the new machine:
- System files outside
$HOME—/etc/hostsand other/etcedits are not in scope. - VPN configuration and certificates — reconfigure your VPN client and re-import any profiles.
- Browser sync — sign in to your browser to pull bookmarks, extensions, and saved sessions.
- Provisioning profiles and signing identities — re-download Apple provisioning profiles and re-import code-signing certificates into the keychain.
- Ollama models — the snapshot records model names (
ai.ollama.models) but not the weights. Re-pull each model, e.g.ollama pull llama3. - Anything requiring an interactive login — App Store apps, licensed software, and 2FA-gated services need to be signed in again.