Author SHA1 Message Date
Emma ThorpeandClaude Opus 4.8 87318cd04d fix(docs): reformat README module table to satisfy treefmt
CI / flake (pull_request) Successful in 10s
The module-catalogue table in README.md was committed without prettier's
alignment, so the treefmt formatting flake check fails. This has left
CI (nix flake check) red on main since the #56 docs merge and blocks
every PR that triggers the full check.

Reformat with the flake's pinned formatter; no content change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:10:02 +01:00
lyrathorpe ad9decdf47 Merge pull request 'docs: repo layout, module catalogue, host READMEs, stale-path fixes' (#56) from docs/audit-improvements into main
CI / flake (push) Failing after 4m2s
Reviewed-on: #56
2026-07-06 15:37:28 +01:00
lyrathorpe 819633260e docs(rpi5): fix stale module paths
CI / flake (pull_request) Successful in 19s
2026-07-06 15:27:03 +01:00
lyrathorpe 610d5d8b28 docs(macpro31): fix stale module paths 2026-07-06 15:27:02 +01:00
lyrathorpe 58c0004f20 docs(t400): fix stale module paths 2026-07-06 15:27:02 +01:00
lyrathorpe f57d6ab1f9 docs(darwin): add host README 2026-07-06 15:27:01 +01:00
lyrathorpe 574773de73 docs(edaas): add host README 2026-07-06 15:27:00 +01:00
lyrathorpe a857365cc3 docs: add repo layout, module catalogue and add-a-host guide 2026-07-06 15:26:59 +01:00
lyrathorpeandEmma Thorpe c06a57f249 chore: post-refactor cleanups (#50-#53) (#54)
CI / flake (push) Successful in 3m30s
## Summary

Follow-up cleanups from the post-refactor audit (issues #50–#53). All behaviour-preserving except the work-host changes (kube-tmux + Lens removal).

## Changes

- **#51** `refactor(ssh)` — move `services.openssh.enable` + `firewall.allowedTCPPorts = [ 22 ]` into `modules/ssh.nix`; drop the duplicated lines from T400, MacPro31, RPi5.
- **#50** `fix/feat(work)` — load kube-tmux from a pinned `flake = false` input (it is not in nixpkgs) and reference `${inputs.kube-tmux}/kube.tmux` directly, so the status line no longer depends on a manual `$HOME/code/kube-tmux` checkout. (Supersedes the interim file-existence guard.)
- **#52** `chore` — gitignore the untracked `tf-inspect/` scratch project.
- **#53** `chore` — remove the unused Lens package entirely (`pkgs.lens` + its unfree entry; `unfreePackages` is now just `claude-code`), fix the `nil`→`nil_ls` LSP doc, remove the redundant `.editorconfig` block, name the RPi5 Docker subnet in a `let` binding.

## Deferred (from #53, noted in the commit)

- `.gitignore` firmware entry — documented behaviour, low value, left as-is.
- Per-eval `nixpkgs-unstable` overlay import — inherently per-system; no clean single-import hoist.

## Verification

- `nix flake check` passes (treefmt, deadnix, statix, pre-commit, all hosts + Darwin + homeConfigurations).
- Derivation-path diff vs `main`: `lyrathorpe-mbp`, `lyrathorpe-t400`, `lyrathorpe-macpro31`, `lyrathorpe-rpi5` are byte-identical (confirms #51 and the subnet `let` binding change nothing). Only `emmathorpe-edaas` differs — the kube-tmux input (#50) and the Lens removal (#53).

Closes #50, #51, #52, #53.

---------

Co-authored-by: Emma Thorpe <emma.thorpe@citrix.com>
Reviewed-on: #54
2026-06-29 14:09:38 +01:00
lyrathorpeandEmma Thorpe 128deca2e3 refactor(flake): user registry, multi-user hosts, and portable home outputs (#49)
CI / flake (push) Successful in 3m26s
## Summary

Separates user identity (data) from the reusable Nix modules and lets a host declare any number of users, replacing the previous one-user-per-host structure. Also restructures the tree and exposes the home config for use off these hosts.

## Changes

- **User registry** (`users/registry.nix`): per-user identity (name, email, groups, authorized + signing keys) as the single source of truth; no user data hardcoded in modules.
- **Multi-user `mkHost`**: a host declares a `users` set keyed by username; per-user identity is injected into each home config via the `identity` module arg.
- **Restructured layout**: `users/`, `home/`, `modules/`, `hosts/`, `lib/` replace the former `lyrathorpe/` and `system/` trees.
- **Portable outputs**: standalone `homeConfigurations."<user>@<system>"` (the portable subset — shell, git, editor, claude) plus an exported `homeModules` for use on machines not managed by this flake, or as an input to other flakes.
- Docs (`README.md`, `home/README.md`) and `.gitignore` updated for the new paths.

## Fixes

- Closes #46 — shared user module authorized one user's SSH key for every account.
- Closes #47 — git committer identity hardcoded as defaults instead of per-user.
- Closes #48 — EDaaS systemd linger hardcoded to a literal username.

## Verification

- `nix flake check` passes: treefmt, deadnix, statix, pre-commit, and evaluation of all NixOS hosts + Darwin + homeConfigurations.
- Derivation-path comparison vs `main`: `lyrathorpe-mbp` and `emmathorpe-edaas` are byte-identical; `lyrathorpe-t400`, `lyrathorpe-macpro31` and `lyrathorpe-rpi5` differ only by de-duplicating a repeated `authorized_keys` entry (confirmed with nix-diff — no other change).
- Standalone `homeConfigurations."lyrathorpe@x86_64-linux".activationPackage` builds.

## Notes

- `emmathorpe` has no personal authorized key yet (it previously inherited Lyra's key via the bug in #46); the registry entry is intentionally empty — add a real key if SSH login as `emmathorpe` is wanted (moot on the WSL host).
- A two-repo (public dotfiles / private systems) split is deferred by design; this internal restructure is the prerequisite for it.

---------

Co-authored-by: Emma Thorpe <emma.thorpe@citrix.com>
Reviewed-on: #49
2026-06-29 13:06:23 +01:00
23 changed files with 332 additions and 141 deletions
-6
View File
@@ -8,12 +8,6 @@ indent_size = 2
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[*.{nix,yaml,yml,json,md,sh,toml}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
# Markdown uses trailing whitespace for hard line breaks. # Markdown uses trailing whitespace for hard line breaks.
[*.md] [*.md]
trim_trailing_whitespace = false trim_trailing_whitespace = false
+3
View File
@@ -2,3 +2,6 @@ modules/firmware/*
# vim swap files # vim swap files
*.swp *.swp
# Local scratch project, not part of this flake.
tf-inspect/
+99 -5
View File
@@ -12,16 +12,67 @@ Defined in the host table in [`flake.nix`](./flake.nix):
| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) | | `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) |
| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](./hosts/T400/README.md) | | `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) | | `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) | | `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-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) | | `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) — [notes](./hosts/Darwin/README.md) |
Shared layers: `home` (home-manager: shell, git, editor), Shared layers: `home` (home-manager: shell, git, editor),
`modules/common-nixos.nix` (all NixOS hosts: fonts, nix-ld, caches), `modules/common-nixos.nix` (all NixOS hosts: fonts, nix-ld, caches),
`modules/workstation.nix` (physical graphical hosts: audio, thermald, `modules/workstation.nix` (physical graphical hosts: audio, thermald,
earlyoom, fwupd), `modules/laptop.nix` (laptops: Wi-Fi, Bluetooth, power, earlyoom, fwupd), `modules/laptop.nix` (laptops: Wi-Fi, Bluetooth, power,
lid), and `modules/ssh.nix` (key-only sshd). The x86 hosts also pull lid), `modules/desktop.nix` (wired desktops: NetworkManager), and
`nixos-hardware` profiles. `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, 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 feature-flag options (currently `features.swayDesktop.enable`) so any host can read/set them without importing the heavy implementation module. |
| `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.
## Users ## Users
@@ -38,6 +89,16 @@ Identity is data, kept separate from the reusable modules:
identity into that user's home config as the `identity` module arg. A host can identity into that user's home config as the `identity` module arg. A host can
therefore declare any number of users. 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, and the corporate ssh
handling; 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) ### Portable home (off-NixOS / external consumers)
The home config is also exposed for use beyond these hosts: The home config is also exposed for use beyond these hosts:
@@ -61,6 +122,36 @@ sudo nixos-rebuild switch --flake .#<configuration>
darwin-rebuild switch --flake .#lyrathorpe-mac 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 ## Shell environment & keybindings
- Interactive shell features (zsh, tmux, git, ssh, CLI tools, auto-tmux): - Interactive shell features (zsh, tmux, git, ssh, CLI tools, auto-tmux):
@@ -111,4 +202,7 @@ A dev shell and a formatting/lint gate are wired through the flake:
[`.gitea/workflows/ci.yaml`](./.gitea/workflows/ci.yaml) runs `nix flake check` [`.gitea/workflows/ci.yaml`](./.gitea/workflows/ci.yaml) runs `nix flake check`
(formatting, `deadnix`, `statix`, the pre-commit hooks) and evaluates every (formatting, `deadnix`, `statix`, the pre-commit hooks) and evaluates every
NixOS and Darwin host configuration on push/PR. 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.
Generated
+17
View File
@@ -191,6 +191,22 @@
"type": "github" "type": "github"
} }
}, },
"kube-tmux": {
"flake": false,
"locked": {
"lastModified": 1779714285,
"narHash": "sha256-l1wjg2ReWKCI7h/K11vvX2ykYTs/mVD+tfz/mQsjn/E=",
"owner": "jonmosco",
"repo": "kube-tmux",
"rev": "8b7e1d127c16b6dc87ff5743f4d775b245198b69",
"type": "github"
},
"original": {
"owner": "jonmosco",
"repo": "kube-tmux",
"type": "github"
}
},
"nix-darwin": { "nix-darwin": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -373,6 +389,7 @@
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"git-hooks": "git-hooks", "git-hooks": "git-hooks",
"home-manager": "home-manager", "home-manager": "home-manager",
"kube-tmux": "kube-tmux",
"nix-darwin": "nix-darwin", "nix-darwin": "nix-darwin",
"nix-homebrew": "nix-homebrew", "nix-homebrew": "nix-homebrew",
"nix-index-database": "nix-index-database", "nix-index-database": "nix-index-database",
+26 -41
View File
@@ -60,6 +60,13 @@
url = "github:NixOS/nixos-hardware"; url = "github:NixOS/nixos-hardware";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
# kube-tmux: kube context/namespace for the tmux status line on the work
# host. Not in nixpkgs and not a flake -- pinned here as a plain source so
# the script is always in the store (no manual checkout). See work.nix.
kube-tmux = {
url = "github:jonmosco/kube-tmux";
flake = false;
};
}; };
outputs = outputs =
@@ -93,14 +100,9 @@
# Unfree packages permitted to be built (replaces blanket allowUnfree). # Unfree packages permitted to be built (replaces blanket allowUnfree).
unfreePackages = [ unfreePackages = [
"claude-code" "claude-code"
"lens"
"lens-desktop"
]; ];
# Identity registry: who each user is (name, email, keys, groups), keyed # Per-user identity, keyed by username. See README "Users".
# by username. Threaded into the system layer as the `userRegistry`
# specialArg and into each user's home config as the `identity` module
# arg. See users/registry.nix, modules/users.nix, home/git.nix.
userRegistry = import ./users/registry.nix; userRegistry = import ./users/registry.nix;
# nixpkgs + nix-daemon settings shared by NixOS and Darwin hosts. # nixpkgs + nix-daemon settings shared by NixOS and Darwin hosts.
@@ -132,14 +134,8 @@
} }
]; ];
# mkHost :: { system, modules, users, portable } -> nixosSystem # Build one NixOS host. `users` is an attrset keyed by username (home
# Builds one machine by appending its host-specific modules to the shared # modules + optional per-user system bits). See README "Users".
# baseModules. `users` is an attrset keyed by username; each value carries
# that user's home-module list and optional per-host-user system bits
# (e.g. linger). Per-user identity is injected into each home config via
# the `identity` module arg (extraSpecialArgs is per-host, so it cannot
# carry per-user data); the system layer reads the global `userRegistry`
# restricted to this host's `hostUsers` set.
mkHost = mkHost =
{ {
system, system,
@@ -190,11 +186,8 @@
} }
]; ];
# mkDarwinHost :: { system, username, modules, homeModules } -> darwinSystem # Darwin counterpart of mkHost: single-user (macOS owns the account),
# Darwin counterpart of mkHost. macOS already owns the login user, so we # identity still from the registry. See README "Users".
# only attach the platform and home-manager; no NixOS user module here.
# Stays single-user (macOS owns the account); identity is still sourced
# from the registry so the shared home modules behave as on NixOS.
mkDarwinHost = mkDarwinHost =
{ {
system, system,
@@ -223,12 +216,8 @@
]; ];
}; };
# Host table — declarative registry of every machine. To add a host: # Host table — one entry per machine, realised into a nixosConfiguration
# give it a name, its `system`, its `users` set (each user's home-module # of the same name below. See README "Hosts" / "Users".
# list, plus optional per-host-user bits like linger), and the system
# `modules`. mapAttrs below turns each entry into a nixosConfiguration of
# the same name. Per-user home configs compose ./home (the shared bundle)
# with any per-user modules (e.g. ./users/emmathorpe/work.nix).
hosts = { hosts = {
lyrathorpe-mbp = { lyrathorpe-mbp = {
system = "aarch64-linux"; system = "aarch64-linux";
@@ -240,6 +229,7 @@
]; ];
users.lyrathorpe.homeModules = [ users.lyrathorpe.homeModules = [
./home ./home
./users/lyrathorpe/home.nix
./home/desktop.nix ./home/desktop.nix
]; ];
}; };
@@ -261,6 +251,7 @@
]; ];
users.lyrathorpe.homeModules = [ users.lyrathorpe.homeModules = [
./home ./home
./users/lyrathorpe/home.nix
./home/desktop.nix ./home/desktop.nix
]; ];
}; };
@@ -278,6 +269,7 @@
]; ];
users.lyrathorpe.homeModules = [ users.lyrathorpe.homeModules = [
./home ./home
./users/lyrathorpe/home.nix
./home/desktop.nix ./home/desktop.nix
]; ];
}; };
@@ -311,7 +303,10 @@
inputs.nixos-hardware.nixosModules.raspberry-pi-5 inputs.nixos-hardware.nixosModules.raspberry-pi-5
./modules/ssh.nix ./modules/ssh.nix
]; ];
users.lyrathorpe.homeModules = [ ./home ]; users.lyrathorpe.homeModules = [
./home
./users/lyrathorpe/home.nix
];
}; };
}; };
@@ -327,6 +322,7 @@
]; ];
homeModules = [ homeModules = [
./home ./home
./users/lyrathorpe/home.nix
]; ];
}; };
}; };
@@ -418,14 +414,8 @@
flake.nixosConfigurations = lib.mapAttrs (_name: mkHost) hosts; flake.nixosConfigurations = lib.mapAttrs (_name: mkHost) hosts;
flake.darwinConfigurations = lib.mapAttrs (_name: mkDarwinHost) darwinHosts; flake.darwinConfigurations = lib.mapAttrs (_name: mkDarwinHost) darwinHosts;
# Reusable home modules, exported so this config can be consumed off these # Reusable home modules, exported for use off these hosts. See README
# hosts -- by a standalone home-manager on a non-NixOS machine, or as an # "Portable home" for the consumer module-arg expectations.
# input to someone else's flake. `default` is the portable bundle
# (shell + git + editor + claude). Consumers must supply the module args
# these expect: `inputs` always; `identity` (see users/registry.nix) for
# git/desktop; `portable` for sway. `desktop`/`sway` additionally require
# a NixOS host that provides the Sway/Firefox binary -- they are not
# standalone-portable.
flake.homeModules = { flake.homeModules = {
default = ./home; default = ./home;
shell = ./home/shell.nix; shell = ./home/shell.nix;
@@ -436,13 +426,8 @@
sway = ./home/sway.nix; sway = ./home/sway.nix;
}; };
# Standalone home-manager configurations: the portable bundle built for a # Standalone home-manager configs (portable bundle) for machines not
# machine NOT managed by this flake (`home-manager switch --flake # managed by this flake. See README "Portable home".
# .#"<user>@<system>"`). Only the portable subset is exposed; the desktop
# suite stays NixOS-only. homeConfigurations are not per-system, so the
# system is encoded in the attribute name, and home.username/homeDirectory
# are set explicitly (the NixOS module sets them automatically; standalone
# does not).
flake.homeConfigurations = flake.homeConfigurations =
let let
mkHome = mkHome =
+2 -2
View File
@@ -111,7 +111,7 @@ every host. Migrated from plain vim; the practical gain is a real LSP stack in
place of the old (inert) ALE. place of the old (inert) ALE.
| Feature | Notes | | Feature | Notes |
| -------------- | -------------------------------------------------------------------------------------- | | -------------- | ----------------------------------------------------------------------------------------- |
| Colorscheme | Catppuccin Mocha (matches the terminal and the rest of the desktop) | | Colorscheme | Catppuccin Mocha (matches the terminal and the rest of the desktop) |
| File tree | nvim-tree, toggled with `,,` (comma twice; was nerdtree) | | File tree | nvim-tree, toggled with `,,` (comma twice; was nerdtree) |
| Fuzzy finder | telescope (+fzf-native): `<leader>ff` files, `<leader>fg` grep, `<leader>fb` buffers | | Fuzzy finder | telescope (+fzf-native): `<leader>ff` files, `<leader>fg` grep, `<leader>fb` buffers |
@@ -124,7 +124,7 @@ place of the old (inert) ALE.
| Editing | which-key hints, comment (`gc`/`gcc`), autopairs, treesitter textobjects | | Editing | which-key hints, comment (`gc`/`gcc`), autopairs, treesitter textobjects |
| Pane nav | vim-tmux-navigator — `Ctrl`+`h/j/k/l` moves across vim splits and tmux panes | | Pane nav | vim-tmux-navigator — `Ctrl`+`h/j/k/l` moves across vim splits and tmux panes |
| Syntax | tree-sitter (nix, lua, bash, markdown, groovy, c#, python, terraform, yaml) | | Syntax | tree-sitter (nix, lua, bash, markdown, groovy, c#, python, terraform, yaml) |
| LSP | nvim-cmp completion + servers `nil` (Nix), `lua_ls`, `pyright` (Python), `terraformls` | | LSP | nvim-cmp completion + servers `nil_ls` (Nix), `lua_ls`, `pyright` (Python), `terraformls` |
| Indentation | 2-wide hard tabs (`noexpandtab`, `tabstop`/`shiftwidth` = 2); line numbers on | | Indentation | 2-wide hard tabs (`noexpandtab`, `tabstop`/`shiftwidth` = 2); line numbers on |
| Filetypes | `*Jenkinsfile` → groovy | | Filetypes | `*Jenkinsfile` → groovy |
+5 -8
View File
@@ -1,6 +1,5 @@
# Version control: git + delta pager + commitizen + lazygit. The committer # Version control: git + delta + commitizen + lazygit. Committer identity comes
# identity (name, email, signing key) comes from the per-user `identity` arg, # from the per-user `identity` arg (the registry). See README "Users".
# derived from the registry (users/registry.nix) by mkHost.
{ {
pkgs, pkgs,
lib, lib,
@@ -20,8 +19,7 @@ in
package = pkgs.gitFull; package = pkgs.gitFull;
settings = { settings = {
user.name = identity.fullName; user.name = identity.fullName;
# Identity from the registry. mkDefault so a host-specific module can still # mkDefault so a host-specific module can still override it.
# override it without conflicting.
user.email = lib.mkDefault identity.email; user.email = lib.mkDefault identity.email;
push.autoSetupRemote = true; push.autoSetupRemote = true;
init.defaultBranch = "main"; init.defaultBranch = "main";
@@ -78,9 +76,8 @@ in
cc = "!cz commit"; cc = "!cz commit";
}; };
# SSH commit signing, key from the registry. mkDefault on the key and on # SSH signing, key from the registry. mkDefault so a host lacking the key
# gpgsign so a host without that key in its ssh-agent can override gpgsign # in its agent can set gpgsign = false instead of failing every commit.
# to false rather than fail every commit.
gpg.format = "ssh"; gpg.format = "ssh";
user.signingkey = lib.mkDefault identity.signingKey; user.signingkey = lib.mkDefault identity.signingKey;
commit.gpgsign = lib.mkDefault true; commit.gpgsign = lib.mkDefault true;
-3
View File
@@ -341,9 +341,6 @@ in
IdentityFile = "~/.ssh/code.emmathe.dev"; IdentityFile = "~/.ssh/code.emmathe.dev";
IdentitiesOnly = true; IdentitiesOnly = true;
}; };
"dockerpi.inf.cbg.emmaisvery.gay" = {
User = "emmathorpe";
};
}; };
}; };
+3 -4
View File
@@ -334,13 +334,12 @@ in
]; ];
}; };
# Night light. Manual location (no geoclue dependency); adjust the coordinates # Night light. Manual location (no geoclue dependency); warmer at night,
# to taste. Warmer at night, neutral by day. # neutral by day. Coordinates come from the per-user module (e.g.
# users/lyrathorpe/home.nix), not this shared module.
services.gammastep = { services.gammastep = {
enable = true; enable = true;
provider = "manual"; provider = "manual";
latitude = 51.5;
longitude = -0.13; # London-ish; set to your actual location
temperature = { temperature = {
day = 6500; day = 6500;
night = 3700; night = 3700;
+51
View File
@@ -0,0 +1,51 @@
# macOS (nix-darwin) — `lyrathorpe-mac`
Flake host: `lyrathorpe-mac` (`aarch64-darwin`). Apple Silicon Mac managed by
**nix-darwin** from this same flake. Built via `mkDarwinHost` (single-user —
macOS owns the account; identity still comes from the registry). Files:
`configuration.nix`.
## What this host is
A macOS workstation. The interactive user environment (shell, git, editor,
Claude) is the **shared `../../home` bundle** — the same modules the Linux hosts
use — so the terminal experience matches. The Linux-only `desktop.nix`/`sway.nix`
are intentionally left out. This host config covers the macOS-specific layer:
system packages, Homebrew, and macOS UI defaults.
## Package sourcing
- **nixpkgs** (`environment.systemPackages`) for CLI tooling and libraries.
- **Homebrew**, owned declaratively by `nix-homebrew` (Rosetta enabled for
x86_64 formulae). The `brews`/`casks` lists are **authoritative**:
`onActivation.cleanup = "zap"` uninstalls anything not declared. GUI apps are
casks (nixpkgs darwin GUI support is unreliable); a few version-pinned
toolchains and the PWA host stay on brew for continuity.
- **Mac App Store** apps are **not** declarative: nix-darwin 26.05 runs
activation as root, and `mas` cannot reach the App Store session from root.
Install them by hand with `mas install <id>` from a GUI Terminal (the `mas`
CLI is in `environment.systemPackages`).
## macOS integration
- `security.pam.services.sudo_local`**Touch ID for sudo** (and
`darwin-rebuild`'s sudo prompt), kept in `sudo_local` so it survives OS
updates. `reattach` pulls in `pam_reattach` so Touch ID works inside tmux
(which the terminals auto-start).
- `system.defaults` — declarative dock / finder / global / trackpad preferences,
applied on activation and reversible. This is the main reason to run nix-darwin
beyond package management.
- The JetBrainsMono Nerd Font is installed to `/Library/Fonts`; set it in
iTerm2 (Settings → Profiles → Text → Font) so the tmux statusline glyphs
render.
## stateVersion
`system.stateVersion = 5` (the nix-darwin state version, an integer — not a
NixOS release string). Read `darwin-rebuild changelog` before changing it.
## Apply
```sh
darwin-rebuild switch --flake .#lyrathorpe-mac
```
+53
View File
@@ -0,0 +1,53 @@
# Work WSL box — `emmathorpe-edaas`
Flake host: `emmathorpe-edaas` (`x86_64-linux`). NixOS running under
**NixOS-WSL** on the corporate Windows machine. Headless: no Sway desktop
(`features.swayDesktop.enable = false`), plain WSL shell login. Files:
`configuration.nix`.
## What this host is
The day-to-day work environment. It layers the corporate Kubernetes / Helm /
Terraform / cloud toolchain and a couple of work-only editor language servers on
top of the shared home profile. The system config here is thin — it is mostly
WSL plumbing; the user-facing tooling lives in
[`../../users/emmathorpe/work.nix`](../../users/emmathorpe/work.nix).
## WSL specifics
- `wsl.enable`, default user `emmathorpe`, Windows PATH interop and start-menu
launchers on. `/etc/hosts` generation is off (`generateHosts = false`).
- **Docker Desktop integration**, not the native daemon as the primary path:
`wsl.extraBin` shims the coreutils/`groupadd`/`usermod` binaries Docker
Desktop's `wsl-distro-proxy` expects, and `docker-desktop-proxy.script` is
patched to the real proxy path. The native `virtualisation.docker` is also
enabled (with `enableOnBoot` + `autoPrune`).
- `programs.ssh.systemd-ssh-proxy.enable = false` — the NixOS-WSL store is a
read-only VHD owned by `nobody`, and OpenSSH rejects the generated
`ssh-proxy` Include as "Bad owner or permissions", which would break ssh/git
for every command. The vsock proxy it provides is unused under WSL.
- `networking.hostName = "emmathorpe-edaas"` matches the flake attribute so
`nh os switch` resolves without `-H`.
## Renovate review timer
The host-table entry sets `users.emmathorpe.linger = true` so the user's
`systemd --user` instance stays alive without an open login session. That keeps
the daily headless **Renovate PR review** timer firing — defined in
[`../../users/emmathorpe/renovate-review.nix`](../../users/emmathorpe/renovate-review.nix)
(imported only from `work.nix`, so it exists on this machine alone). See that
file's header for the auth (Vertex AI ADC), triage policy, and caveats.
## stateVersion
`system.stateVersion = "24.11"` — the release this box was first installed on.
Leave it; it freezes stateful defaults and is not meant to track the current
nixpkgs.
## Apply
```sh
sudo nixos-rebuild switch --flake .#emmathorpe-edaas
# or, since the hostname matches the attribute:
nh os switch
```
+1 -1
View File
@@ -48,7 +48,7 @@ gigabit ports.
## Login ## Login
Graphical login via a Wayland greeter — `greetd` running ReGreet inside the Graphical login via a Wayland greeter — `greetd` running ReGreet inside the
`cage` kiosk compositor — configured centrally in `lyrathorpe/swaywm.nix` for `cage` kiosk compositor — configured centrally in `../../modules/sway.nix` for
every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced
to the Dvorak layout to match the console and Sway session. Set the user to the Dvorak layout to match the console and Sway session. Set the user
password (`passwd lyrathorpe`) after install, or the greeter cannot password (`passwd lyrathorpe`) after install, or the greeter cannot
+2 -4
View File
@@ -26,10 +26,8 @@
# workstation.nix is the backstop). # workstation.nix is the backstop).
zramSwap.enable = true; zramSwap.enable = true;
# This host accepts SSH, so open 22 (the firewall itself is enabled in # sshd (daemon, port 22, key-only policy) comes from ../../modules/ssh.nix;
# workstation.nix with a default-deny policy). # the firewall itself is enabled in workstation.nix with a default-deny policy.
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
# Dual Harpertown Xeon microcode. Redistributable firmware (GPU/NIC blobs) is # Dual Harpertown Xeon microcode. Redistributable firmware (GPU/NIC blobs) is
# enabled in workstation.nix. # enabled in workstation.nix.
+3 -2
View File
@@ -15,7 +15,7 @@ Headless `aarch64-linux` server with two roles:
```sh ```sh
nixos-generate-config --root /mnt nixos-generate-config --root /mnt
# copy /mnt/etc/nixos/hardware-configuration.nix over # copy /mnt/etc/nixos/hardware-configuration.nix over
# system/machine/RPi5/hardware-configuration.nix in this repo, then commit # hosts/RPi5/hardware-configuration.nix in this repo, then commit
``` ```
`hardware-configuration.nix` in this directory is a **placeholder** committed `hardware-configuration.nix` in this directory is a **placeholder** committed
only so the host evaluates in CI. The machine will not boot correctly until it only so the host evaluates in CI. The machine will not boot correctly until it
@@ -28,7 +28,8 @@ Headless `aarch64-linux` server with two roles:
nh os switch nh os switch
``` ```
4. Give the login user a password (`passwd lyrathorpe`) and confirm the key in 4. Give the login user a password (`passwd lyrathorpe`) and confirm the key in
`system/modules/ssh.nix` is the one you will connect with. the user registry (`../../users/registry.nix`, applied by
`../../modules/ssh.nix`) is the one you will connect with.
## Docker socket (security) ## Docker socket (security)
+4 -7
View File
@@ -25,15 +25,12 @@
boot.loader.grub.enable = false; boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true; boot.loader.generic-extlinux-compatible.enable = true;
# Remote administration. Key-only policy and the authorized key come from # Remote administration: the daemon, port 22 and key-only policy all come from
# ../../modules/ssh.nix; here we just enable the daemon and open the port. # ../../modules/ssh.nix.
services.openssh.enable = true;
# Default-deny inbound. Open only SSH here; the Docker and nginx submodules # Default-deny inbound; the Docker and nginx submodules open their own ports
# open their own ports (Docker via a source-restricted nftables rule, nginx # (Docker via a source-restricted nftables rule, nginx via 80/443).
# via 80/443). List-valued, so these merge with the submodule definitions.
networking.firewall.enable = true; networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
# See `man configuration.nix` / the stateVersion docs before changing. # See `man configuration.nix` / the stateVersion docs before changing.
system.stateVersion = "26.05"; system.stateVersion = "26.05";
+5 -1
View File
@@ -8,6 +8,10 @@
# secure upgrade path is mutual TLS on 2376 (--tlsverify with client certs); # secure upgrade path is mutual TLS on 2376 (--tlsverify with client certs);
# that needs out-of-band cert provisioning and is intentionally not wired here. # that needs out-of-band cert provisioning and is intentionally not wired here.
{ ... }: { ... }:
let
# LAN allowed to reach the unauthenticated Docker TCP socket (see SECURITY above).
trustedSubnet = "10.187.1.0/24";
in
{ {
virtualisation.docker.enable = true; virtualisation.docker.enable = true;
@@ -29,6 +33,6 @@
# CIDR to match the LAN that should reach the Docker API. # CIDR to match the LAN that should reach the Docker API.
networking.nftables.enable = true; networking.nftables.enable = true;
networking.firewall.extraInputRules = '' networking.firewall.extraInputRules = ''
ip saddr 10.187.1.0/24 tcp dport 2375 accept ip saddr ${trustedSubnet} tcp dport 2375 accept
''; '';
} }
+1 -1
View File
@@ -35,7 +35,7 @@ change and `radeon` stays idle.
## Login ## Login
Graphical login via a Wayland greeter — `greetd` running ReGreet inside the Graphical login via a Wayland greeter — `greetd` running ReGreet inside the
`cage` kiosk compositor — configured centrally in `lyrathorpe/swaywm.nix` for `cage` kiosk compositor — configured centrally in `../../modules/sway.nix` for
every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced
to the Dvorak layout to match the console and Sway session. Set the user to the Dvorak layout to match the console and Sway session. Set the user
password (`passwd lyrathorpe`) after install, or the greeter cannot password (`passwd lyrathorpe`) after install, or the greeter cannot
+2 -4
View File
@@ -21,10 +21,8 @@
# Low-RAM host (4 GiB max): a compressed RAM swap reduces disk paging. # Low-RAM host (4 GiB max): a compressed RAM swap reduces disk paging.
zramSwap.enable = true; zramSwap.enable = true;
# This host accepts SSH, so open 22 (the firewall itself is enabled in # sshd (daemon, port 22, key-only policy) comes from ../../modules/ssh.nix;
# laptop.nix with a default-deny policy). # the firewall itself is enabled in laptop.nix with a default-deny policy.
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
# Intel Core 2 (Penryn) microcode. Redistributable firmware (enabled in # Intel Core 2 (Penryn) microcode. Redistributable firmware (enabled in
# workstation.nix) supplies the iwlwifi blobs (Intel WiFi Link 5100/5300) and # workstation.nix) supplies the iwlwifi blobs (Intel WiFi Link 5100/5300) and
+6 -6
View File
@@ -1,11 +1,11 @@
# Key-only SSH hardening, imported by the hosts that run sshd (T400, Mac Pro, # sshd for the hosts that run it (T400, Mac Pro, RPi5): enable the daemon, open
# RPi5). The host config still does `services.openssh.enable = true` and opens # port 22, and apply a key-only policy. Authorized keys are owned per-user by the
# port 22 next to where it documents the listening service; this module only # registry (modules/users.nix), not here.
# tightens the policy so a host opting into sshd cannot accidentally ship
# password/root login. Authorized keys are owned per-user by the identity
# registry (users/registry.nix, applied via modules/users.nix), not here.
{ ... }: { ... }:
{ {
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
services.openssh.settings = { services.openssh.settings = {
PasswordAuthentication = false; # keys only PasswordAuthentication = false; # keys only
KbdInteractiveAuthentication = false; # no keyboard-interactive fallback KbdInteractiveAuthentication = false; # no keyboard-interactive fallback
+3 -9
View File
@@ -1,9 +1,5 @@
# System-level user accounts, built from the identity registry # System user accounts, built from the registry (users/registry.nix) for the
# (users/registry.nix). `hostUsers` is the host's user set, threaded by mkHost # host's `hostUsers` set. See README "Users".
# from the flake host table; `userRegistry` is the global identity table passed
# as a specialArg. Every account's identity -- description, groups, authorized
# keys -- comes from its registry entry, so no user data is hardcoded here and a
# host may declare any number of users.
{ {
config, config,
pkgs, pkgs,
@@ -29,9 +25,7 @@
openssh.authorizedKeys.keys = id.sshAuthorizedKeys; openssh.authorizedKeys.keys = id.sshAuthorizedKeys;
shell = pkgs.zsh; shell = pkgs.zsh;
} }
# Keep this user's systemd --user instance running without an open login # linger opt-in (host table); left unmanaged when unset.
# session (e.g. for home-manager user timers). Only emitted when the host
# table opts in, so hosts that don't set it leave linger entirely unmanaged.
// lib.optionalAttrs (spec ? linger) { inherit (spec) linger; } // lib.optionalAttrs (spec ? linger) { inherit (spec) linger; }
) hostUsers; ) hostUsers;
+11 -7
View File
@@ -1,8 +1,11 @@
# Home-manager module for the work (EDaaS/WSL) profile: corporate work toolchain # Work (EDaaS/WSL) home profile: corporate toolchain + tmux tweaks. Git identity
# packages and tmux tweaks. Imported only by the work host. The corporate git # comes from the registry (users/registry.nix), not here.
# identity (email + signing key) is the emmathorpe entry in the identity {
# registry (users/registry.nix), applied by the shared home/git.nix. pkgs,
{ pkgs, lib, ... }: lib,
inputs,
...
}:
{ {
# Host-scoped extras for this machine only (the EDaaS/WSL host). # Host-scoped extras for this machine only (the EDaaS/WSL host).
@@ -26,7 +29,6 @@
pkgs.powershell pkgs.powershell
pkgs.nuget pkgs.nuget
pkgs.gedit pkgs.gedit
pkgs.lens
pkgs.python3 pkgs.python3
pkgs.gnumake pkgs.gnumake
pkgs.gcc pkgs.gcc
@@ -50,8 +52,10 @@
docker = "/run/current-system/sw/bin/docker"; docker = "/run/current-system/sw/bin/docker";
}; };
programs.tmux = { programs.tmux = {
# kube context/namespace in the status line. kube-tmux is pinned as a flake
# input (it is not in nixpkgs), so the script is always present in the store.
extraConfig = '' extraConfig = ''
set -g status-right "#(/run/current-system/sw/bin/bash $HOME/code/kube-tmux/kube.tmux 250 red black)" set -g status-right "#(${pkgs.bash}/bin/bash ${inputs.kube-tmux}/kube.tmux 250 red black)"
''; '';
}; };
programs.go = { programs.go = {
+17
View File
@@ -0,0 +1,17 @@
# Lyra's personal home extras, imported on her hosts (not the work box). Keeps
# personal data out of the shared home/ modules. See README "Users".
{ pkgs, lib, ... }:
{
# Personal ssh host shortcut.
programs.ssh.settings."dockerpi.inf.cbg.emmaisvery.gay" = {
User = "emmathorpe";
};
# Night-light location for gammastep (the service itself is enabled by
# home/sway.nix on graphical hosts). Linux-guarded so Darwin, which imports
# this module but has no gammastep, skips it.
services.gammastep = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
latitude = 51.5;
longitude = -0.13;
};
}
+3 -15
View File
@@ -1,14 +1,5 @@
# User identity registry -- pure data, no machinery. Keyed by username. # User identity registry -- pure data, keyed by username. See README "Users".
# # (`identity.username` is injected by mkHost, so it is not repeated here.)
# Each entry describes WHO a user is (display name, email, authorized/signing
# keys, supplementary groups). The reusable modules read this indirectly:
# - system: modules/users.nix builds users.users.* from `userRegistry`
# (specialArg) restricted to the host's `hostUsers` set.
# - home: home/git.nix and home/desktop.nix read the per-user `identity`
# module arg, which mkHost derives from the matching registry entry.
#
# The login name is injected by mkHost as `identity.username`, so it is not
# repeated inside each entry here.
{ {
lyrathorpe = { lyrathorpe = {
fullName = "Lyra Thorpe"; fullName = "Lyra Thorpe";
@@ -30,10 +21,7 @@
"wheel" "wheel"
"docker" "docker"
]; ];
# No personal authorized key on file yet. The previous shared user module # No personal key on file yet; add one if SSH login as emmathorpe is wanted.
# applied Lyra's key to every account, including this one -- a defect. Leave
# this empty until a real key is provisioned; SSH login is moot on the WSL
# host (entered via wsl.exe, not sshd).
sshAuthorizedKeys = [ ]; sshAuthorizedKeys = [ ];
signingKey = "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAJMVgeRKnfX1G8coU3nAobI485aeUpGTMqH7+zbKI8o emma.thorpe@cloud.com"; signingKey = "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAJMVgeRKnfX1G8coU3nAobI485aeUpGTMqH7+zbKI8o emma.thorpe@cloud.com";
}; };