Files
nixfiles/README.md
T
Emma Thorpe dcc13f94e0
CI / flake (push) Skipped
CI / flake (pull_request) Successful in 4m21s
docs: move prose documentation into docs/ so the docs site publishes it
The docs-site build syncs this repo's README.md and docs/ into the site
tree; nothing else is copied. All prose apart from the README therefore lived
outside the sync and never appeared on https://docs.lyrapup.pet/nixfiles/, and
the one page that did publish carried 18 link targets that resolved to nothing.

Moves:

  home/README.md           -> docs/shell.md
  home/KEYBINDINGS.md      -> docs/keybindings.md
  hosts/<Name>/README.md   -> docs/hosts/<name>.md

docs/.pages and docs/hosts/.pages give the awesome-pages plugin an explicit
order; new pages are picked up by the trailing '...' without an edit.

Links are rewritten so a single URL is correct in both Gitea and the published
site: absolute Gitea source URLs for .nix files and directories, relative links
between pages under docs/, and absolute docs.lyrapup.pet URLs from the root
README, which the build republishes at a different depth from the rest of the
tree. In-code comments that pointed at a moved README are updated to the new
path.

The README gains a Documentation section covering the sync contract and the
linking rules, and CLAUDE.md carries the short version so future edits do not
reintroduce unsynced pages or dead links.

Verified by reproducing the docs-site assembly locally against its pinned
toolchain (mkdocs 1.6.1, mkdocs-material 9.7.7, awesome-pages 2.10.1): pages
render at the URLs used above and in the declared order.
2026-08-19 17:38:50 +01:00

19 KiB

nixfiles

NixOS / nix-darwin / home-manager configuration for all hosts, built from a single flake.

Hosts

Defined in the host table in flake.nix:

Configuration System Machine
lyrathorpe-mbp aarch64-linux MacBook Pro (Apple Silicon, Asahi)
lyrathorpe-t400 x86_64-linux ThinkPad T400 — install notes
lyrathorpe-macpro31 x86_64-linux Mac Pro 3,1, desktop — install notes
emmathorpe-edaas x86_64-linux Work WSL box (NixOS-WSL) — notes
lyrathorpe-rpi5 aarch64-linux Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — install notes
lyrathorpe-mac aarch64-darwin macOS (nix-darwin) — notes

Shared layers: home (home-manager: shell, git, editor), modules/common-nixos.nix (all NixOS hosts: fonts, nix-ld, caches), modules/workstation.nix (physical graphical hosts: audio, thermald, earlyoom, fwupd), modules/laptop.nix (laptops: Wi-Fi, Bluetooth, power, lid), modules/desktop.nix (wired desktops: NetworkManager), and modules/ssh.nix (key-only sshd). The x86 hosts also pull nixos-hardware profiles. The full module catalogue is below.

Repository layout

flake.nix              # inputs, mkHost/mkDarwinHost, the host tables, dev shell + checks
flake.lock             # pinned input revisions (Renovate keeps this fresh)
modules/               # reusable NixOS system modules (see "Module catalogue")
home/                  # home-manager profile: shell, git, editor, claude, secret-service, desktop, sway
docs/                  # all prose documentation; published to docs.lyrapup.pet (see "Documentation")
users/                 # identity registry + per-user home extras (see "Users")
hosts/<Name>/          # per-machine config: configuration.nix + hardware-configuration.nix
lib/                   # small pure helpers (currently the Catppuccin Mocha palette)
.gitea/workflows/      # CI (nix flake check + per-host eval)
statix.toml            # lint config (house-style lints disabled)
.editorconfig          # base whitespace style
tf-inspect/            # UNRELATED scratch project (gitignored, its own git repo);
                       #   RouterOS / home-services Terraform, not part of this flake

Each nixosConfiguration / darwinConfiguration is assembled in flake.nix from three layers: the shared baseModules (or darwinBaseModules), the per-form-factor and nixos-hardware modules listed in the host table, and the per-machine hosts/<Name>/configuration.nix. Home-manager is wired in as a system module; each user's home is composed from the homeModules list in that host's table entry.

Module catalogue

