Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. MAISON_CONSUMER_ROOT;
  2. MAISON_REPOSITORY or MAISON_REPO as compatibility aliases;
  3. 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/Lixmise
Nix daemon, caches, GC, and store policyUser tools and runtimes
PAM, sudoers, and privileged policyHomebrew formulae and casks
Hostname, timezone, locale, usersMac App Store applications
Root-owned files and SSH daemon policyUser dotfiles and preferences
System launchd/systemd servicesUser services
System-wide fonts and OS integrationsNon-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.