Personal NixOS and macOS configuration flake. Built on snowfall-lib, with home-manager, nix-darwin, NixOS impermanence, and sops-nix.
Ported from my previous Ubuntu setup at .dotfiles-old.
- NixOS workstation with Sway / Wayland
- Impermanent NixOS root: only
/nix,/boot, and/persistsurvive reboots - Apple Silicon MacBook managed with nix-darwin and Home Manager
- SOPS + age for secrets, scoped per host
- Custom xkb layouts on Linux, plumbed through to Sway keybindings
- Darwin keyboard defaults for Dvorak, Caps Lock, and non-US tilde behavior
- Homebrew-managed macOS apps through the Darwin layer
Everything defined by this repo uses the custom. namespace.
| Path | Purpose |
|---|---|
flake.nix |
Flake entry point and inputs |
systems/ |
Per-host NixOS / nix-darwin configurations |
homes/ |
Per-user Home Manager configurations |
modules/nixos/ |
NixOS system modules |
modules/darwin/ |
nix-darwin system and app modules |
modules/home/ |
Home Manager modules |
modules/shared/ |
Modules shared between system and home |
lib/ |
Snowfall library extensions exposed under lib.custom.* |
packages/ |
Custom packages surfaced as flake outputs |
overlays/ |
nixpkgs overlays |
shells/ |
Per-language nix develop shells |
secrets/ |
SOPS-encrypted secrets |
docs/ |
Operational notes and recovery runbooks |
systems/x86_64-linux/workstation- main NixOS desktopsystems/x86_64-linux/vm- virtualized test targetsystems/aarch64-darwin/macbook- Apple Silicon MacBook
Use just as the normal entry point:
just switch # Darwin: HM then sudo darwin-rebuild; Linux: sudo nixos-rebuild
just hm-switch # standalone Home Manager switch
just darwin-switch # sudo darwin-rebuild for macbook
just nixos-switch # sudo nixos-rebuild for workstationDevelopment shells:
nix develop .#nix
nix develop .#go
nix develop .#luaRun custom packages:
nix run .#backup
nix run .#monitor-control
nix run .#gammastep-helper
nix run .#waybar-vpn-status
nix run .#networkmonFormat the tree:
nix fmt- Workstation failsafes: memory-pressure prevention, earlyoom behavior, Magic SysRq commands, recovery order, and post-incident diagnostics.
- Pins and patches: every version pin, overlay, and source patch that only works around an upstream bug, with its removal condition.
- RustDesk: private relay and macOS/NixOS client setup.
The rule for Darwin is:
- CLI tools: use nixpkgs, Home Manager, flake inputs, overlays, or local packages.
- GUI apps and macOS app bundles: use nix-darwin-managed Homebrew, with nix-homebrew as the preferred bootstrap layer if Homebrew ownership needs to be made fully declarative.
This keeps the Mac simple and reliable. Even if a nixpkgs package exposes a
.app, pinning that app into the Dock can leave a missing question mark after
rebuilds because the Dock points at an old store path. Use Homebrew casks for
apps that live in /Applications.
Current examples:
ghosttyandweztermare Homebrew casks inmodules/darwin/apps/terminals/default.nixworktrunkis a Homebrew formula on macOSworktrunkis installed from its upstream Nix flake on Linux
Install Nix on a clean macOS machine:
curl -L https://nixos.org/nix/install | sh -s -- --daemonRestart the terminal, then enable flakes for bootstrap:
mkdir -p ~/.config/nix
printf 'experimental-features = nix-command flakes\n' >> ~/.config/nix/nix.confClone this repo:
mkdir -p ~/Desktop
cd ~/Desktop
git clone https://github.com/Nikola-Milovic/.dotfiles.git dotfiles
cd dotfilesInstall the MacBook age key before activating SOPS-backed Home Manager:
mkdir -p ~/.config/sops/age
install -m 600 ~/Downloads/macbook-keys.txt ~/.config/sops/age/keys.txtBootstrap with just through Nix:
nix run nixpkgs#just -- switchAfter the first successful activation:
just switchdarwin-rebuild switch must run as root. The just recipes already do that
with sudo.
The NixOS root filesystem is meant to be reset on every boot. It currently is
not: boot.initrd.systemd.services.rollback in
modules/nixos/system/impermanence/default.nix has wantedBy = [ ], so the
wipe never runs (see the TODO below). Everything therefore survives whether or
not it is opted in, which means the persist list is untested -- re-enabling the
rollback would be the moment you discover which paths were never really
persisted.
A files = [...] entry also fails loudly if a real file already sits at the
target: the bind-mount helper refuses rather than clobbering it, the unit fails
during just switch, and the two copies silently diverge. Fix it by moving the
authoritative copy under /persist/... and starting the unit.
To keep state across reboots, opt in explicitly:
- System state:
modules/nixos/system/impermanence/default.nix - User state:
modules/home/impermanence/default.nix - App-specific state: the relevant module's own impermanence block
Persistent data lives under /persist/... and is mounted back to where apps
expect it. Before major changes, snapshot it:
nix run .#backupDarwin does not use impermanence.
codex on the workstation talks to the self-hosted CodexLB in the prod
Kubernetes cluster, reached only over the tailnet at
https://codex-lb-prod.tail469983.ts.net. Runbook: docs/codex-lb.md in the
frmlabz/infra repo.
The provider is configured in ~/.codex/config.toml, which is not managed by
Nix. It authenticates with CODEX_LB_API_KEY, one key per machine. Codex has
no dotenv support, so env_key needs a genuinely exported variable:
- the key lives in a mode-
0600~/.codex/env, never in the Nix store, which is world-readable; modules/home/programs/terminal/common/default.nixsources that file fromprograms.bash.initExtra;~/.codexis persisted, so the key survives reboots.
A custom.services.codex-lb module used to run CodexLB locally as a Docker
oci-containers unit. It was removed on 2026-08-18 when prod became
authoritative: the unit had autoStart = true, so any rebuild would have
started a second writer against a by-then-stale SQLite database.
Heavy inspiration and borrowed patterns from:
- Security enhancements
- Evaluate Looking Glass as an alternative to dual-booting
- YubiKey setup
- Fix sway brightness hotkeys
- Wrong audio device picked as default
- BTRFS rollback not working