modules #
Everything the hosts are assembled from. Four of these directories declare a custom option namespace; the rest are plumbing.
| directory | namespace | evaluated in |
|---|---|---|
nixos/ |
myNixOS.* |
NixOS |
home/ |
myHome.* |
home-manager |
hardware/ |
myHardware.* |
NixOS |
snippets/ |
mySnippets.* |
both |
users/ |
NixOS | |
disko/ |
NixOS, via diskoConfigurations |
|
flake/ |
flake-parts |
how a module gets loaded #
flake/modules.nix hands each of nixos, home,
hardware, users, and snippets to import-tree, which imports every .nix
file beneath it. Creating the file is the whole registration step. There is no
import list, and adding one would break the pattern rather than tidy it.
So the directory structure is organisational only. Nesting says nothing about
the option path. nixos/services/tangled/knot.nix declares
myNixOS.services.tangled.knot because it says so, not because of where it sits.
Keep the two aligned anyway; every file in here does.
disko/ and flake/ are the exceptions. flake/modules.nix exports reusable
disk layouts; hosts/<host>/disko.nix selects a layout and supplies its by-id
drive paths. Helium's single-host layout lives there in full. flake/ is itself
the import-tree target of flake.nix.
Each host declares nixpkgs.hostPlatform in its own default.nix. CPU-vendor
modules configure drivers and firmware, not the architecture.
the shape of a module #
{ config, lib, ... }:
let
cfg = config.myNixOS.services.foo;
in
{
options.myNixOS.services.foo.enable = lib.mkEnableOption "what this is";
config = lib.mkIf cfg.enable {
# ...
};
}
A module aliases its own subtree as cfg. System, home and hardware modules
take self, never inputs, because flake/hosts.nix passes only the former in
specialArgs. A package built from an input gets re-exported through
flake/packages.nix and reached as self.packages.<system>.<name>. An input's
modules are wired in by flake/hosts.nix, not imported from a module. Only an
input's source tree, which is not a package, is read as self.inputs.<name>,
as nix-search.nix does to point manix at this flake's nixpkgs. In-repo files are
"${self}/keys/foo.pub", not relative paths.
Use lib.mkDefault for shared settings a host may override, such as locale,
timezone or boot-loader choices. Reserve lib.mkForce for conflicting modules
where one must win, such as lanzaboote disabling systemd-boot.
Option descriptions carry the reasoning. See the comment style section of AGENTS.md for what that means in practice.
the sub-namespaces #
profiles/bundles otherenables, keyed on role (server,workstation) or on a subsystem every host wants a coherent answer for (btrfs,swap,audio). A host picks one and names its exceptions. Both role profiles enablebase, so anything true of every machine goes there.programs/configures a single program.services/is a daemon, usually with anetworkMapentry, an agenix secret, and a Caddy vhost in the host'sproxy.nix.
snippets hold data, no config #
A file under snippets/ declares options and sets no config. It is a registry,
imported into both the NixOS and the home-manager module sets so that two sides
of the same fact cannot drift:
mySnippets.biscuits-at.networkMapholds domain, port, and SSH details per service. The knot module and the SSH client config read the same port from here. Entries require a nonemptydomainand aportfrom 1 through 65535. SSH entries require bothsshDomainandsshPort. Unknown fields fail evaluation. Add a service here and read the entry from both its module and proxy; never write a port literal into a Caddy vhost.mySnippets.tailnetholds the tailnet name and the hostname to Tailscale IP map. Backups use it to address the backup server, and fleet assertions require its names to match the build roster.mySnippets.ssh.knownHostspins host keys. Each entry is written by hand, reading../keys/root_<host>.puband the tailnet map.mySnippets.monitoringholds the monitoring server's hostname, the exporter and store ports, and the two roster lists that refine what happens to a host.alwaysOnhosts alert on absence (Argon, Carbon, Helium). An offline workstation is normal.physicalDiskshosts run SMART monitoring (Helium, Tungsten, Uranium), since the OVH guests expose virtual disks. The client module assertsphysicalDisksagrees withservices.smartd.enable.
fleet agreement #
The build roster in flake/hosts.nix, the tailnet map and the per-host borg
rules in secrets/secrets.nix must contain the same names. nixos/fleet.nix
asserts this on every host. It also checks that shared tailnet secret rules
include every host and the recovery YubiKey, and that each configured age file
exists with a rule naming its consuming host and the YubiKey.
These checks inspect rules and public keys. They cannot prove that an encrypted file was rekeyed after its rule changed. Follow the secrets procedure and run the key recovery test after changing recipients.
nix eval --json .#restoreCoverage lists every enabled backup client and each
repository's restore coverage. flake/restore.nix derives this plan from the
host configurations. See the restore procedure
for local repository omissions and workstation rehearsals.
modules with their own README #
home/desktop/niri/covers the minimal Niri and Noctalia workstation session and its shortcuts.nixos/services/backups/covers borg and borgmatic, provisioning a host into the backup set, and the three tiers of restore testing. It is the highest-consequence module here. Read it in full before editing.nixos/services/monitoring/covers the Prometheus, Loki and Grafana stack on Helium and the exporters and Alloy on every host.