Imported from antob/nixos-config (
AGENTS.md). Install upstream withnpx skills add antob/nixos-config. Copyright stays with the author.
Agent Guidelines for nixos-config
A multi-host, flake-based NixOS configuration with Home Manager integration.
All custom options live under the antob.* namespace.
Where to Look (read this before scanning)
-
This repo is the source of truth for all config. Never run
find,ls -R, orgrep -racross/nix/store,/, or the whole filesystem. Those trees are huge and slow. -
Do not build to resolve questions.
just build / switch / test / boot / deployandnixos-rebuild ...write to the store and change running system state. Only run them when the user explicitly asks you to. Never run them to inspect current configuration. -
resultis an ignored symlink at the repo root pointing to the most recent local build. The path encodes the host:/nix/store/<hash>-nixos-system-<host>-.... Resolve it withrealpath resultorjust store-result, and trust it only if it names the host you are working on (it may point to another host built previously). If it is missing or names a different host, do not rebuild. Ask the user to build the host you need. -
flake.nixdeclares the inputs/channels and which host subsystems they use. -
flake.lockpins every input to a committed revision. For a one-off lookup usejust store-rev <input>(e.g.nixpkgs,nixpkgs-stable), or read it directly:jq -r '.nodes.<name>.locked.rev' flake.lockjust bump-prevre-pinsnixpkgs-prevto the locked rev of the current system. -
modules/,hosts/,pkgs/,overlays/, andlib/hold the actual config. Start there, never in the store. If you must touch/nix/store, start from theresultsymlink or a concretely named store path, never from the/nix/storeroot. Thestore-*steps are the only allowed store-adjacent lookups; thebuild/switch/test/boot/deploysteps are state-changing and off-limits for info-gathering.
Build / Lint / Format Commands
The primary task runner is just (see justfile).
| Command | Description |
|---|---|
just build [host] |
Build current or named host config (nixos-rebuild build) |
just switch [host] |
Apply config to running system (nixos-rebuild switch) |
just test [host] |
Apply without creating boot entry (nixos-rebuild test) |
just boot [host] |
Apply on next boot (nixos-rebuild boot) |
just deploy <host> <target> [mode] |
Remote deploy via SSH |
just fmt |
Format all .nix files (nix fmt, uses nixfmt-tree) |
just iso [type] |
Build install or minimal ISO |
Read-only store lookups (safe to run):
| Command | Description |
|---|---|
just store-result |
Resolve result symlink to the most recent local build |
just store-rev <input> |
Print the pinned revision of a flake input from flake.lock |
just nixpkgs-src |
Print the store path of the pinned nixpkgs source |
These store-* / nixpkgs-src commands only read state and change nothing. The build / switch / test / boot / deploy / iso commands write to the store and change system state; do
not run them to gather information. Ask the user when you need a host built or
switched.
There is no test suite and no CI. Correctness is validated by
nixos-rebuild build (dry run) or nixos-rebuild test on the target machine.
Reading nixpkgs source & option definitions (fast)
To understand what an upstream option does or where a package is defined, do NOT
grep/find across /nix/store or the whole filesystem. Those trees are huge and
slow. The pinned nixpkgs source already exists in the store; resolve it to a
concrete path first, then search only inside that path.
NIXPKGS=$(just nixpkgs-src) # e.g. /nix/store/<hash>-source (pure eval, <1s)
Then grep/read against $NIXPKGS directly, never against /nix/store itself:
rg -l "options\\.programs\\.vscode" "$NIXPKGS/nixos/modules/programs"
less "$NIXPKGS/nixos/modules/programs/vscode.nix"
Useful directories inside the source:
| Path | Contents |
|---|---|
nixos/modules/ |
NixOS module/option definitions |
nixos/modules/services/ |
Service options (systemd, web, …) |
nixos/modules/programs/ |
Program options (vscode.nix, firefox.nix, …) |
pkgs/top-level/all-packages.nix |
Central package attribute map |
pkgs/by-name/ |
Package definitions keyed by name |
lib/ |
nixpkgs lib helpers (mkOption, mkIf, …) |
nixos/modules/misc/ |
Core system options (system.nixos, …) |
To inspect what an option resolves to (not just its default), evaluate the configuration read-only instead of building:
nix eval --json .#nixosConfigurations.desktob.config.services.openssh.enable
Prints true/false (use --json for non-string option values; --raw only
prints strings). This runs a full evaluation (~10s the first time, cached
afterwards) but writes nothing to the store. Prefer nix eval over
nixos-rebuild build when answering "what value does option X have on host
Y?".
The alternate channels (nixpkgs-stable, nixpkgs-next, nixpkgs-prev) are
separate flake inputs and have their own store paths. When a question targets
those specifically, look in overlays/default.nix to see how they are wired
to pkgs.stable.* / pkgs.pkgs-next.* / pkgs.pkgs-prev.*, then resolve the
evaluated package set the same way (e.g. nix eval --raw .#nixosConfigurations.desktob.pkgs.stable.path).
Repository Structure
flake.nix # Flake entry: all inputs and outputs
flake.lock # Pins every input to a locked revision
justfile # Task runner
docs/ # Documentation (empty at present)
result # Ignored symlink to the most recent local build in /nix/store
hosts/ # Per-machine NixOS configs
common/ # Shared secrets (sops-encrypted secrets.yaml)
<host>/ # e.g. desktob, laptob, laptob, hyllan, wiggum, pihole, pikvm
default.nix # Host options entry point
hardware.nix # Machine hardware config
disk-config.nix # Partition layout (desktop/laptop family)
secrets.yaml # Host sops secrets
public_key.asc # SSH/age public key
modules/ # All custom NixOS/HM modules under antob.*
apps/ # GUI applications (firefox, vscode, thunderbird, …)
cli-apps/ # CLI tools (neovim, helix, tmux, yazi, llm-agents, …)
color-scheme/ # Color schemes (catppuccin, gruvbox, tokyonight, …)
debug/ # Debug helpers (track-changes)
desktop/ # Window managers (hyprland, niri, cosmic, gnome, plasma, mango, …)
addons/ # Shared WM addons (waybar, mako, rofi, gtk), keyring, …)
scripts/ # Shared WM scripts
features/ # High-level presets (common, common-minimal, desktop, gaming, laptop, rpi)
hardware/ # audio, bluetooth, fingerprint, ledger, networking, ddcutil, yubikey, …
home/ # Home Manager integration wrapper
monitoring/ # Monitoring email defaults
nix/ # nixpkgs settings, overlay wiring, nix access tokens
persistence/ # Persistent storage (preservation module)
security/ # Hardening (gpg)
services/ # avahi, openssh, tailscale, ollama, syncthing, restic-backup, …
system/ # console, env, fonts, info, locale, time, zfs
tools/ # CLI/user tools (zsh, git, starship, alacritty, fzf, …)
user/ # Default user account, default icon
virtualisation/ # docker, podman, virt-manager
overlays/ # nixpkgs overlays (stable, pkgs-next, pkgs-prev, additions, modifications, flake-inputs)
pkgs/ # Custom packages
lib/ # Custom library functions extending nixpkgs lib
Code Style
Formatting
- 2 spaces for indentation throughout, no tabs.
- Formatter:
nixfmt-tree(wrapper aroundnixfmt). Runjust fmtbefore committing. - File names:
kebab-case(e.g.open-webui.nix,color-scheme/). - Variable names:
camelCasefor local bindings (cfg,gtkCfg,emailFrom). - NixOS option names:
camelCaseas per upstream convention.
Module Signature
Every module uses the standard NixOS module function signature with arguments on separate lines:
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.antob.<category>.<module>;
in
{
options.antob.<category>.<module> = { ... };
config = mkIf cfg.enable { ... };
}
Only add inputs or outputs to the argument set when actually needed.
with lib;
Always open with with lib; at the top of any file using lib functions. This brings
mkIf, mkOption, mkEnableOption, mkMerge, types, enabled, disabled, etc.
into scope without qualification.
let...in Blocks
Use a let block to bind cfg = config.antob.<module>; as the first line of every
module. Derive other computed values there (e.g. package selections, path strings).
Options Definition
options.antob.tools.git = with types; {
enable = mkEnableOption "Whether or not to install and configure git.";
userName = mkOpt str user.fullName "The name to configure git with.";
userEmail = mkOpt str user.email "The email to configure git with.";
};
- Use
mkEnableOptionfor boolean on/off flags. - Use the custom helpers from
lib/:mkOpt type default description,mkBoolOpt default description,mkOpt'/mkBoolOpt'when a description is not needed. - Always supply a description string as the third argument to
mkOpt. - Use
with types;inside option blocks to avoid repeatingtypes..
Custom Lib Helpers
Defined in lib/default.nix and available wherever lib is in scope:
lib.mkOpt # mkOption shorthand: type → default → description → option
lib.mkBoolOpt # mkOpt types.bool shorthand
lib.enabled # { enable = true; }
lib.disabled # { enable = false; }
lib.relativeToRoot # lib.path.append from repo root
lib.scanPaths # auto-discover .nix files in a directory (exclude default.nix)
lib.getFiles # list plain files under a path
lib.mkSslProxy # Caddy vhost builder with Let's Encrypt DNS challenge
lib.mkProxy # Caddy vhost builder with internal TLS
Config Guards
Every module must guard its config block with mkIf cfg.enable:
config = mkIf cfg.enable {
# …
};
For modules with multiple independent flags, use mkMerge:
config = mkMerge [
(mkIf cfg.enable { … })
(mkIf cfg.enableCache { nix.settings = { … }; })
];
enabled / disabled Shorthand
Use the enabled / disabled helpers in host configs instead of { enable = true; }:
antob = {
features.common = enabled;
hardware.bluetooth = enabled;
tools.git = enabled;
services.openssh = disabled;
};
Auto-Discovery (scanPaths)
Every category directory (e.g. modules/tools/default.nix) should import child
modules via lib.scanPaths:
{ lib, ... }: { imports = lib.scanPaths ./.; }
This auto-discovers all .nix files except default.nix. Adding a new module only
requires placing a file in the correct directory, with no import list to update.
Home Manager Integration
Home Manager runs as a NixOS module, not a standalone flake output. Modules add
home-manager config through the unified antob.home API:
config = mkIf cfg.enable {
antob.home.extraOptions = {
programs.git.enable = true;
};
# or for file management:
antob.home.configFile."some/path".source = ./file;
};
Never access home-manager.users.<name> directly from within a module; always go
through antob.home.*.
Overlays and Multiple nixpkgs Channels
Custom packages and patched packages live in overlays/. The overlays expose
alternate channels as top-level attrs on pkgs:
pkgs.stable.<pkg> # from nixpkgs-stable
pkgs.pkgs-next.<pkg> # from nixpkgs-next (newer unstable)
pkgs.pkgs-prev.<pkg> # from nixpkgs-prev (pinned previous)
Use these when a specific package version is needed from a different channel.
Custom Packages
Packages in pkgs/ use pkgs.callPackage or pkgs.writeShellScriptBin. They are
exposed via the additions overlay. Use pkgs.<name> to reference them from modules.
Secrets (SOPS)
- Secrets are encrypted YAML files under
hosts/<name>/secrets.yamlandhosts/common/secrets.yaml. - Each host declares
sops.defaultSopsFileand individualsops.secrets.<key>options. - Access a secret at runtime with
config.sops.secrets.<key>.path. - Never hardcode secrets or place plaintext credentials in
.nixfiles.
Error Handling
- Use
lib.mkForceat host level to override module defaults (e.g.mkForce false). - Use
lib.optionalString,lib.optional, andlib.optionalsfor conditional lists/strings. - Use NixOS
assertionsfor runtime validation where correctness cannot be enforced by types. - No explicit
throw/abortunless truly unrecoverable.
Naming Conventions Summary
| Thing | Convention | Example |
|---|---|---|
| File names | kebab-case |
open-webui.nix |
| Directory names | kebab-case |
cli-apps/ |
| Local Nix bindings | camelCase |
cfg, gtkCfg |
| NixOS options | camelCase |
hostName, storageDriver |
| Custom option namespace | antob.* |
antob.tools.git.enable |
| Host names | kebab-case |
laptob, desktob |
Key Architectural Decisions
antob.*namespace all custom options live underantob, to prevent conflicts with upstream NixOS options.- Feature presets the
antob.features.*modules act as presets.common,common-minimal,desktop,gaming,laptop, andrpienable many sub-modules at once. Hosts opt into presets instead of enabling every module individually. - Impermanence. Root filesystem is ephemeral. Persistent data lives at
antob.persistence.path(default/persist), and the.safesubfolder is additionally backed up offsite. Some hosts override the path. System and user state is preserved via thepreservationmodule, not written into the ephemeral root. - No CI. Validate with
just build <host>locally; there is no automated test pipeline.
