NixOS, nix-darwin, and Home Manager flake managing multiple systems declaratively. Mixin pattern for composable modules. Builds workstations, servers, VMs, macOS systems, and ISO images.
A NixOS MCP server is available and must be used as the primary reference for NixOS, Home Manager, and nix-darwin options, packages, and modules - do not rely on training data for these.
| Purpose | Tools |
|---|---|
| NixOS options/packages | mcp__nixos__nixos_search, mcp__nixos__nixos_info |
| Home Manager options | mcp__nixos__home_manager_search, mcp__nixos__home_manager_options_by_prefix, mcp__nixos__home_manager_list_options |
| nix-darwin options | mcp__nixos__darwin_search, mcp__nixos__darwin_options_by_prefix, mcp__nixos__darwin_list_options |
| Package versions | mcp__nixos__nixhub_package_versions, mcp__nixos__nixhub_find_version |
| Flake search | mcp__nixos__nixos_flakes_search |
Run just --list to see all available recipes with descriptions.
just install handles SOPS age key injection, SSH host key decryption, and LUKS setup. Optional: keep_disks="true", vm_test="true". just inject-tokens optional: user="nixos".
- British English spelling; comments use full sentences with proper punctuation
nixfmtformatter (run vianix fmt)- Prefer
lib.mkDefault/lib.mkForceover plain values for overridability lib.optional/lib.optionalsfor conditional imports- Explicit
inheritstatements for clarity - String interpolation:
"${variable}"notvariable - camelCase for Nix attributes and functions, kebab-case for files and directories
- Hostnames: Sith Lords (workstations/servers), TIE fighters (VMs)
common/default.nix- shared cross-platform config, imported by NixOS and nix-darwin_mixins/directories - self-contained modules, each withdefault.nixentry pointnixos/{hostname}/,darwin/{hostname}/- host-specific configs (disks, kernel modules)pkgs/- custom packages, exposed via overlay; register inpkgs/default.nixnixos/_mixins/scripts/,home-manager/_mixins/scripts/- shell scriptshome-manager/_mixins/scripts/_template/- script template
Shell scripts use pkgs.writeShellApplication (never writeShellScriptBin). Each script lives in its own directory: script-name/default.nix + script-name.sh. Runtime dependencies go in runtimeInputs. Shellcheck validation is automatic.
Mixin placement: system-level (services, kernel, boot, networking) in nixos/_mixins/; user-level (programs, dotfiles, scripts) in home-manager/_mixins/. Use home.packages in Home Manager, environment.systemPackages in NixOS.
All systems defined in lib/registry-systems.toml, users in lib/registry-users.toml (read via builtins.fromTOML). Full field reference: lib/registry-systems-schema.json and lib/registry-users-schema.json.
ISO hosts use tags = ["iso"] which implies desktop = null, username = "nixos". The ISO host is nihilus.
resolveEntry in lib/flake-builders.nix merges four layers: baseline username, kind+OS derived desktop, ISO defaults, then explicit values. The registry entry flows through resolveEntry -> mkSystemConfig -> mkNixos/mkHome/mkDarwin -> noughty.* options. No other files need updating when adding a host.
All host/user metadata accessed via config.noughty.*, not specialArgs. Provides type checking, defaults, and mkDefault/mkForce overridability.
Only four specialArgs: inputs, outputs, stateVersion, catppuccinPalette.
Module shorthand: use inherit (config.noughty) host; in let bindings (not cfg).
{ config, noughtyLib, lib, ... }:
let
inherit (config.noughty) host;
in
lib.mkIf host.is.workstation {
# host.name, host.desktop, host.is.laptop, host.gpu.hasNvidia, etc.
}Key gating patterns:
host.is.workstation,host.is.server,host.is.laptop,host.is.iso,host.is.vm,host.is.darwin,host.is.linuxhost.gpu.hasNvidia,host.gpu.hasCuda,host.gpu.hasAmd,host.gpu.hasROCmhost.display.primaryOutput,host.display.isMultiMonitornoughtyLib.isUser [ "martin" ],noughtyLib.isHost [ "skrye" "zannah" ]noughtyLib.hostHasTag "studio",noughtyLib.userHasTag "developer"
Full reference: lib/noughty/README.md.
Flat pattern (~95% of modules): lib.mkIf condition { ... } as the entire module body.
Long-form pattern (hub modules with imports):
{ imports = [...]; config = lib.mkIf condition { ... }; }Imports stay unconditional; each sub-module gates itself. Never use lib.optional config.noughty.* ./foo in imports - causes infinite recursion.
Secrets encrypted with sops-nix using age keys.
- User key:
~/.config/sops/age/keys.txt - Host key:
/var/lib/private/sops/age/keys.txt - Edit:
sops secrets/secrets.yamlorsops secrets/host-{hostname}.yaml - Rekey:
sops updatekeys secrets/secrets.yaml
just inject-tokens sends age keys to /tmp/injected-tokens/ on ISO media. Both keys are hard requirements for install-system.
Catppuccin Mocha palette via catppuccinPalette (passed in specialArgs):
getColor "base"- hex with#getHyprlandColor "blue"- hex without#colors- raw palette attribute setisDark- true for mochapreferShade-"prefer-dark"for dark themes
Three overlays applied in order:
localPackages- custom packages frompkgs/modifiedPackages- overrides and patches to nixpkgsunstablePackages- nixpkgs-unstable viapkgs.unstable
Four workflows in .github/workflows/ - read them to understand CI behaviour. Auto-merge of update PRs is gated on all build jobs passing.
- Infinite recursion -
config.noughty.*used insideimports; move toconfigblock - Home Manager activation fails - file conflicts; use
home-manager switch -b backup --flake . - Secrets not accessible - verify age keys exist; check
.sops.yamlrecipients; runsops updatekeys - Package not found - verify in
pkgs/default.nixand overlay applied; usemcp__nixos__nixos_searchto confirm package name - Shellcheck failure - use
writeShellApplication(notwriteShellScriptBin); add deps toruntimeInputs
- Never modify
flake.lockdirectly; usejust update - Never change
stateVersionon existing systems - Never commit unencrypted secrets outside
secrets/ - Never use
environment.systemPackagesin Home Manager; usehome.packages - Never use
writeShellScriptBin; usewriteShellApplication - Never use
config.noughty.*insideimports; use the long-formconfig = lib.mkIfpattern - Run
just evalbefore committing to catch evaluation errors - Keep each mixin self-contained with a single concern