How the flakes fit together
Nothing on your Mac changes until you say so. This is the machinery behind that, from the config the installer scaffolds to the exact commit every input is pinned at.
Nothing about your machine changes until you ask for it, and no two machines built from the same config disagree. Both of those come from the same place: haus is a Nix flake, your config is another one that imports it, and every import is pinned to an exact commit.
Your config is a thin consumer
The installer scaffolds ~/.config/nix as a flake that takes haus as an input
and calls its builder:
{
inputs.haus.url = "github:hausfold/haus";
outputs = { haus, ... }: {
darwinConfigurations.<hostname> = haus.mkHaus {
username = "<user>";
hostname = "<hostname>";
host = ./hosts/<hostname>;
};
};
}(The input's name is yours to choose; it is a local handle and nothing outside
your flake reads it. haus is what the installer scaffolds; configs written
before 2026-08-14 say nebelhaus, after the desktop rather than the layer, and
keep working. Rename it only together with every use of it in the same file.)
Your choices live in hosts/<hostname>/default.nix, the one file you
edit. haus stays upstream, and you never
edit it.
The builder
mkHaus is the entry point. Given a user, a hostname and a host file it
returns a whole nix-darwin system with every room wired up:
haus.mkHaus {
username = "ada";
hostname = "fog";
host = ./hosts/fog; # your identity and your choices
system = "aarch64-darwin"; # Apple Silicon only
extraModules = [ ]; # more nix-darwin modules, or a desktop
}Only username and hostname are required. desktop selects the one
desktop this machine runs (the argument the
installer writes for you), and extraModules is the seam for any other
nix-darwin module, an app pack included. Eight of haus's modules (den,
hearth, prowl, sill, collar, pounce, hush, secrets) are also
exported on their own under darwinModules, alongside a default holding the
lot. Reach for one of those only with the builder still around it: a module
declares no options of its own and takes the username from mkHaus, so on
its own it doesn't evaluate.
The inputs
The haus flake pulls together ten:
| Input | What it is |
|---|---|
nixpkgs | the package set, tracking unstable |
nix-darwin | the macOS system modules |
home-manager | your user environment |
catppuccin | the theme framework nebelung derives from |
nebelung | the palette |
pounce | the launcher app |
perch | the notch file shelf; this input tracks perch's default branch, but what gets built is the notarized release zip perch's own flake pins |
holt | the worktree substrate, put on your PATH when haus.ai.enable is on |
zmx | the session backend an agent lane's window uses when haus.hearth.lanes.backend = "zmx"; the one input that deliberately doesn't follow our nixpkgs, since it builds through zig2nix against a pinned Zig |
nix-index-database | the index behind nix-index and comma |
Why a pin means "nothing changes until you say so"
Every input is recorded in flake.lock as an exact commit hash, never as
"latest". That is what makes a rebuild reproducible: the same lock produces the
same system on any machine, on any day.
The flip side surprises everyone: pushing a change to the palette or to haus
changes nothing on your Mac until your flake.lock is bumped to the new
commit. A single colour change therefore has to walk the whole chain, each link
a lock pinning the exact commit of the one before it:
nebelung ──► pounce ──► haus ──► ~/.config/nix ──► your MacYou never walk it by hand. haus update
moves your pin to the latest haus (which already has its own inputs pinned),
upgrades the family's Homebrew apps, and rebuilds. Walking the upstream half is
a contributor's job, and bench ship does it; see
Contributing.
Cold-boot safety
Nix lives on its own APFS volume, and at cold boot macOS will try to launch haus's three GUI agents (AeroSpace, SketchyBar and Pounce) before that volume is mounted. haus wraps those three in a shim that waits for the Dock, Finder and SystemUIServer first, then gives up after a minute and launches anyway. That is why your bar and your tiling come back after a reboot rather than racing the login.
Troubleshooting
The handful of things that go sideways on macOS (a wedged agent, a missing grant, a stubborn cask), and the exact command that unsticks each one.
Contributing
The workshop checkout, the bench CLI, and the loop that takes a change from an edit to a release. Try it without pushing, feel the whole PR queue at once, then ripple it down the chain.