Maison
Maison is a reusable macOS and Linux configuration framework:
- Nix/Lix owns operating-system state. nix-darwin configures Apple Silicon macOS; system-manager configures supported non-NixOS Linux.
- mise owns user state. The consumer repository supplies tools, packages, applications, preferences, and dotfiles.
Home Manager is intentionally absent. nh provides the local nix-darwin workflow, while deploy-rs handles remote
system-manager profiles and connection-aware system rollback.
Maison is provider-neutral. It contains framework code, neutral examples, tests, and bootstrap tooling—not a maintainer’s personal application list, deployment target, or dotfiles.
Public flake
Consumers pin Maison as a flake input and keep their own flake, lock file, inventory, host topology, configuration, and deployment state:
nix run github:RobertDeRose/maison#maison -- --help
nix flake check --no-update-lock-file
The public flake exports the CLI package and app, reusable nix-darwin and system-manager modules, the orchestration library, the inventory schema, and neutral validation fixtures. See the public flake reference and consumer reference.
Quickstart
For an existing consumer repository, clone it first. For example, Terroir can own the personal inventory and configuration while Maison remains the reusable framework:
git clone git@github.com:RobertDeRose/terroir.git "$HOME/src/terroir"
MAISON_BOOTSTRAP_VERSION="v0.1.1"
bash bootstrap.sh --consumer "$HOME/src/terroir" --ref "$MAISON_BOOTSTRAP_VERSION"
For a fresh consumer, Maison retains its Copier starter. Bootstrap renders the starter, registers the current host, pins the consumer lock, and stops for review before activation:
bash bootstrap.sh --setup "$HOME/src/terroir"
Review the generated flake.nix, inventory.toml, and user policy before creating the consumer’s first commit. The
starter is a setup-time scaffold only; runtime configuration and Git history remain consumer-owned.
The bootstrap script installs Maison under ~/.maison when needed, links the maison command, verifies pinned
platform artifacts, and hands control to the consumer repository. It never uses the Maison checkout as the personal
execution or deployment root.
For a local Maison checkout:
MAISON_HOME="$PWD" MAISON_CONSUMER_ROOT="$HOME/src/terroir" ./bin/maison plan
Consumer repository
A consumer repository must contain regular, committed files named flake.nix, flake.lock, and inventory.toml.
It normally also owns hosts/, config/mise/, dotfiles/, and any host-specific Nix modules. Maison commands resolve
the consumer in this order:
MAISON_CONSUMER_ROOT;MAISON_REPOSITORYorMAISON_REPOas compatibility aliases;- the Git checkout containing the current working directory, when it is not the Maison installation.
Maison’s own checkout is rejected as a consumer for packaged or explicit CLI invocations. Set the canonical environment variable in scripts and CI:
export MAISON_HOME="$HOME/.maison"
export MAISON_CONSUMER_ROOT="$HOME/src/terroir"
maison plan
The consumer’s flake should import Maison’s public modules and compose its own host outputs. Maison’s flake.lock is
only for Maison development, validation, and release; consumer commands never update it.
Declare logical confidential values in the consumer’s fnox.toml and let the consumer choose the fnox provider.
Passwords, tokens, SSH private keys, signing private keys, and other secret material resolve at runtime; they never
belong in Maison, the consumer Git history, Nix expressions, process arguments, or the Nix store. A private Git
repository is not a substitute for secret storage. See the fnox reference.
Bootstrap behavior
bootstrap.sh accepts --consumer PATH for an existing consumer or --setup PATH for a fresh Copier-generated
consumer. MAISON_CONSUMER_ROOT selects an existing repository. A fresh setup destination must be separate from Maison;
bootstrap renders overlay_template/, registers the current host through maison host add, creates the consumer
flake.lock, validates the required consumer files, and stops for review. Create the consumer’s first commit, then rerun
with --consumer to activate it.
Interactive bootstrap without either path offers the same fresh setup and otherwise installs only the CLI. Non-interactive
runs require --consumer or --setup and fail clearly instead of activating neutral or Maison-owned state. The
Copier starter does not create saved alternate roots or runtime overlay state.
Bootstrap first installs verified pinned mise, trusts only the Maison project configuration, installs verified Nix/Lix
artifacts when needed, and runs the bootstrap task with the consumer root. Use --user-only on the mise task when system
activation should be skipped. Do not use pipe-to-shell bootstrap examples; verify downloaded bootstrap artifacts against
bootstrap/artifacts.toml before execution.
Common commands
Run commands from the consumer checkout or set MAISON_CONSUMER_ROOT:
maison doctor
maison consumer validate
maison plan
maison apply
maison update
maison self update
maison system plan
maison system apply
maison system history
maison system rollback
maison system clean
maison user plan
maison user apply
maison user status
maison user update
maison deploy example-linux
maison consumer validate runs the read-only consumer contract: flake composition, inventory, mise package and dotfile
declarations, fnox references, documentation links, supported systems, and raw-credential/private-key boundaries. It
never activates a system, mutates a lock file, or invokes fnox providers, so CI does not need personal credentials.
maison self update upgrades only the Maison input in the selected consumer’s flake.lock. It builds the locked
candidate CLI, validates the consumer through that candidate, and records the candidate CLI in owner-only local state;
failed updates restore both the lock and the prior CLI state. Maison’s own checkout and lock are never an update target.
The same operations are available as Maison mise tasks, for example mise -C ~/.maison run system:plan. Authoring
commands write only consumer files. They require a Git checkout, reject pre-existing changes in mutation targets, and
create focused commits only after successful transactions. Use Git for consumer history, with fast-forward-only pulls
when you choose to synchronize it.
maison apply is deliberately system-first: it activates the consumer’s Nix system layer and then converges its user
layer. A user-layer failure does not roll back the active Nix generation.
Deployment and recovery
maison deploy <host> requires a clean consumer working tree and transfers committed consumer content only. Nix
planning and deployment target the consumer flake; the Maison checkout and lock remain untouched.
Repository replacement uses a root-owned transaction boundary and revision-bound rollback. Restricted recovery repairs only reversible user state; package and application side effects are not rolled back. See the architecture, deployment guide, recovery guide, and package policy.
Ownership boundary
| Nix/Lix | mise |
|---|---|
| Nix daemon, caches, GC, and store policy | User tools and runtimes |
| PAM, sudoers, and privileged policy | Homebrew formulae and casks |
| Hostname, timezone, locale, users | Mac App Store applications |
| Root-owned files and SSH daemon policy | User dotfiles and preferences |
| System launchd/systemd services | User services |
| System-wide fonts and OS integrations | Non-privileged configuration |
A file, package, service, or preference must have exactly one owner. Consumer-owned state is never silently copied into or written to Maison.
Repository layout
flake.nix Maison development/release inputs
inventory.toml neutral Maison validation inventory
nix/ reusable OS modules and orchestration helpers
.mise/tasks/ Maison framework workflows
scripts/ bootstrap, validation, and deployment transactions
overlay_template/ Copier starter for fresh consumer repositories
mise.toml Maison development tools and task discovery
mise.lock Maison development tool lock
schemas/ public inventory schema
Consumer repositories own their flake.nix, flake.lock, inventory, host overrides, mise policy, dotfiles, deployment
state, and personal configuration.
Development
Maison uses repository-owned mise tools and dstack/Beads workflow controls:
mise install --locked
mise run check
uv run scripts/check-docs.py
mise exec -- hk check
Use /plan-features, /start-feature <slug>, /implement-feature <slug>, and /close-feature <slug> for planned work.