Claude Code runs on Node, whose V8 build requires SSE4.2 and POPCNT (x86-64-v2). On an older x86_64 CPU it does not run, so it must not be installed there in the first place. Nix cannot detect the CPU (pure evaluation, hosts often built elsewhere), so add features.cpu.microarchLevel: the psABI level a host declares about itself, defaulting to 2. features.claudeCode.enable derives from it, and home/claude.nix reads that through home-manager's osConfig and installs nothing -- CLI, CLAUDE.md, output style or memory symlink -- when it is off. Hosts without the option (Darwin, the standalone homeConfigurations) keep the tool enabled. An assertion fails evaluation if a host force-enables the flag below the required level, so the mistake surfaces in nix flake check rather than as an illegal-instruction crash on the machine.
233 lines
16 KiB
Markdown
233 lines
16 KiB
Markdown
# nixfiles
|
|
|
|
NixOS / nix-darwin / home-manager configuration for all hosts, built from a
|
|
single flake.
|
|
|
|
## Hosts
|
|
|
|
Defined in the host table in [`flake.nix`](./flake.nix):
|
|
|
|
| Configuration | System | Machine |
|
|
| --------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) |
|
|
| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](./hosts/T400/README.md) |
|
|
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./hosts/MacPro31/README.md) |
|
|
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) — [notes](./hosts/EDaaS/README.md) |
|
|
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](./hosts/RPi5/README.md) |
|
|
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) — [notes](./hosts/Darwin/README.md) |
|
|
|
|
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
|
|
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/`](./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`, 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`](./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`](./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`](./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>/`:
|
|
|
|
- [`users/lyrathorpe/home.nix`](./users/lyrathorpe/home.nix) — personal extras
|
|
(an ssh host shortcut, gammastep coordinates); imported on Lyra's hosts.
|
|
- [`users/emmathorpe/work.nix`](./users/emmathorpe/work.nix) — the work
|
|
toolchain (kubectl/helm/az/etc.), work-only LSP servers, the corporate ssh
|
|
handling, and the headless Secret Service that gcx needs for its keychain
|
|
tokens (see [`home/secret-service.nix`](./home/secret-service.nix)); imports
|
|
[`users/emmathorpe/renovate-review.nix`](./users/emmathorpe/renovate-review.nix),
|
|
the daily headless Renovate-PR review timer (EDaaS only).
|
|
|
|
### 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
|
|
|
|
```sh
|
|
# 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
|
|
|
|
- Interactive shell features (zsh, tmux, git, ssh, CLI tools, auto-tmux):
|
|
[`home/README.md`](./home/README.md).
|
|
- All Sway / tmux / foot / zsh keyboard shortcuts:
|
|
[`home/KEYBINDINGS.md`](./home/KEYBINDINGS.md).
|
|
|
|
## Login / greeter
|
|
|
|
Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running
|
|
ReGreet inside the `cage` kiosk compositor — implemented in
|
|
[`modules/sway.nix`](./modules/sway.nix), gated on
|
|
`features.swayDesktop.enable` (the option is declared in
|
|
[`modules/features.nix`](./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](https://github.com/tpwrules/nixos-apple-silicon)) into
|
|
`modules/firmware/` and commit with `git add -f`.
|
|
|
|
## 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`](./.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.
|