Making it yours
nebelhaus ships a complete opinion — system and shell, already wired. So “customizing” isn’t reassembling anything; it’s turning a handful of dials. This page is the map of those dials and the thinking behind the loudest defaults.
The one file you edit
Section titled “The one file you edit”There are two halves to your setup, and they stay cleanly apart:
- The rice (
github:nebelhaus/nebelhaus) — the generic, no-identity system. You never edit this to use it. It updates upstream; you pull it when you like. - Your config (
~/.config/nix) — a ~18-line flake that imports the rice, plus one host file where you live:hosts/<hostname>/default.nix.
That host file is where every tweak on this page goes. Open it instantly:
haus editIt’s a plain nix-darwin module — whatever you write merges with what the rice already declares. Change something, then:
haus rebuild # builds first; a broken config never activatesThe one-app-per-workspace idea
Section titled “The one-app-per-workspace idea”The single biggest opinion in nebelhaus is how windows are arranged, so it’s worth saying out loud: each app you live in gets its own workspace, and one key takes you there.
Instead of alt-tabbing through a soup of windows, you build muscle memory: the terminal is always ⇪ T, the browser is always ⇪ B, Slack is always ⇪ S. The app is launched if it isn’t running, focused if it is, and it lands on its workspace every time. Your hands stop negotiating with the window manager.
That whole scheme comes from one keyed map — nebelhaus.apps, the roster —
so the launcher key, the workspace, the bar pill, and the auto-tiling rule can
never drift out of sync. Keyed entries also compose cleanly across your host
file and pounce-generated package modules. A few things worth internalizing:
- Letters are apps, digits are scratch. Workspaces
1–4are generic scratch space; letters (T,B,S…) belong to specific apps. - Focus vs. throw. Tap ⇪ then a letter/digit to go there. Same leader, but ⇧ with that letter/digit, to throw the current window there instead.
- Launcher-only apps exist. Give a roster entry
workspace = nulland its key just launches it in place — no dedicated workspace, no pill. Handy for a password manager or a calculator.
Adding your own is a one-liner — see Adding apps & tools for the recipe and Window management for the full binding set.
The cookbook
Section titled “The cookbook”Every knob below is a nebelhaus.* option (or a plain nix-darwin one) you set in
your host file. This is the tour; the exhaustive list with types and defaults is
the options reference.
Or don’t look any of it up. The rice installs a skill that teaches Claude Code this whole surface — generated from the revision you’re pinned to — so you can ask in plain words instead: Changing your Mac with an agent.
Identity
Section titled “Identity”nebelhaus.git.name = "Ada Lovelace";nebelhaus.git.email = "ada@example.com";nebelhaus.git.signingKey = "6F7BD6F43A7C1420"; # "" to disable commit signingThe look
Section titled “The look”nebelhaus.theme.accent = "sapphire"; # any of 14 Catppuccin accent namesnebelhaus.theme.wallpaper = "orbits"; # none | orbits | constellation | flow | boldaccent recolours lazygit, fzf, yazi, and the browser accent; bold generates
a wallpaper from your accent. Full palette on Theming & accents.
The shell
Section titled “The shell”nebelhaus.hearth.editor = "nvim"; # the ONE editor: $EDITOR + every "open in editor" actionnebelhaus.hearth.hijackFileAssociations = false; # make that editor the default file openerMore on each in The shell.
Apps, tiling & the launcher
Section titled “Apps, tiling & the launcher”nebelhaus.apps.slack = { /* one keyed roster entry — see Adding apps */ };nebelhaus.prowl.enable = true; # false: no tiling, no Caps-Lock remapnebelhaus.pounce.enable = true; # false: no ⌘Space palettenebelhaus.pounce.signingIdentity = ""; # keeps Pounce's Accessibility grant across rebuildsThe bar
Section titled “The bar”nebelhaus.sill.enable = true; # false: keep the native macOS menu barnebelhaus.sill.plugins = [ "elgato" ]; # opt-in extras: agents | elgato | harvestThe bar’s optional plugins are personal — an Elgato Key Light toggle, a Harvest timer, an agents pill — off unless you ask. See The bar.
The first-run tour
Section titled “The first-run tour”The built-in tour teaches nebelhaus’s four core moves. A shared rice can replace that lap with its own instructions while staying data-only:
nebelhaus.tour.steps = [ { hint = "Press ⌘Space, type tour, then hit ↵"; detect = "palette"; } { hint = "Move to another workspace"; detect = "workspace"; }];Steps run in order and advance from signals the rice already emits — launch,
workspace, navigate, resize, or palette — rather than by logging keys.
The first four need prowl; palette needs Pounce. A left-click skips the current
step, and the module warns during a rebuild if a step depends on a room you
disabled. Leave tour.steps at its default null to keep the built-in tour.
Homebrew behaviour
Section titled “Homebrew behaviour”nebelhaus.homebrew.cleanup = "none"; # none | uninstall | zap — how rebuild treats undeclared appsnebelhaus.homebrew.autoUpdate = false; # brew update before each rebuildnebelhaus.homebrew.upgrade = false; # upgrade outdated packages each rebuildhomebrew.casks = [ "discord" ]; # plain installs, no launcher keycleanup defaults to "none" so nothing you installed by hand is ever removed.
Set it to "zap" if you want a fully declarative machine where undeclared
casks get uninstalled on rebuild.
Turning rooms off
Section titled “Turning rooms off”Three rooms are opt-out; the rest are load-bearing:
| Room | Option | Off means |
|---|---|---|
| prowl (tiling) | nebelhaus.prowl.enable = false | No AeroSpace, no leader key |
| sill (bar) | nebelhaus.sill.enable = false | The native menu bar stays |
| pounce (launcher) | nebelhaus.pounce.enable = false | ⌘Space stays Spotlight |
If what you actually want is the tiler without its keyboard claims, don’t turn
the room off — set nebelhaus.keys.leader = "none" and
nebelhaus.keys.windowNav = "none" instead. prowl keeps tiling and Caps-Lock stays
Caps-Lock. See rebinding.
den (system + Homebrew), hearth (shell), and collar (Touch ID sudo) are always on — they’re the foundation the rest sits on. Turn any of the three optional rooms off and the others keep working; the rice degrades gracefully.
Presets: start from someone else’s rice
Section titled “Presets: start from someone else’s rice”A preset is a data-only rice — a file whose only top-level key is
nebelhaus. No pkgs, no lib, no activation scripts, so it can’t reach outside
the option surface and you can read one in a minute and know the worst it can do.
Import one alongside your host file:
darwinConfigurations.myhost = nebelhaus.mkNebelhaus { username = "ada"; hostname = "myhost"; host = ./hosts/myhost; extraModules = [ nebelhaus.presets.everyday ];};Your host file is imported after, so anything you set there still wins.
| Preset | For |
|---|---|
full | the whole rice: bar, tiling, palette, developer pack. The default. |
minimal | just the themed shell — no bar, no tiling, no palette. Still a developer machine. |
everyday | the inverse: a Mac for someone who doesn’t write code. No developer pack, no Caps-Lock remap. |
large-print | bigger and sharper — the rice UI and whole display, plus the high-contrast palette and macOS’s own contrast lift. |
The first three are whole rices; pick one. large-print is a layer — it
describes seeing rather than the person, so it stacks:
extraModules = [ nebelhaus.presets.everyday nebelhaus.presets.large-print ];It uses two complementary size controls. ui.scale = 1.4 moves the terminal
font, the Dock icons and the tiling gaps together — pin any single one afterwards
and yours wins. displays.main.uiScale = "larger-text" moves the current main
display one step toward larger text, which makes everything bigger, including
third-party apps the rice knows nothing about. The trade-off is less desktop
space.
For per-display control, add a UUID-specific setting in your host file; it wins
over the preset’s broad main selector. hausdisp list prints the persistent
UUIDs of the displays currently attached.
The remaining size limits are the menu bar and the Pounce window: both use geometry tuned to their layouts, so a multiplier would break alignment rather than enlarge them. The preset also doesn’t change an app’s own font preference or macOS’s separate Dynamic Type text-size setting; display scaling is the system-wide lever that works.
Want to make one for other people? Sharing a rice
covers the data-only boundary, checkRice, composition testing, and the
smallest useful publishing shape.
Overriding macOS defaults
Section titled “Overriding macOS defaults”The rice sets Dock, Finder, trackpad, and keyboard defaults — but every one of them is a soft default you can beat by just setting the same key with a plain value in your host file:
# Rice hides the Dock and puts it on the bottom; put it on the left, always shown:system.defaults.dock.autohide = false;system.defaults.dock.orientation = "left";Anything under system.defaults
is fair game. Your plain value wins over the rice’s default.
Secrets & private things
Section titled “Secrets & private things”Anything sensitive — API keys, tokens, your app list if you’d rather it stay private — belongs in your host file (which is your repo), and secrets themselves belong in files the Nix store never sees:
home-manager.users.${username} = { # Read secrets at shell startup, not from the (world-readable) Nix store: programs.zsh.initContent = lib.mkAfter '' export SOME_API_KEY="$(cat ~/.secrets/some-key 2>/dev/null)" ''; home.packages = with pkgs; [ /* your private tools */ ];};Keep ~/.secrets/* out of git. Your host repo can be private; the rice never
needs to know who you are.
Where to go next
Section titled “Where to go next”- Adding apps & tools — the most common change, in depth.
- Moving to a new Mac — because all of this is just git.
- Keeping in sync — updating, rolling back,
haus. - Options reference — every knob, formally.