# How the flakes fit together (/docs/haus/internals/flakes)



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](https://nix.dev/concepts/flakes.html), your config is
another one that imports it, and every import is pinned to an exact commit.

## Your config is a thin consumer [#your-config-is-a-thin-consumer]

The installer scaffolds `~/.config/nix` as a flake that takes haus as an input
and calls its builder:

```nix
{
  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. 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](/docs/haus/desktops/customizing). haus stays upstream, and you never
edit it.

## The builder [#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:

```nix
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](/docs/haus/desktops/choosing) this machine runs (the argument the
installer writes for you), and `extraModules` is the seam for any other
nix-darwin module. Eight of haus's modules (`core`,
`terminal`, `windows`, `bar`, `security`, `launcher`, `focus`, `secrets`) are also
exported on their own under `darwinModules`, alongside a `default` holding the
lot. That is how you take the tiling or the bar into a flake of your own and
leave the rest of the house behind:

```nix
inputs.haus.darwinModules.windows   # just the tiling
inputs.haus.darwinModules.bar       # just the bar
```

Each export is the bare foundation plus that one room, so it needs no desktop
and switches no neighbour on. What it does need is the scaffolding `mkHaus`
would otherwise have supplied: your own `darwinSystem` call with
`nixpkgs.hostPlatform` (a room won't pick a platform for you),
`system.primaryUser`, `system.stateVersion`, home-manager, and two arguments
the modules read: `username` in `specialArgs` and `nebelung` in
`home-manager.extraSpecialArgs`.

It also needs an overlay. snug is the painter haus's own commands draw
through, and it ships from its own flake rather than from nixpkgs, so carry
it whichever export you took:

```nix
inputs.snug.url = "github:hausfold/snug";
# …
nixpkgs.overlays = [ inputs.snug.overlays.default ];
```

For most exports that is the whole list. `darwinModules.launcher` activates
its own room, so it wants pounce's overlay from the start, and
`haus.ai.enable` wants scruff's and factory's wherever you turn it on.
`darwinModules.default` holds the lot, so there
`haus.notifications.compositor` wants trill's and `haus.shelf.enable` perch's.
Leave one out and the build stops at eval with a message naming the overlay
and the line to add, rather than naming a derivation you have never heard of.
(`mkHaus` applies all six itself, which is why a config built that way never
meets any of this.)

An export switches its own room on and claims none of the `haus.keys` chords.
Those are a shared surface a [desktop](/docs/haus/desktops/choosing) sets, and no
export selects a desktop, so `leader`, `windowNav` and `palette` all stay at
`"none"`. What `darwinModules.windows` gives you is a tiler whose main and
service binding blocks come out empty: nothing is bound until your own config
sets `haus.keys.windowNav` and `haus.keys.leader`, and the palette under
`darwinModules.launcher` runs the same way, with no hotkey on it.
[Keys](/docs/haus/rooms/keys) is every chord those options move, and the
generated `aerospace.toml` says which option emptied each block.

haus's own [`flake.nix`](https://github.com/hausfold/haus/blob/main/flake.nix)
has the whole thing spelled out as `standaloneSystem`, which is not an example
but the fixture: a flake check builds every export through it, with snug's
overlay alone as well as with all six, so a room that quietly started asking
for more than that fails in haus's CI rather than in your flake.

Rooms with no export of their own ride along with the full house instead. Which
ones those are is a fact about `flake.nix`, not about the room: read the
`darwinModules` block there rather than a list here. `apps` is the one that
could never have an export, because it needs the roster resolver beside it to
install anything.

## The inputs [#the-inputs]

The haus flake pulls together twelve:

| 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, on perch's footing: this input tracks pounce's default branch, but what the daemon runs is the notarized release app pounce's own flake pins                                                                                                            |
| `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                                                                                                                                 |
| `trill`              | the notification compositor, packaged the same way as perch. The only input a room installs and no room needs: [`haus.notifications.compositor`](/docs/haus/rooms/notifications) places the bundle, and `haus-notify` finds it at runtime or falls back to Apple's banner |
| `scruff`             | the worktree substrate, put on your `PATH` when `haus.ai.enable` is on                                                                                                                                                                                                    |
| `snug`               | the painter behind every haus command: one set of tables, spinners and colour tiers, shared by the tools that draw them. The one input a standalone `darwinModules` import has to carry itself                                                                            |
| `factory`            | the unattended merge shift, on your `PATH` beside `scruff` when `haus.ai.enable` is on                                                                                                                                                                                    |
| `nix-index-database` | the index behind `nix-index` and `comma`                                                                                                                                                                                                                                  |

## Why a pin means "nothing changes until you say so" [#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:

```text
nebelung ──► pounce ──► haus ──► ~/.config/nix ──► your Mac
```

You never walk it by hand. [`haus update`](/docs/haus/keeping-it-current#pulling-a-newer-haus)
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](/docs/haus/internals/contributing).

## Cold-boot safety [#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.