Reusable NixOS modules under modules/. "Imported by" says how a module reaches a host: baseModules (every NixOS host, via flake.nix), host table (listed explicitly per host in flake.nix), or transitively (pulled in by another module's imports).

Module Imported by What it does / when to use it
common-nixos.nix baseModules (all NixOS) Timezone/locale, store hygiene (auto-optimise, big download buffer, no auto-GC), the nix-community binary cache, nix-ld, sudo-rs in place of sudo, base CLI (git, fastfetch), and the fleet-wide font stack.
users.nix baseModules (all NixOS) Builds users.users from the registry for the host's hostUsers; enables zsh; enables Firefox + Thunderbird only when features.swayDesktop.enable is on. Applies per-user linger.
features.nix baseModules (all NixOS) Declares the feature-flag options (features.swayDesktop.enable, features.claudeCode.enable) so any host can read/set them without importing the heavy implementation module, plus the CPU capability fact they derive from (features.cpu.microarchLevel) and the assertion that guards it. See "CPU capability gating".
workstation.nix transitively (via laptop/desktop) Form-factor-agnostic base for physical graphical hosts: turns on swayDesktop, Dvorak console, PipeWire, firewall (default-deny), fstrim, earlyoom, fwupd, thermald (x86), redistributable fw.
laptop.nix host table (MBP, T400) imports workstation.nix, then adds the portable bits: iwd Wi-Fi, lid suspend/lock, Bluetooth + blueman.
desktop.nix host table (Mac Pro) imports workstation.nix, then swaps Wi-Fi for wired NetworkManager. Pair with portable = false in the host table.
sway.nix host table (graphical hosts) Implementation of features.swayDesktop: the system Sway package, the greetd/ReGreet (cage) greeter forced to Dvorak, xdg-portal, Wayland utility packages. Home-side Sway config is in home/sway.nix.
ssh.nix host table (T400, Mac Pro, RPi5) Enables sshd, opens port 22, enforces a key-only policy (no password / keyboard-interactive, no root). Authorized keys come from the registry via users.nix.
firmware/ referenced by MBP host config Committed Apple peripheral firmware blobs for the Asahi MBP (see "MacBook (Asahi) firmware").

Form-factor decision: a laptop imports laptop.nix (default portable = true); a wired desktop imports desktop.nix and sets portable = false; a headless server imports neither (leaves features.swayDesktop.enable at its default false) and adds only what it serves. portable is threaded through to home/sway.nix, which drops the battery block and brightness keys on desktops.

CPU capability gating

Not every host can run everything the fleet installs. Nix cannot probe the CPU (evaluation is pure, and a host may be built elsewhere), so each machine declares what it is and the shared modules derive from that:

  • features.cpu.microarchLevel — the x86-64 psABI level the CPU implements (1 = baseline, 2 = SSE4.2/POPCNT, 3 = AVX2, 4 = AVX-512). Defaults to 2; only a host older than that sets it (the Mac Pro 3,1's 2008 Harpertown Xeons are level 1). Ignored on non-x86_64 hosts.
  • features.claudeCode.enable — derived: on unless the host is below x86-64-v2, because Claude Code's Node runtime needs SSE4.2/POPCNT. home/claude.nix reads it through home-manager's osConfig and installs nothing (CLI, CLAUDE.md, output style, memory symlink) when it is off. Hosts with no such option — the Darwin host and the standalone homeConfigurations — fall back to enabled.
  • An assertion in features.nix fails evaluation if a host force-enables a flag its declared CPU level cannot support, so the mistake surfaces in nix flake check/CI rather than as an illegal-instruction crash on the box.

Adding another CPU-sensitive tool means deriving one more flag there, not editing every host.

Users

Identity is data, kept separate from the reusable modules:

  • users/registry.nix — one entry per user (display name, email, supplementary groups, authorized + signing keys). This is the single source of identity; no user data is hardcoded in the modules.
  • Each host's table entry declares a users set keyed by username; every entry lists that user's home-module composition (the shared ./home bundle plus any per-user modules, e.g. users/emmathorpe/work.nix) and optional per-host-user system bits such as linger.
  • mkHost builds each account from the registry and injects the matching identity into that user's home config as the identity module arg. A host can therefore declare any number of users.

Per-user home extras live under users/<name>/:

Portable home (off-NixOS / external consumers)

The home config is also exposed for use beyond these hosts:

  • homeConfigurations."<user>@<system>" — a standalone home-manager profile (the portable subset: shell + git + editor + claude) that can be activated on a machine this flake does not manage: home-manager switch --flake .#"lyrathorpe@x86_64-linux". The desktop/sway modules are intentionally excluded (they rely on a NixOS-provided Sway/Firefox binary).
  • homeModules — the reusable modules exported so another flake can import them (inputs.<this>.homeModules.default). Consumers must supply the module args these expect: inputs always, identity for git/desktop, portable for sway.

Applying

# NixOS
sudo nixos-rebuild switch --flake .#<configuration>
# Darwin
darwin-rebuild switch --flake .#lyrathorpe-mac

On a host whose networking.hostName matches its flake attribute (the WSL box and the Pi are set up this way), nh os switch resolves the configuration from the hostname with no --flake/-H flag.

Adding a new host

  1. Create hosts/<Name>/. Add configuration.nix with the host-specific bits only: networking.hostName, bootloader (firmware-specific — it is deliberately not set in the shared modules), and any per-machine hardware quirks. Keep anything reusable in modules/ instead.
  2. Hardware config. Generate hardware-configuration.nix on the real machine with nixos-generate-config and commit it. If the machine does not exist yet, commit a clearly-labelled placeholder so the host still evaluates in CI (see the existing T400 / RPi5 placeholders), and replace it at install. These files are excluded from the formatter and linters.
  3. Add a host-table entry in flake.nix. Under hosts (NixOS) or darwinHosts (macOS), set system, the modules list (host config + form factor + any nixos-hardware profiles), and the users map (each user's homeModules). Choose the form factor per the decision note above; a headless host imports neither laptop.nix nor desktop.nix.
  4. Users. If the host introduces a new person, add them to users/registry.nix first; otherwise reference an existing username.
  5. Verify. nix flake check formats, lints, and evaluates every host — including the new one — so a broken entry fails locally before CI. Then sudo nixos-rebuild switch --flake .#<configuration> on the machine.

No change to CI is needed: the host-eval step discovers hosts from the flake (attrNames of the configuration sets), so a new entry is picked up automatically.

Shell environment & keybindings

Login / greeter

Graphical (Sway) hosts log in through a Wayland greeter — greetd running ReGreet inside the cage kiosk compositor — implemented in modules/sway.nix, gated on features.swayDesktop.enable (the option is declared in modules/features.nix, so headless hosts can leave it off without importing modules/sway.nix). The greeter is forced to Dvorak to match the console and Sway session. Headless hosts (the WSL work box and the Raspberry Pi server) keep plain TTY login. The target account needs a password (passwd <user>) before it can log in.

MacBook (Asahi) firmware

The MBP host references modules/firmware/ for Apple peripheral firmware (Wi-Fi/Bluetooth). These blobs are committed (tracked) even though .gitignore lists the directory: the flake is git+file, so it only sees tracked files — untracking them breaks lyrathorpe-mbp evaluation (and the CI host-eval) because the config can't find the firmware. They are not redistributable; the repo is private.

To refresh them, copy the firmware extracted during the Asahi install (from /etc/nixos/firmware, or re-extract per the Asahi NixOS docs) into modules/firmware/ and commit with git add -f.

Documentation

All prose documentation lives in docs/; this README is the overview. The pages are published to https://docs.lyrapup.pet/nixfiles/ by the docs-site repository, which clones this repo on every build (on its own push, nightly, or on demand) and assembles the tree:

README.md   ->  docs/nixfiles/index.md     # this file becomes the section landing page
docs/       ->  docs/nixfiles/             # everything here, ordering from docs/.pages

Nothing is pushed from this side and there is no build step here — editing a page and merging is all that is required. Files outside docs/ (bar this README) are not synced, so a doc kept next to the code it describes will never appear on the site.

Linking rules

The site has no copy of the source tree, and this README is republished at a different depth from the rest of docs/. Both facts break naive relative links, so:

Link from To Use
anywhere a source file or dir absolute https://code.emmathe.dev/.../src/branch/main/…
a page in docs/ another page in docs/ relative (./keybindings.md) — correct in Gitea and on the site
this README a page in docs/ absolute https://docs.lyrapup.pet/nixfiles/…

mkdocs build runs non-strict on the docs-site side, so a broken link fails silently rather than failing the build. Check links by hand when moving a page.

Development

A dev shell and a formatting/lint gate are wired through the flake:

  • nix develop — shell with deadnix, statix, treefmt, and the git pre-commit hooks (installed automatically on first entry).
  • nix fmt — formats the tree via treefmt (nixfmt + shfmt + prettier; generated files and flake.lock are excluded).
  • nix flake check — runs formatting, deadnix, statix, the pre-commit hooks, and evaluates every host. .editorconfig carries the base style; statix.toml disables the two house-style lints (repeated_keys, empty_pattern).

CI

.gitea/workflows/ci.yaml runs nix flake check (formatting, deadnix, statix, the pre-commit hooks) and evaluates every NixOS and Darwin host configuration on push/PR. It always runs (no paths: filter) so the required check never hangs pending; the heavy Nix steps are skipped when a PR touches no .nix/lockfile/workflow file, and the job still reports green.