Imported from masood09/nix (
AGENTS.md). Install upstream withnpx skills add masood09/nix. Copyright stays with the author.
AGENTS.md
This repository is a multi-machine Nix Flake for a homelab: NixOS servers, NixOS desktops, and macOS machines managed with home-manager, nix-darwin, disko, sops-nix, ZFS, and impermanence.
Use this file as the fast-start guide for coding agents working in /home/masoodahmed/code/nix.
Scope And Layout
- Entry point:
flake.nix - Machine definitions:
machines/<name>/ - Shared NixOS modules:
modules/nixos/ - Shared Home Manager modules:
modules/home-manager/ - Shared Darwin modules:
modules/macos/ - Service modules:
modules/services/ - Custom NixOS service modules:
nix/services/ - Custom packages:
nix/pkgs/ - Shared helpers:
lib/ - Operational docs:
docs/ - Secrets:
secrets/andmachines/*/*.sops.yaml
Agent Priorities
- Prefer minimal, local changes that match existing patterns.
- Do not invent a new module structure if an adjacent module already shows the right pattern.
- Preserve cross-platform behavior: NixOS and Darwin share some option schemas but not all implementations.
- Never hardcode secrets; use
sops-nixsecret paths. - Before adding a new service, check
docs/service-registry.orgfor UID/GID and port allocations.
Build, Lint, And Validation Commands
Primary commands live in justfile.
- List available tasks:
just - Format repo:
just fmt - Check formatting only:
nix fmt -- --check . - Lint repo:
just lint - Direct linter invocation:
statix check . - Run standard local validation:
just preflight - Run full validation (all hosts):
just test - Update flake inputs:
just up - Build installer ISO:
just build-iso - Garbage collect:
just gc(orjust gc age=30d) - Verify/repair the nix store:
just repair - Rotate sops keys:
just sops-rotate(requires a clean git tree); update key files:just sops-update
Deployment Is Not An Agent Action
just deploy, just machine=<name> deploy, darwin-rebuild switch, and nixos-rebuild switch are user-run commands. Stop at just preflight and hand back. The user runs activation themselves so they can watch the output and choose when to apply. Only run a deploy command if the user explicitly asks for it in that turn.
Test Strategy
There is no conventional unit-test suite in this repo. Validation is mostly Nix evaluation, targeted builds, and deployment-safe checks.
- Full fast validation:
just test(wraps./test-flake.sh) - Flake metadata:
nix flake metadata - Show flake outputs:
nix flake show - Parse one file for syntax:
nix-instantiate --parse path/to/file.nix
Running A Single Test
Because this repo is configuration-heavy, a “single test” usually means evaluating or building one host, one output, or one file.
- Evaluate one NixOS host:
nix eval .#nixosConfigurations.heartbeat.config.system.name - Build one NixOS host:
nix build .#nixosConfigurations.heartbeat.config.system.build.toplevel - Evaluate one Darwin host:
nix eval .#darwinConfigurations.work-okta.system.primaryUser - Build one Darwin host:
nix build .#darwinConfigurations.work-okta.system - Build one custom package:
nix build .#mailarchiver - Check one Nix expression parses:
nix-instantiate --parse modules/services/mailarchiver/default.nix
When changing only one machine or service, prefer the narrowest command that exercises that change first.
Recommended Validation By Change Type
- Nix formatting/style only:
nix fmt -- --check . && statix check . - Shared module change:
just preflightplus at least one affected host eval/build - Machine-only change: evaluate or build only that machine
- Service module change: evaluate/build a machine that enables that service
- Flake wiring change: run
just test - Installer change:
just build-iso
Important Machines
NixOS servers (built with mkNixOSConfig):
accesscontrolsystem— Authentik SSOcaretaker— DNS, Tailscale (non-ZFS: LUKS + ext4)commrelay— Matrix, monitoringheartbeat— NAS, primary servicesmeshcontrol— Headscale VPN controltrialunit— secondary serviceswatchfulsystem— monitoring, uptime
NixOS desktops (built with mkNixOSDesktopConfig, which adds niri/noctalia/zen-browser):
usul— primary laptop (non-ZFS)sonic— family laptop, dual-user (non-ZFS)
Darwin hosts (mkDarwinConfig):
work-okta— the sole macOS machine; has no_config.nix(options live indefault.nix)
Other:
nixiso— minimal installer ISO, built directly bynixpkgs.lib.nixosSysteminflake.nix
Formatting Rules
- Formatting is handled by
alejandravianix fmt. - Linting is handled by
statix. - Do not hand-format in a way that fights the formatter.
- Keep files ASCII unless the file already uses Unicode and there is a good reason.
Nix Style Guidelines
- Use fully nested attribute sets, not dotted attribute assignment for nested config.
- Preferred:
services = {
greetd = {
enable = true;
};
};
- Avoid:
services.greetd = {
enable = true;
};
- Keep attribute trees visually shallow and grouped by subsystem.
- The rule is absolute outside
disko/. It applies to option trees, to third-party settings DSLs (niriaction.*,animations.*.kind), and to paths with interpolated keys (certs.${domain},users.${userName}) alike. - The only exemption is disko disk layout (
modules/nixos/disko/,machines/*/disko/), wheredisk.root/zpool.rpoolmatch upstream disko idiom. - Use one attribute per line for lists unless the list is tiny and already formatted that way.
- Favor
inherit (...) foo bar;when it reduces repetition and keeps ownership clear.
Imports And File Organization
- Use
default.nixas the entry point for directories. - Files prefixed with
_are private/internal helpers, such as_config.nixor_networking.nix. - In machine
default.nixfiles, keep local machine imports together, then shared module directories. - In module directories, import submodules unconditionally and gate behavior inside each module.
- Do not make
importsdepend onconfigorpkgs; that can trigger module recursion.
Module Patterns
- Typical argument set order is
{ config, lib, pkgs, ... }:or a small variant. - Use
letbindings forcfg = ...,homelabCfg = config.homelab, and other repeated expressions. - Define options with
lib.mkOptionandlib.mkEnableOption. - Use explicit Nix types:
lib.types.str,lib.types.int,lib.types.port,lib.types.enum,lib.types.nullOr,lib.types.listOf,lib.types.attrsOf. - Use
lib.mkIfto gate optional config. - Use
lib.mkMerge,lib.optionalAttrs, andlib.optionalsto compose conditional pieces. - For cross-platform Home Manager modules, guard platform-specific namespaces with
options.<path> ? <name>orlib.optionalAttrs.
Naming Conventions
- Repository option namespace:
homelab.* - Shared machine identity lives under
homelab.role,homelab.purpose,homelab.primaryUser, andhomelab.networking. - Local module variables usually use
cfgfor the module config andhomelabCfgfor top-level repo config. - Service modules typically live in
modules/services/<service>/withdefault.nixplusoptions.nixand supporting files. - Custom packages live in
nix/pkgs/<name>/default.nixand are auto-discovered.
Types, Defaults, And Option Design
- Prefer declarative options over ad hoc literals in machine configs.
- Add sane defaults where they are truly global; otherwise require explicit values.
- Use enums instead of free-form strings when the valid values are known.
- Keep shared schema in
modules/shared/options.nix; keep platform-specific implementation inmodules/nixos/ormodules/macos/.
Error Handling And Safety
- Prefer
assertionswith clear messages for invalid configuration combinations. - Gate dependent services explicitly, for example requiring PostgreSQL or MongoDB when needed.
- Use
lib.mkIfrather than partial config that silently misconfigures a service. - For services not backed by upstream NixOS modules, set
isSystemUser = true;andgroup = "<name>";. - Allocate service UID/GID and ports from
docs/service-registry.orginstead of inventing new values.
Secrets, Persistence, And ZFS
- Reference secrets through
config.sops.secrets."path/to/secret".path. - Do not commit plaintext credentials, tokens, or generated secret material.
- Use
lib/persistence-helpers.nixfor impermanence bind-mount persistence. - Use
lib/systemd-helpers.nixfor permission-fixing oneshot services. - Use
lib/zfs-options.nixand existing ZFS dataset patterns when adding persisted service data.
Comments And Documentation
- Keep the existing style of concise, high-signal comments.
- Add comments when the constraint is non-obvious, cross-platform, or operationally important.
- Prefer explaining why a rule exists, not narrating obvious syntax.
Policies Defined In CLAUDE.md
CLAUDE.md is the authoritative source for several repo policies that are easy to violate accidentally. Read it before touching these areas:
- Binary caches / flake inputs — inputs with their own cachix cache must not use
inputs.nixpkgs.follows, and must be consumed via theirpackagesattr, not their overlay. - Darwin / Homebrew — nixpkgs and Home Manager are the default on
work-okta; a Homebrew cask is a fallback that needs aTECH DEBT:comment naming the blocker. - Stylix — one system-level source of truth (
modules/nixos/_stylix.nix,modules/macos/_stylix.nix) propagating to HM. Servers gate GTK/Qt/KDE/GNOME targets off; removing those gates adds ~1 GB to every server closure. - Linux-only HM behavior — gate
dconf, fontconfig files, and dbus-dependent activation behindlib.mkIf pkgs.stdenv.isLinux. - Research routing — use the
nixosMCP server for option schemas and package availability rather than guessing from training data.
Rule Files Checked
- Existing repo-specific agent guidance:
CLAUDE.md - Cursor rules: none found in
.cursor/rules/or.cursorrules - Copilot instructions: none found at
.github/copilot-instructions.md
Practical Workflow For Agents
- Read the nearest similar file before editing.
- Make the smallest change that preserves current architecture.
- Run narrow validation first, then broader validation if the change touches shared code.
- If you add or modify a service, verify registry assignments and dependency assertions.
- If you touch shared module plumbing, validate at least one Linux host and one Darwin host when relevant.