Personal macOS configuration: system packages, GUI apps, shell setup, and tool configs for work and personal Macs.
Grimoire is intentionally macOS-only. It is built around nix-darwin, Homebrew, darwin-rebuild switch, and macOS desktop conventions; Linux support belongs in a separate tool, not behind conditionals here.
On a new machine, clone to ~/.grimoire and run the wrapper directly:
git clone git@github.com:LarsNorlander/grimoire.git ~/.grimoire
~/.grimoire/grimoire bootstrapThe wrapper offers to install Nix and Homebrew if they are missing. bootstrap prompts for a profile, applies runes, then applies rites. It is safe to re-run.
After bootstrap, use the narrower verbs for day-to-day work. Run grimoire --help and grimoire <verb> --help for the current flag reference.
- Runes (
runes/) are nix-darwin system configuration: packages, Homebrew apps, fonts, and macOS defaults. - Rites (
rites/<tool>/) build and link per-tool config into place. - Tome (
tome/) is gitignored build output. Managed symlinks point here, not directly at tracked source files. - Familiars (
familiars/) are ephemeral Nix shells for tools that should not persist on the machine. - Profiles (
workorpersonal) select the nix-darwin output and any profile-specific rite behavior. The current profile lives in~/.grimoire-profile.
grimoire bootstrapβ provision a fresh machine by applying runes, then rites.grimoire inscribeβ apply runes.grimoire cast [tool ...]β apply rites, rebuilding tome files and refreshing symlinks.grimoire diff [tool]β inspect drift between rite sources, tome files, and the manifest.grimoire accept <tool ...>β copy externally edited tome files back to rite sources where possible.grimoire updateβ fast-forward the checkout to its upstream; with local commits or uncommitted changes in the way, it reports them and leaves the reconciliation to git.grimoire profileβ show or change the current profile.grimoire summon <name>β enter an ephemeral familiar shell, or run one command in it with--.grimoire scribe [tool ...]β generate cheatsheet JSON from rites that document themselves.
A rite is a Python module, rites/<tool>/rite.py, whose rite(ctx) function registers operations with RiteContext:
copy()copies source files from the rite intotome/.write()generates files intotome/from Python builders.link()creates user-facing symlinks totome/.patch()owns selected keys inside a JSON file the machine otherwise owns. The rite holds a fragment; its leaf keys are merged into the live file in place, so this is the one operation that writes a real file instead of symlinking. Arrays are replaced whole, not merged.hook()performs necessary imperative setup.doc()registers cheatsheet data forgrimoire scribe.
tome/.manifest records each built file's hash, the operation that produced it, and the symlinks pointing at it, so a full cast can prune files a rite stopped managing along with their links. If a tool edits a managed file in tome/, cast skips it rather than overwriting it silently. Use diff to inspect the state, cast --force to rebuild from source, or accept to pull external edits back into copy()-managed rite sources. Generated write() files require manual reconciliation. For patch() files, drift and accept look only at the owned keys; the rest of the file is free to change.
Profile-specific rites use a header directive:
#!/usr/bin/env python3
# profile: workRites without a directive apply to every profile.
The work profile runs Docker through Lima rather than Docker Desktop. The lima rite manages the VM template at ~/.lima-templates/docker.yaml and creates the rootless docker instance on first cast, which downloads an Ubuntu image and takes a few minutes. The docker rite creates a lima-docker context pointed at the VM's socket and owns the keys in ~/.docker/config.json that select it and Homebrew's CLI plugins. The VM does not start at login; limactl start docker brings it up.
grimoire scribe writes schema-validated JSON for documented rites. Grimoire only produces data; rendering belongs to homepage. By default, sheets are written to homepage's content directory; override with --output or GRIMOIRE_SCRIBE_OUTPUT.
Generated sheets carry a generator marker, and grimoire only overwrites or prunes sheets it owns so hand-written sheets can share the same directory.
Secrets are managed through 1Password and its CLI (op). Nothing sensitive belongs in this repository.
This is a personal configuration repo. Contributions are not accepted, but you are welcome to fork it.
This repo is maintained with the help of Pi.