Adopting your dotfiles
The honest walkthrough — no magic, and the two moments where gripsack deliberately refuses to be magic.
Start with one directory, not your whole setup
A config-only repo needs nothing but grip itself: the frontend
source rides inside the binary, and the only provisioning is the eval
runtime — the first grip check/apply downloads a pinned,
sha256-verified Deno once (~40MB, cached). And before a repo's code
runs at all, grip asks once: an unfamiliar repo gets a trust prompt
naming exactly what the sandbox allows. y, and you're in business.
grip init my-env && cd my-env
You get a working repo: env.toml, a host entrypoint named after this
machine, a tiny hello module that deploys one file into
~/.config/hello/, and a commented tour of the features. Apply it:
grip check && grip apply
Then look at ~/.config/hello/hello.toml — it's a symlink into the
store. Edit the file in the repo, apply again, watch the generation
flip. That's the whole mechanism; everything else is detail.
Move one real tool over
grip adopt is the walkthrough as a command (plan 0015).
Point it at a live config path; it inspects what it sees, then asks
you the ownership question with the semantics laid out — it never
guesses, because only you know whether the app writes its own config.
The safe default is tracked_copy (wrong guesses are free there;
owned is the informed opt-in). Then it generates the payload +
module + host entry, shows the plan, and touches nothing until you
confirm:
$ grip adopt ~/.config/helix
adopting ~/.config/helix — 2 files, 1.1 kB
ownership: owned — helix doesn't rewrite its config
wrote configs/helix/ · modules/helix.ts · hosts/laptop.ts
prior state will be recorded — rollback restores your original files
apply? [y/N] y
applied — generation 1 active
$ grip rollback
rolled back to generation 0 # your original files are back
The apply absorbs exactly the adopted destinations (scoped take-over — unrelated drift is never clobbered) and records what every destination was before gripsack wrote it. Rollback — and undeclaring the module later — restores the original files, bytes and permission bits, drift-guarded: edits you make after adopting are yours, and a rollback keeps them. On a fresh machine adopt first records an empty generation 0, so there's always something to roll back to.
The safety net under all of it
Adoption is honest because every layer under it has an inverse:
- Foreign-path refusal — gripsack never touches a path it didn't
deploy unless you say
--take-over. - Prior capture — the moment it takes anything over, your original bytes (and permission bits) go to a content-addressed blob store.
- Drift detection — if you edit a deployed file, gripsack keeps your edit and says so; it never silently reverts you.
- Run rollback — an apply that fails mid-graph restores every destination it touched before reporting the error.
- Generation rollback —
grip rollbackflipscurrentback; adopted paths get their originals restored. - Crash recovery — kill -9 or power loss mid-apply leaves a journal; the next apply restores your files before doing anything else — and if you edited them after the crash, your edit wins.
grip plan labels every planned mutation with its reversibility
class, so "what does undoing this mean" is answered before you
confirm, not after.
Writing the module by hand still works, of course — the generated file is exactly what you'd have written.
The ownership question is the whole game
For each tool, one question: does this app ever rewrite its own config?
- Never (helix, git, tmux) →
ownedsymlinks. - Sometimes (zed, VS Code, most GUI apps) →
tracked_copy: a real file; gripsack detects drift and keeps your edit instead of pretending the app behaves. - Shared files (
.bashrc,.profile) →merge: gripsack owns one delimited block, everything outside the markers is never touched. - Same file, per-host values (work email vs personal) →
templatewithvarscomputed in the host entrypoint fromctx.facts.
What stays outside (on purpose)
You don't have to migrate your packages. brew/apt/pixi keep managing
binaries; gripsack manages ~/.config — and you can adopt binary
fetching later, tool by tool, when the pinned-download story is worth
it. Fish users: your universal variables and conf.d are yours; a
merge block in ~/.bashrc-style files is the seam.
When it breaks
grip doctor checks the runtime, grip why-owns <path> answers "which
module deployed this", and grip rollback flips the whole environment
back to the previous generation. If a linter crashes, that's a warning,
not a failed apply — your tools' problems are not your outage.
When you're ready to move a whole tree, the gripsack-adopt skill
teaches an agent to do the inventory and the interview with you:
cp -r skills/gripsack-adopt ~/.claude/skills/ (see
skills).