hausfold

haus

Keeping it current

The everyday loop (edit, rebuild, update, roll back) and the payoff it buys, a new Mac that becomes the old one in two commands.

Every change to a haus machine is the same two beats: edit the file, run one command. Pulling a newer haus, undoing yesterday's change and rebuilding the whole setup on new hardware are that same loop, started from different places.

The everyday loop

haus edit       # your host file, in $EDITOR
# …change something…
haus rebuild    # build first, then switch

haus rebuild builds before it switches, so a config with a mistake in it never reaches the running system: you get a build error and a machine still exactly as it was. For a one-line change, skip the editor: haus set theme.accent teal writes the setting, type-checks it and rebuilds in one go. The haus reference has the rest.

From the launcher, too

haus ships a Rebuild System command into the launcher (⌘Space, type "rebuild"), which runs haus rebuild in a floating terminal, and a Nix Config command that opens your host file.

Pulling a newer haus

haus update

haus lives upstream, pinned to an exact commit in your config's flake.lock. Nothing about your machine changes until you run this: no background updater, no surprise on a Monday. haus update moves that one pin to the latest haus, upgrades the family's Homebrew apps alongside it, prints what changed upstream, and rebuilds.

haus status says where you stand: your current generation, the revision you're pinned to, and whether upstream has moved past it. If you want the machinery behind that pin (what a flake input actually is, and why a pushed commit reaches you only when a lock moves), that's How the flakes fit together.

When a change goes wrong

haus rollback      # back to the previous generation, atomically
haus generations   # what you can roll back to
haus doctor        # health check, when it isn't obvious what broke

Every switch creates a generation, and rollback steps back to the last one in seconds. When it's a symptom rather than a change (a dead bar, a launcher that won't answer), Troubleshooting has the known quirks and the command for each.

Rollback doesn't rewind macOS's own settings

A generation holds everything Nix wrote (packages, services, login agents), but not the macOS preferences a rebuild set imperatively: the Dock, Finder, the keyboard. haus capture before a risky change snapshots those, and haus revert-settings puts the snapshot back.

A second Mac

Your config is a git repo, so a machine you already set up catches up the way any checkout does, assuming you pushed it somewhere both machines can reach:

cd ~/.config/nix && git pull
haus rebuild

Both machines read the same pin from the same lock file, so they converge on the same system rather than on roughly the same one.

A new Mac, or a wiped one

This is the payoff for describing a machine as text: a restore, not a weekend of clicking through System Settings. It assumes one thing: that ~/.config/nix is pushed somewhere you can clone it from. If it isn't, that is today's five-minute job, not next month's.

Point the installer at your config

The one-liner that scaffolds a fresh setup also restores an existing one: it clones your repo into ~/.config/nix and skips the interview entirely.

curl -fsSL https://hausfold.co/haus.sh | bash -s -- --from <your-config-remote>

On a brand-new Mac the Xcode Command Line Tools are missing, so the first run opens Apple's installer dialog and stops. Approve it, then run the line again. Determinate Nix it installs for you. (By hand, --from is nothing more than xcode-select --install, Determinate Nix, and git clone.)

Build, then switch

cd ~/.config/nix
nix build .#darwinConfigurations.$(scutil --get LocalHostName).system \
  && sudo ./result/sw/bin/darwin-rebuild switch --flake .

The installer prints this pair for you, with your hostname already filled in. The first build downloads the world and takes a while; when it lands, haus is on your PATH and the machine is yours again; haus doctor is the check that it all came back.

New hostname? Name the host you want.

Your config declares each machine by hostname, and a new one isn't picked up on its own. Either build the old host by name (--flake .#<oldhostname>) or copy hosts/<old>/ to hosts/<new>/ and add it to flake.nix beside the first. Your identity and app choices come along either way.

nix eval ~/.config/nix#darwinConfigurations --apply builtins.attrNames

What comes back on its own

That one switch restores every app in haus.roster that names a Homebrew cask or formula or a nixpkgs package, plus the whole terminal, the macOS defaults haus manages down to the Caps-Lock remap and ⌘Space, and the launchd agents behind tiling, the bar and the launcher. On macOS Tahoe and later that last group has a catch: Background Task Management can quietly refuse to start them on a fresh machine, and haus btm prints the one thing to click.

App Store entries are the exception: installing from an appStoreId is opt-in (haus.appStore.install), and even switched on it can only fetch apps your Apple account already owns; a paid app has to be bought in App Store.app once, by you.

The bits Nix doesn't carry

A handful of things never enter your config, on purpose. Walk this list once per machine:

  • Secrets. Your config declares which secrets exist, never their values. secretspec check lists what's still missing and secretspec set <NAME> fills one in. By default they live in this Mac's login keychain, which is exactly why they don't ride along in the clone; point haus.secrets.provider at a cloud vault and they follow you instead, at the cost of one login to bootstrap it.
  • Commit signing keys. Your config names the key id; the key material is yours to import, along with whatever agent unlocks it.
  • Pounce's Accessibility grant. macOS ties it to the machine, so run pounce --request-accessibility once more. If you set haus.pounce.signingIdentity, import that certificate into the new keychain too; that's what makes the grant survive later rebuilds (Pounce config).
  • Apps you installed by hand. They won't reappear. Reinstall the ones you want, and consider moving them into a roster entry so next time they do.
  • Logins and licences. Nix restores the app, not your session; sign back into the handful that need it, the App Store among them.

Every app you move off that list and into haus.roster is one less thing to remember next migration. The goal is a Mac you could wipe on a Tuesday without flinching.

Adopting a Mac you've already set up by hand?

That's not a restore, and the install flow handles it deliberately: your existing Homebrew casks are kept, dotfiles are backed up rather than clobbered, your current macOS settings can be captured into the new host file, and nothing activates until you say so. Run from a terminal, it offers to build and switch at the end; piped, it just prints the command.

On this page