Author SHA1 Message Date
Emma ThorpeandClaude Opus 4.8 9b7a9fa9b9 chore(memory): note nix shell for ad-hoc nixpkgs tooling
CI / flake (pull_request) Successful in 13s
Record that any nixpkgs tool can be run on the fly via nix run / nix
shell, so a missing command during development is not a dead end. Takes
effect after a home-manager rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:25:56 +01:00
Emma ThorpeandClaude Opus 4.8 33278d9ed2 docs: add project CLAUDE.md documenting flake checks
Document the formatting and lint gates (treefmt/nixfmt/shfmt/prettier,
deadnix, statix, pre-commit) and how to run them, so changes -- docs
included -- are formatted before commit. Notes the CI detect step that
skips heavy checks on docs-only PRs, which can report a false green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:25:56 +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
67 changed files with 711 additions and 291 deletions
-6
View File
@@ -8,12 +8,6 @@ indent_size = 2
trim_trailing_whitespace = 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.
[*.md]
trim_trailing_whitespace = false
+4 -1
View File
@@ -1,4 +1,7 @@
system/modules/firmware/*
modules/firmware/*
# vim swap files
*.swp
# Local scratch project, not part of this flake.
tf-inspect/
+63
View File
@@ -0,0 +1,63 @@
# Working on this flake
Project notes for changes to this repository. Persona and memory rules live in
the user-global config; this file is about the flake's checks and conventions.
## Before you commit: run the formatter
Formatting and linting are driven by the flake. CI (`.gitea/workflows/ci.yaml`)
runs `nix flake check`, which fails the build if any file is unformatted or trips
a lint. From the repo root:
- `nix fmt` — format the whole tree (writes changes).
- `nix flake check` — run every check read-only (what CI runs).
- `nix develop` — dev shell; its `shellHook` installs the git pre-commit hooks so
the same gates run on `git commit`.
Never commit with `--no-verify`. A bypassed commit ships unformatted content and
turns CI red on the next push to `main` (see "Docs are checked too").
## What gets checked
Defined in `flake.nix` (the `treefmt`, `pre-commit`, and `checks` blocks) and
`statix.toml`:
| Check | Tool | Covers |
| ------------ | --------------------------------- | ------------------------------------------------------- |
| `formatting` | treefmt → `nixfmt` | all `*.nix` |
| `formatting` | treefmt → `shfmt` | shell scripts |
| `formatting` | treefmt → `prettier` | **Markdown, YAML, JSON** (incl. `README.md`, this file) |
| `deadnix` | deadnix | dead Nix bindings (`--no-lambda-pattern-names`) |
| `statix` | statix | Nix antipatterns (config in `statix.toml`) |
| pre-commit | nixfmt-rfc-style, deadnix, statix | the same gates, run on commit |
Excluded from formatting: `*/hardware-configuration.nix` (generated by
`nixos-generate-config`) and `flake.lock`. Editor defaults (indent, EOL, final
newline) are in `.editorconfig`; note Markdown keeps trailing whitespace, which
encodes hard line breaks.
## Docs are checked too — the common trap
prettier formats `*.md`, so **documentation edits must be run through `nix fmt`**
exactly like code. prettier re-aligns Markdown tables in particular; hand-editing
a table almost always leaves it non-conformant and fails the `formatting` check.
Beware a false green: the CI `detect` step skips the heavy checks on a pull
request that touches **no** `.nix`, `flake.lock`, or the workflow file — so a
docs-only PR reports success without ever running prettier. The failure then
surfaces on the push-to-`main` run (which always runs the full check) or on the
next unrelated PR that does touch Nix. Run `nix flake check` locally before
merging a docs change, regardless of what the PR check shows.
## Host evaluation
CI also evaluates every `nixosConfigurations` / `darwinConfigurations` host's
toplevel (eval only, no build) on an x86_64 runner, so eval errors fail cheaply.
Reproduce locally:
```sh
nix eval --raw ".#nixosConfigurations.<host>.config.system.build.toplevel.drvPath"
```
Host lists are discovered from the flake, so adding or removing a host needs no
change to the workflow.
+143 -20
View File
@@ -8,20 +8,110 @@ single flake.
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](./system/machine/T400/README.md) |
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./system/machine/MacPro31/README.md) |
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) |
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](./system/machine/RPi5/README.md) |
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) |
| `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: `lyrathorpe/home` (home-manager: shell, git, editor),
`system/modules/common-nixos.nix` (all NixOS hosts: fonts, nix-ld, caches),
`system/modules/workstation.nix` (physical graphical hosts: audio, thermald,
earlyoom, fwupd), `system/modules/laptop.nix` (laptops: Wi-Fi, Bluetooth, power,
lid), and `system/modules/ssh.nix` (key-only sshd). The x86 hosts also pull
`nixos-hardware` profiles.
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, 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
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, 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)
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
@@ -32,28 +122,58 @@ sudo nixos-rebuild switch --flake .#<configuration>
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):
[`lyrathorpe/home/README.md`](./lyrathorpe/home/README.md).
[`home/README.md`](./home/README.md).
- All Sway / tmux / foot / zsh keyboard shortcuts:
[`lyrathorpe/home/KEYBINDINGS.md`](./lyrathorpe/home/KEYBINDINGS.md).
[`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
[`lyrathorpe/swaywm.nix`](./lyrathorpe/swaywm.nix), gated on
[`modules/sway.nix`](./modules/sway.nix), gated on
`features.swayDesktop.enable` (the option is declared in
[`system/modules/features.nix`](./system/modules/features.nix), so headless hosts
can leave it off without importing `swaywm.nix`). The greeter is forced to Dvorak
[`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 `system/modules/firmware/` for Apple peripheral
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
@@ -63,7 +183,7 @@ 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
`system/modules/firmware/` and commit with `git add -f`.
`modules/firmware/` and commit with `git add -f`.
## Development
@@ -82,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`
(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
+38 -21
View File
@@ -25,11 +25,11 @@
},
"locked": {
"dir": "pkgs/firefox-addons",
"lastModified": 1782619356,
"narHash": "sha256-sde3f0uM5mEUwl6Bxom69P+9KOdpJ9YnSj0umSB2qdQ=",
"lastModified": 1782014564,
"narHash": "sha256-F/royQHyJAyKWKrV8AaG4Yf1yjzxa+PFk5xvTdvBrzk=",
"owner": "rycee",
"repo": "nur-expressions",
"rev": "9ad2019bb522c7eeecd9e3e2d18dd681f697f4d0",
"rev": "d6668e34bbce788459883a1097bf0ee170f49c61",
"type": "gitlab"
},
"original": {
@@ -177,11 +177,11 @@
]
},
"locked": {
"lastModified": 1782704057,
"narHash": "sha256-G1I1gd32F7mp9LAe1DaZ4ZL7NX5gyiKwdCMwro1Vrck=",
"lastModified": 1781981105,
"narHash": "sha256-/1nNBbA7PrSQpTc9Qazkhl4kIPg+TNl0CjxS3UQJKlw=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "868d0a692de703c2de98fab61968e4e310b7c28e",
"rev": "7bfff44b465909f69a442701293bc0badcf476dc",
"type": "github"
},
"original": {
@@ -191,6 +191,22 @@
"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": {
"inputs": {
"nixpkgs": [
@@ -237,11 +253,11 @@
]
},
"locked": {
"lastModified": 1782636943,
"narHash": "sha256-ripjZa7BBLwL1uS5VJF3s/VpZpWt5ZIQEvkJ/FJNpQw=",
"lastModified": 1782030356,
"narHash": "sha256-h4WpMr455AfRub0FXBaon6Vcpe0waUyJ4GivIW6oyd4=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "058b1f9381fa79fcda49982370a750ff92dbba43",
"rev": "3017088b49efd404f78e3b104f553b97e4af786b",
"type": "github"
},
"original": {
@@ -258,11 +274,11 @@
]
},
"locked": {
"lastModified": 1782374867,
"narHash": "sha256-wgU8MdUzSH2ccq85xo80pP1PAFW+e5kzx6rofVO1Jsk=",
"lastModified": 1781520503,
"narHash": "sha256-XuqQQG1qRyc3o8ld937sDLQNx+QrGV852KJ0dNglJDg=",
"owner": "nix-community",
"repo": "nixos-apple-silicon",
"rev": "bf99497876c07bb945d5fc536916cdee4f3b9eb6",
"rev": "43043ad207529650f9fa68e1705f7cf9c08bfdeb",
"type": "github"
},
"original": {
@@ -278,11 +294,11 @@
]
},
"locked": {
"lastModified": 1782562157,
"narHash": "sha256-a7+T6QSeowynwZ1ZJJbP8T8ntAytvrui8kFGJmIZt2c=",
"lastModified": 1781622756,
"narHash": "sha256-JrPh4M6S7aPsEE9tOENuZrxC6o2szSLlK+t4+nLke9s=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "a9cf7546a938c737b079e738de73934a13de9784",
"rev": "08018c72174a4df5657f8d94178ac69fb9c243e5",
"type": "github"
},
"original": {
@@ -314,11 +330,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1782535326,
"narHash": "sha256-ZeRxu4yn6shd3SNF5ZUQb4r7BaVo1zBKMjRhfoNSBmw=",
"lastModified": 1781216227,
"narHash": "sha256-9mUW6gNwoN2SWc/l0fW4svPNOulXLl8ijqKyeSOGgJE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "714a5f8c4ead6b31148d829288440ed033ccc041",
"rev": "a0374025a863d007d98e3297f6aa46cc3141c2f0",
"type": "github"
},
"original": {
@@ -330,11 +346,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1782467914,
"narHash": "sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE=",
"lastModified": 1781577229,
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e73de5be04e0eff4190a1432b946d469c794e7b4",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
"type": "github"
},
"original": {
@@ -373,6 +389,7 @@
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"home-manager": "home-manager",
"kube-tmux": "kube-tmux",
"nix-darwin": "nix-darwin",
"nix-homebrew": "nix-homebrew",
"nix-index-database": "nix-index-database",
+134 -77
View File
@@ -23,7 +23,7 @@
# Provides mkFlake: the systems/perSystem scaffolding used below.
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
# Declarative Firefox add-ons (e.g. the Catppuccin theme); see lyrathorpe/user.nix.
# Declarative Firefox add-ons (e.g. the Catppuccin theme); see modules/users.nix.
firefox-addons = {
url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
inputs.nixpkgs.follows = "nixpkgs";
@@ -46,7 +46,7 @@
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# Declarative Neovim (the editor; see lyrathorpe/home/editor.nix). Release
# Declarative Neovim (the editor; see home/editor.nix). Release
# branch matched to the pinned nixpkgs (26.05); follows our nixpkgs to keep a
# single nixpkgs in the closure. editor.nix sets programs.nixvim.nixpkgs.source
# to this same input so the home module doesn't warn about the pin.
@@ -60,6 +60,13 @@
url = "github:NixOS/nixos-hardware";
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 =
@@ -93,10 +100,11 @@
# Unfree packages permitted to be built (replaces blanket allowUnfree).
unfreePackages = [
"claude-code"
"lens"
"lens-desktop"
];
# Per-user identity, keyed by username. See README "Users".
userRegistry = import ./users/registry.nix;
# nixpkgs + nix-daemon settings shared by NixOS and Darwin hosts.
commonModule = {
nixpkgs.overlays = overlays;
@@ -112,9 +120,9 @@
# Shared scaffolding for every NixOS host: common user, settings, home-manager.
baseModules = [
./lyrathorpe/user.nix
./system/modules/common-nixos.nix
./system/modules/features.nix
./modules/users.nix
./modules/common-nixos.nix
./modules/features.nix
commonModule
home-manager.nixosModules.home-manager
{
@@ -126,18 +134,13 @@
}
];
# mkHost :: { system, username, fullName, modules, homeModules } -> nixosSystem
# Builds one machine by appending its host-specific modules to the shared
# baseModules. The user identity (username/fullName) is threaded through
# specialArgs so user.nix and the home modules stay host-agnostic, and the
# home-manager profile is keyed by the host's username.
# Build one NixOS host. `users` is an attrset keyed by username (home
# modules + optional per-user system bits). See README "Users".
mkHost =
{
system,
username,
fullName,
modules,
homeModules,
users,
# Host form factor. Laptops inherit the default; a desktop host sets
# `portable = false` to drop mobile components (battery block,
# brightness keys) from the home-manager Sway config.
@@ -148,8 +151,7 @@
specialArgs = {
inherit
inputs
username
fullName
userRegistry
portable
;
};
@@ -157,16 +159,15 @@
baseModules
++ modules
++ [
{ _module.args.hostUsers = users; }
{
home-manager.extraSpecialArgs = {
inherit
inputs
username
fullName
portable
;
home-manager.extraSpecialArgs = { inherit inputs portable; };
home-manager.users = lib.mapAttrs (name: spec: {
imports = spec.homeModules;
_module.args.identity = userRegistry.${name} // {
username = name;
};
home-manager.users.${username}.imports = homeModules;
}) users;
}
];
};
@@ -185,19 +186,17 @@
}
];
# mkDarwinHost :: { system, username, fullName, modules, homeModules } -> darwinSystem
# Darwin counterpart of mkHost. macOS already owns the login user, so we
# only attach the platform and home-manager; no NixOS user module here.
# Darwin counterpart of mkHost: single-user (macOS owns the account),
# identity still from the registry. See README "Users".
mkDarwinHost =
{
system,
username,
fullName,
modules,
homeModules,
}:
nix-darwin.lib.darwinSystem {
specialArgs = { inherit inputs username fullName; };
specialArgs = { inherit inputs username; };
modules =
darwinBaseModules
++ modules
@@ -206,40 +205,41 @@
nixpkgs.hostPlatform = system;
# macOS owns the account; point home-manager at its home dir.
users.users.${username}.home = "/Users/${username}";
home-manager.extraSpecialArgs = { inherit inputs username fullName; };
home-manager.users.${username}.imports = homeModules;
home-manager.extraSpecialArgs = { inherit inputs; };
home-manager.users.${username} = {
imports = homeModules;
_module.args.identity = userRegistry.${username} // {
inherit username;
};
};
}
];
};
# Host table — declarative registry of every machine. To add a host:
# give it a name, its `system`, the owning user, and the module lists.
# mapAttrs below turns each entry into a nixosConfiguration of the same name.
# Host table — one entry per machine, realised into a nixosConfiguration
# of the same name below. See README "Hosts" / "Users".
hosts = {
lyrathorpe-mbp = {
system = "aarch64-linux";
username = "lyrathorpe";
fullName = "Lyra Thorpe";
modules = [
./system/machine/MBP-Asahi/configuration.nix
./system/modules/laptop.nix
./hosts/MBP-Asahi/configuration.nix
./modules/laptop.nix
nixos-apple-silicon.nixosModules.default
./lyrathorpe/swaywm.nix
./modules/sway.nix
];
homeModules = [
./lyrathorpe/home
./lyrathorpe/home/desktop.nix
users.lyrathorpe.homeModules = [
./home
./users/lyrathorpe/home.nix
./home/desktop.nix
];
};
lyrathorpe-t400 = {
system = "x86_64-linux";
username = "lyrathorpe";
fullName = "Lyra Thorpe";
modules = [
./system/machine/T400/configuration.nix
./system/modules/laptop.nix
./system/modules/ssh.nix
./hosts/T400/configuration.nix
./modules/laptop.nix
./modules/ssh.nix
# No t400-specific profile exists; compose the generic ThinkPad +
# laptop/SSD/Intel building blocks (tp_smapi/acpi_call for battery
# thresholds, SSD + microcode defaults).
@@ -247,78 +247,82 @@
inputs.nixos-hardware.nixosModules.common-pc-laptop
inputs.nixos-hardware.nixosModules.common-pc-laptop-ssd
inputs.nixos-hardware.nixosModules.common-cpu-intel
./lyrathorpe/swaywm.nix
./modules/sway.nix
];
homeModules = [
./lyrathorpe/home
./lyrathorpe/home/desktop.nix
users.lyrathorpe.homeModules = [
./home
./users/lyrathorpe/home.nix
./home/desktop.nix
];
};
lyrathorpe-macpro31 = {
system = "x86_64-linux";
username = "lyrathorpe";
fullName = "Lyra Thorpe";
portable = false;
modules = [
./system/machine/MacPro31/configuration.nix
./system/modules/desktop.nix
./system/modules/ssh.nix
./hosts/MacPro31/configuration.nix
./modules/desktop.nix
./modules/ssh.nix
inputs.nixos-hardware.nixosModules.common-pc-ssd
inputs.nixos-hardware.nixosModules.common-cpu-intel
./lyrathorpe/swaywm.nix
./modules/sway.nix
];
homeModules = [
./lyrathorpe/home
./lyrathorpe/home/desktop.nix
users.lyrathorpe.homeModules = [
./home
./users/lyrathorpe/home.nix
./home/desktop.nix
];
};
emmathorpe-edaas = {
system = "x86_64-linux";
username = "emmathorpe";
fullName = "Emma Thorpe";
modules = [
./system/machine/EDaaS/configuration.nix
./hosts/EDaaS/configuration.nix
nixos-wsl.nixosModules.default
./lyrathorpe/swaywm.nix
./modules/sway.nix
];
users.emmathorpe = {
homeModules = [
./lyrathorpe/home
./lyrathorpe/home/work.nix
./home
./users/emmathorpe/work.nix
];
# Keep the systemd --user instance alive without a login session so
# the renovate-review home timer fires on schedule.
linger = true;
};
};
lyrathorpe-rpi5 = {
system = "aarch64-linux";
username = "lyrathorpe";
fullName = "Lyra Thorpe";
portable = false;
# Headless server: Docker host + nginx reverse proxy. No swaywm.nix
# Headless server: Docker host + nginx reverse proxy. No sway.nix
# (no desktop); the raspberry-pi-5 profile supplies kernel/firmware,
# ssh.nix adds key-only sshd.
modules = [
./system/machine/RPi5/configuration.nix
./hosts/RPi5/configuration.nix
inputs.nixos-hardware.nixosModules.raspberry-pi-5
./system/modules/ssh.nix
./modules/ssh.nix
];
users.lyrathorpe.homeModules = [
./home
./users/lyrathorpe/home.nix
];
homeModules = [ ./lyrathorpe/home ];
};
};
# Darwin host table — macOS machines built via mkDarwinHost. The shared
# ./lyrathorpe/home modules (shell, git, editor) are reused; the Linux-only
# ./home bundle (shell, git, editor) is reused directly; the Linux-only
# desktop/sway modules are intentionally left out.
darwinHosts = {
lyrathorpe-mac = {
system = "aarch64-darwin";
username = "lyrathorpe";
fullName = "Lyra Thorpe";
modules = [
./system/machine/Darwin/configuration.nix
./hosts/Darwin/configuration.nix
];
homeModules = [
./lyrathorpe/home
./home
./users/lyrathorpe/home.nix
];
};
};
@@ -409,6 +413,59 @@
# Realise the host tables: each entry becomes a {nixos,darwin}Configuration.
flake.nixosConfigurations = lib.mapAttrs (_name: mkHost) hosts;
flake.darwinConfigurations = lib.mapAttrs (_name: mkDarwinHost) darwinHosts;
# Reusable home modules, exported for use off these hosts. See README
# "Portable home" for the consumer module-arg expectations.
flake.homeModules = {
default = ./home;
shell = ./home/shell.nix;
git = ./home/git.nix;
editor = ./home/editor.nix;
claude = ./home/claude.nix;
desktop = ./home/desktop.nix;
sway = ./home/sway.nix;
};
# Standalone home-manager configs (portable bundle) for machines not
# managed by this flake. See README "Portable home".
flake.homeConfigurations =
let
mkHome =
{
system,
name,
}:
home-manager.lib.homeManagerConfiguration {
pkgs = import nixpkgs {
inherit system overlays;
config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) unfreePackages;
};
extraSpecialArgs = {
inherit inputs;
portable = true;
identity = userRegistry.${name} // {
username = name;
};
};
modules = [
./home
{
home.username = name;
home.homeDirectory = "/home/${name}";
}
];
};
in
{
"lyrathorpe@x86_64-linux" = mkHome {
system = "x86_64-linux";
name = "lyrathorpe";
};
"lyrathorpe@aarch64-linux" = mkHome {
system = "aarch64-linux";
name = "lyrathorpe";
};
};
}
);
}
+9 -7
View File
@@ -15,8 +15,10 @@ Keyboard shortcuts have their own reference: [`KEYBINDINGS.md`](./KEYBINDINGS.md
| GUI apps, GTK/Firefox theming, cursor | [`desktop.nix`](./desktop.nix) (graphical hosts only) |
Shared by every host via [`default.nix`](./default.nix); the work box also layers
[`work.nix`](./work.nix) on top (work email, its own ssh config, extra packages,
and the C#/Helm language servers).
[`work.nix`](../users/emmathorpe/work.nix) on top (its own ssh config, extra
packages, and the C#/Helm language servers). The committer identity (name, email,
signing key) comes from the user registry
([`../users/registry.nix`](../users/registry.nix)), not this module.
---
@@ -55,7 +57,7 @@ and the C#/Helm language servers).
| `hyperfine` / `sd` | command-line benchmarking; saner find-and-replace than sed |
**Theming:** `fzf`, `bat`, `btop`, `lazygit` and `git`'s `delta` pager are all
Catppuccin Mocha, driven from the shared `../catppuccin-mocha.nix` palette / the
Catppuccin Mocha, driven from the shared `../lib/catppuccin-mocha.nix` palette / the
catppuccin upstream themes.
**Env & defaults:** `xdg.enable` on; `PAGER`/`MANPAGER` (bat) set in `default.nix`
@@ -109,7 +111,7 @@ every host. Migrated from plain vim; the practical gain is a real LSP stack in
place of the old (inert) ALE.
| Feature | Notes |
| -------------- | -------------------------------------------------------------------------------------- |
| -------------- | ----------------------------------------------------------------------------------------- |
| Colorscheme | Catppuccin Mocha (matches the terminal and the rest of the desktop) |
| File tree | nvim-tree, toggled with `,,` (comma twice; was nerdtree) |
| Fuzzy finder | telescope (+fzf-native): `<leader>ff` files, `<leader>fg` grep, `<leader>fb` buffers |
@@ -122,7 +124,7 @@ place of the old (inert) ALE.
| 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 |
| 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 |
| Filetypes | `*Jenkinsfile` → groovy |
@@ -148,7 +150,7 @@ current (`gc`/`fetch.writeCommitGraph`) so `lg` stays fast.
| `cz` `cc` | `git cz <sub>` (e.g. `git cz c`) and `git cc` → commitizen prompt |
| Behaviour | |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pulls | rebase, with autostash + autosquash |
| Fetch | prune deleted remote branches |
| Conflicts | `zdiff3` (shows the common ancestor) |
@@ -157,7 +159,7 @@ current (`gc`/`fetch.writeCommitGraph`) so `lg` stays fast.
| Commit editor | full diff shown (`commit.verbose`) |
| Misc | branches sorted by date, `column.ui = auto`, `help.autocorrect = prompt`, `push.autoSetupRemote` |
| Global ignores | `result`, `result-*`, `.direnv`, `*.swp`, `.DS_Store` |
| Signing | SSH commit + tag signing (`mkDefault`, so a host without the key in its agent can disable it). Personal email `iam@emmathe.dev`; the work box overrides email + signing. |
| Signing | SSH commit + tag signing (`mkDefault`, so a host without the key in its agent can disable it). Name, email and signing key all come from the per-user `identity` (the user registry, `../users/registry.nix`). |
## ssh
@@ -9,3 +9,4 @@
- [Review and comments workflow](workflow_review_and_comments.md) — show PR body and non-trivial Jira comments before posting; terse IaC code comments; PR body content rules
- [Sandbox prompts](feedback_sandbox_prompts.md) — don't prompt for sandbox-disable or routine read-only shell ops; broaden permissions instead
- [Dev clusters disposable](dev_clusters_disposable.md) — Lyra's dev clusters are recreatable; mutate/break freely, no confirmation needed
- [Nix shell tooling](nix_shell_tooling.md) — any nixpkgs tool runs ad hoc via `nix run`/`nix shell nixpkgs#<pkg>`; a missing command is never a dead end
+23
View File
@@ -0,0 +1,23 @@
---
name: nix-shell-tooling
description: "Any nixpkgs tool can be run ad hoc via nix run / nix shell — a missing command is never a dead end during development"
metadata:
node_type: memory
type: feedback
originSessionId: dfb56b58-518b-4daf-b531-7119bb4a9534
---
Any tool in nixpkgs can be run without installing it into the environment. If a
command is missing during development, pull it from nixpkgs on the fly instead
of working around its absence or reporting the tool as unavailable.
**Why:** Lyra runs NixOS; the ambient PATH is deliberately minimal, but the full
nixpkgs set is always one command away. "command not found" is not a blocker.
**How to apply:**
- One-off run: `nix run nixpkgs#<pkg> -- <args>` (e.g. `nix run nixpkgs#jq -- .`).
- Tools on PATH for a session: `nix shell nixpkgs#<pkg> [nixpkgs#<pkg2> ...]`,
then run commands normally.
- Legacy form also works: `nix-shell -p <pkg> --run '<cmd>'`.
- Prefer this over hand-rolling a substitute for a tool that exists in nixpkgs.
@@ -1,12 +1,13 @@
# Graphical desktop layer: GUI apps, Wayland session env, and cursor theme.
# Imported only on hosts that run Sway (MBP, T400, Mac Pro); never pulled onto
# the headless WSL host. Login (and the Sway session launch) is handled by the
# greetd/ReGreet greeter -- see ../swaywm.nix -- so there is no tty1 autostart.
# greetd/ReGreet greeter -- see ../modules/sway.nix -- so there is no tty1
# autostart.
{
pkgs,
config,
inputs,
username,
identity,
...
}:
{
@@ -89,7 +90,7 @@
};
# Firefox is themed at the browser level (it does not follow the GTK theme).
# The system installs the binary (programs.firefox in ../user.nix); here
# The system installs the binary (programs.firefox in ../modules/users.nix); here
# home-manager owns only the profile, hence package = null. Apply the
# Catppuccin Mocha theme add-on (only the mauve accent is packaged upstream;
# the rest of the desktop uses blue) and make content + UI dark.
@@ -101,7 +102,7 @@
# stateVersion<26.05 default-change warning (the new XDG path depends on
# Firefox's own profile support).
configPath = ".mozilla/firefox";
profiles.${username} = {
profiles.${identity.username} = {
id = 0;
isDefault = true;
extensions = {
+10 -15
View File
@@ -1,13 +1,13 @@
# Version control: git + delta pager + commitizen + lazygit. The work host
# layers commit signing and an email override on top (see work.nix).
# Version control: git + delta + commitizen + lazygit. Committer identity comes
# from the per-user `identity` arg (the registry). See README "Users".
{
pkgs,
lib,
fullName,
identity,
...
}:
let
ctp = import ../catppuccin-mocha.nix;
ctp = import ../lib/catppuccin-mocha.nix;
in
{
home.packages = [
@@ -18,10 +18,9 @@ in
enable = true;
package = pkgs.gitFull;
settings = {
user.name = fullName;
# Personal identity. mkDefault so the work module overrides it on the work
# host (and to merge cleanly with that plain definition there).
user.email = lib.mkDefault "iam@emmathe.dev";
user.name = identity.fullName;
# mkDefault so a host-specific module can still override it.
user.email = lib.mkDefault identity.email;
push.autoSetupRemote = true;
init.defaultBranch = "main";
@@ -77,14 +76,10 @@ in
cc = "!cz commit";
};
# SSH commit signing. This personal key is the default; the work module
# (work.nix) overrides it with the work key on the EDaaS host, the same way
# user.email is overridden -- so mkDefault here lets that plain definition
# win instead of conflicting. gpgsign is mkDefault too, so a host without
# the key in its ssh-agent can override it to false rather than fail every
# commit.
# SSH signing, key from the registry. mkDefault so a host lacking the key
# in its agent can set gpgsign = false instead of failing every commit.
gpg.format = "ssh";
user.signingkey = lib.mkDefault "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDxHvdMTOzpFWUFMtCP7C/4tIOUO3GIO2QPvaifSnWH lyrathorpe@Lyra-MBA";
user.signingkey = lib.mkDefault identity.signingKey;
commit.gpgsign = lib.mkDefault true;
tag.gpgsign = lib.mkDefault true;
};
+1 -4
View File
@@ -8,7 +8,7 @@
}:
let
# Shared Catppuccin Mocha palette: raw 6-hex strings, no leading "#".
ctp = import ../catppuccin-mocha.nix;
ctp = import ../lib/catppuccin-mocha.nix;
in
{
imports = [
@@ -341,9 +341,6 @@ in
IdentityFile = "~/.ssh/code.emmathe.dev";
IdentitiesOnly = true;
};
"dockerpi.inf.cbg.emmaisvery.gay" = {
User = "emmathorpe";
};
};
};
+5 -6
View File
@@ -2,7 +2,7 @@
# Imported via ./desktop.nix, so only graphical hosts get it.
#
# The compositor binary, PAM and the polkit *daemon* come from the system-level
# programs.sway (see ../swaywm.nix); package = null below reuses it instead of
# programs.sway (see ../modules/sway.nix); package = null below reuses it instead of
# pulling a second Sway. The polkit authentication *agent* (the thing that draws
# the GUI auth dialog) is a user service started here. home-manager owns the user
# config (~/.config/sway) and wires the systemd user session (sway-session.target),
@@ -20,7 +20,7 @@ let
# Catppuccin Mocha (shared with the ReGreet greeter). Raw hex; prefix "#"
# where a consumer needs it -- Sway/i3status/dunst want "#", foot/swaylock do
# not.
ctp = import ../catppuccin-mocha.nix;
ctp = import ../lib/catppuccin-mocha.nix;
# Focused-window screenshot -> swappy editor (the dotfiles' grimshot.sh logic).
# Full store paths so it needs nothing on PATH.
@@ -334,13 +334,12 @@ in
];
};
# Night light. Manual location (no geoclue dependency); adjust the coordinates
# to taste. Warmer at night, neutral by day.
# Night light. Manual location (no geoclue dependency); warmer at night,
# neutral by day. Coordinates come from the per-user module (e.g.
# users/lyrathorpe/home.nix), not this shared module.
services.gammastep = {
enable = true;
provider = "manual";
latitude = 51.5;
longitude = -0.13; # London-ish; set to your actual location
temperature = {
day = 6500;
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
```
@@ -1,5 +1,5 @@
# Default nix-darwin host. Minimal macOS baseline; the user environment
# (shell, git, editor) is carried by the shared ./lyrathorpe/home modules,
# (shell, git, editor) is carried by the shared ./home modules,
# the same ones used by the Linux hosts. nixpkgs.hostPlatform is set by
# mkDarwinHost in flake.nix.
{ pkgs, username, ... }:
+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
```
@@ -62,12 +62,11 @@
features.swayDesktop.enable = false;
# Keep this user's systemd --user instance running without an open login
# session, so the home-manager user timer (renovate-review.nix) fires on
# schedule even when no terminal is attached. On WSL the timer still only runs
# while the distro itself is up; Persistent=true catches up a missed run at
# next start.
users.users.emmathorpe.linger = true;
# NOTE: this user's systemd --user lingering -- so the home-manager renovate
# timer fires without an open login session -- is enabled from the host table
# in flake.nix (users.emmathorpe.linger = true) and applied by
# modules/users.nix.
# programs.nix-ld is enabled for all NixOS hosts in common-nixos.nix.
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
@@ -48,7 +48,7 @@ gigabit ports.
## Login
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
to the Dvorak layout to match the console and Sway session. Set the user
password (`passwd lyrathorpe`) after install, or the greeter cannot
@@ -26,10 +26,8 @@
# workstation.nix is the backstop).
zramSwap.enable = true;
# This host accepts SSH, so open 22 (the firewall itself is enabled in
# workstation.nix with a default-deny policy).
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
# sshd (daemon, port 22, key-only policy) comes from ../../modules/ssh.nix;
# the firewall itself is enabled in workstation.nix with a default-deny policy.
# Dual Harpertown Xeon microcode. Redistributable firmware (GPU/NIC blobs) is
# enabled in workstation.nix.
@@ -42,7 +40,7 @@
# - ATI Radeon HD 2600 XT -> "radeon" (older) or "amdgpu" KMS
# - NVIDIA GeForce 8800 GT -> "nouveau" KMS
# These come up automatically via the in-tree drivers + KMS, and the graphics
# stack itself is enabled by swaywm.nix. If a card needs to be forced, add it
# stack itself is enabled by modules/sway.nix. If a card needs to be forced, add it
# here, e.g. `services.xserver.videoDrivers = [ "radeon" ];` (or "nouveau"),
# and/or `boot.initrd.kernelModules = [ "radeon" ];` in
# hardware-configuration.nix for early KMS.
@@ -15,7 +15,7 @@ Headless `aarch64-linux` server with two roles:
```sh
nixos-generate-config --root /mnt
# 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
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
```
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)
@@ -15,7 +15,7 @@
# (which selects by the local hostname) resolves without an explicit -H flag.
networking.hostName = "lyrathorpe-rpi5";
# Headless server: the Sway desktop is intentionally not set up. swaywm.nix is
# Headless server: the Sway desktop is intentionally not set up. modules/sway.nix is
# not imported and features.swayDesktop.enable defaults to false (declared in
# system/modules/features.nix), so this host keeps plain TTY/SSH login.
@@ -25,15 +25,12 @@
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
# Remote administration. Key-only policy and the authorized key come from
# ../../modules/ssh.nix; here we just enable the daemon and open the port.
services.openssh.enable = true;
# Remote administration: the daemon, port 22 and key-only policy all come from
# ../../modules/ssh.nix.
# Default-deny inbound. Open only SSH here; the Docker and nginx submodules
# open their own ports (Docker via a source-restricted nftables rule, nginx
# via 80/443). List-valued, so these merge with the submodule definitions.
# Default-deny inbound; the Docker and nginx submodules open their own ports
# (Docker via a source-restricted nftables rule, nginx via 80/443).
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
# See `man configuration.nix` / the stateVersion docs before changing.
system.stateVersion = "26.05";
@@ -8,6 +8,10 @@
# 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.
{ ... }:
let
# LAN allowed to reach the unauthenticated Docker TCP socket (see SECURITY above).
trustedSubnet = "10.187.1.0/24";
in
{
virtualisation.docker.enable = true;
@@ -29,6 +33,6 @@
# CIDR to match the LAN that should reach the Docker API.
networking.nftables.enable = true;
networking.firewall.extraInputRules = ''
ip saddr 10.187.1.0/24 tcp dport 2375 accept
ip saddr ${trustedSubnet} tcp dport 2375 accept
'';
}
@@ -35,7 +35,7 @@ change and `radeon` stays idle.
## Login
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
to the Dvorak layout to match the console and Sway session. Set the user
password (`passwd lyrathorpe`) after install, or the greeter cannot
@@ -21,10 +21,8 @@
# Low-RAM host (4 GiB max): a compressed RAM swap reduces disk paging.
zramSwap.enable = true;
# This host accepts SSH, so open 22 (the firewall itself is enabled in
# laptop.nix with a default-deny policy).
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
# sshd (daemon, port 22, key-only policy) comes from ../../modules/ssh.nix;
# the firewall itself is enabled in laptop.nix with a default-deny policy.
# Intel Core 2 (Penryn) microcode. Redistributable firmware (enabled in
# workstation.nix) supplies the iwlwifi blobs (Intel WiFi Link 5100/5300) and
@@ -1,6 +1,6 @@
# Catppuccin Mocha palette. Raw 6-digit hex (no leading "#"); consumers add a
# "#" where their format needs it. Shared by the Sway desktop theming
# (home/sway.nix) and the ReGreet greeter (swaywm.nix) so the two stay in sync.
# (home/sway.nix) and the ReGreet greeter (modules/sway.nix) so the two stay in sync.
{
base = "1e1e2e";
mantle = "181825";
-31
View File
@@ -1,31 +0,0 @@
{
config,
pkgs,
lib,
username,
fullName,
...
}:
{
programs.zsh.enable = true;
users.users.${username} = {
isNormalUser = true;
home = "/home/${username}";
description = fullName;
extraGroups = [
"wheel"
"docker"
];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDxHvdMTOzpFWUFMtCP7C/4tIOUO3GIO2QPvaifSnWH lyrathorpe@Lyra-MBA"
];
shell = pkgs.zsh;
};
programs.firefox = lib.mkIf (config.features.swayDesktop.enable == true) {
enable = true;
};
programs.thunderbird = lib.mkIf (config.features.swayDesktop.enable == true) {
enable = true;
};
}
@@ -2,7 +2,7 @@
# shared ./workstation.nix base and swaps the mobile Wi-Fi backend for wired
# NetworkManager. A desktop host also sets `portable = false` in its host-table
# entry (flake.nix), which drops the battery block and brightness keybindings
# from the Sway bar -- see lyrathorpe/home/sway.nix.
# from the Sway bar -- see home/sway.nix.
{ ... }:
{
imports = [ ./workstation.nix ];
@@ -2,9 +2,9 @@
# baseModules in flake.nix). Declaring the flags here -- rather than inside the
# module that implements them -- means a host can read or set a flag without
# importing the (often large) implementation module. In particular,
# features.swayDesktop.enable is read by lyrathorpe/user.nix on every host, but a
# features.swayDesktop.enable is read by modules/users.nix on every host, but a
# headless host (e.g. the Pi) must be able to leave it at its default without
# pulling in lyrathorpe/swaywm.nix. The implementation lives in swaywm.nix,
# pulling in modules/sway.nix. The implementation lives in modules/sway.nix,
# gated on this flag.
{ lib, ... }:
{
@@ -2,7 +2,7 @@
# flake.nix. Shared graphical-workstation settings live in ./workstation.nix;
# the only laptop-specific bit is the Wi-Fi backend. Mobile home-manager
# components (battery block, brightness keys) are gated by the `portable` flag
# threaded through mkHost -- see lyrathorpe/home/sway.nix.
# threaded through mkHost -- see home/sway.nix.
{ ... }:
{
imports = [ ./workstation.nix ];
+14
View File
@@ -0,0 +1,14 @@
# sshd for the hosts that run it (T400, Mac Pro, RPi5): enable the daemon, open
# port 22, and apply a key-only policy. Authorized keys are owned per-user by the
# registry (modules/users.nix), not here.
{ ... }:
{
services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
services.openssh.settings = {
PasswordAuthentication = false; # keys only
KbdInteractiveAuthentication = false; # no keyboard-interactive fallback
PermitRootLogin = "no";
};
}
+2 -2
View File
@@ -7,8 +7,8 @@
let
cfg = config.features.swayDesktop;
# Catppuccin Mocha (shared with the Sway desktop, see lyrathorpe/home/sway.nix).
ctp = import ./catppuccin-mocha.nix;
# Catppuccin Mocha (shared with the Sway desktop, see home/sway.nix).
ctp = import ../lib/catppuccin-mocha.nix;
in
{
# The features.swayDesktop.enable option is declared in
+38
View File
@@ -0,0 +1,38 @@
# System user accounts, built from the registry (users/registry.nix) for the
# host's `hostUsers` set. See README "Users".
{
config,
pkgs,
lib,
hostUsers,
userRegistry,
...
}:
{
programs.zsh.enable = true;
users.users = lib.mapAttrs (
name: spec:
let
id = userRegistry.${name};
in
{
isNormalUser = true;
home = "/home/${name}";
description = id.fullName;
inherit (id) extraGroups;
openssh.authorizedKeys.keys = id.sshAuthorizedKeys;
shell = pkgs.zsh;
}
# linger opt-in (host table); left unmanaged when unset.
// lib.optionalAttrs (spec ? linger) { inherit (spec) linger; }
) hostUsers;
programs.firefox = lib.mkIf (config.features.swayDesktop.enable == true) {
enable = true;
};
programs.thunderbird = lib.mkIf (config.features.swayDesktop.enable == true) {
enable = true;
};
}
-19
View File
@@ -1,19 +0,0 @@
# Key-only SSH hardening, imported by the hosts that run sshd (T400, Mac Pro).
# The host config still does `services.openssh.enable = true` and opens port 22
# next to where it documents the listening service; this module only tightens
# the policy and installs the authorized key, so a host opting into sshd cannot
# accidentally ship password/root login.
{ username, ... }:
{
services.openssh.settings = {
PasswordAuthentication = false; # keys only
KbdInteractiveAuthentication = false; # no keyboard-interactive fallback
PermitRootLogin = "no";
};
# The key permitted to log in as the primary user. Add more entries here as
# new client machines are provisioned.
users.users.${username}.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDxHvdMTOzpFWUFMtCP7C/4tIOUO3GIO2QPvaifSnWH lyrathorpe@Lyra-MBA"
];
}
@@ -1,6 +1,11 @@
# Home-manager module for the work (EDaaS/WSL) profile: corporate git signing,
# work toolchain packages and tmux tweaks. Imported only by the work host.
{ pkgs, lib, ... }:
# Work (EDaaS/WSL) home profile: corporate toolchain + tmux tweaks. Git identity
# comes from the registry (users/registry.nix), not here.
{
pkgs,
lib,
inputs,
...
}:
{
# Host-scoped extras for this machine only (the EDaaS/WSL host).
@@ -12,15 +17,6 @@
# programs.ssh (shell.nix) take it over. The ssh-agent below still runs.
programs.ssh.enable = lib.mkForce false;
programs.git = {
settings = {
commit.gpgsign = true;
tag.gpgsign = true;
gpg.format = "ssh";
user.signingkey = "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAJMVgeRKnfX1G8coU3nAobI485aeUpGTMqH7+zbKI8o emma.thorpe@cloud.com";
user.email = "emma.thorpe@citrix.com";
};
};
home.packages = [
pkgs.kubectl
pkgs.argo-rollouts
@@ -33,7 +29,6 @@
pkgs.powershell
pkgs.nuget
pkgs.gedit
pkgs.lens
pkgs.python3
pkgs.gnumake
pkgs.gcc
@@ -57,8 +52,10 @@
docker = "/run/current-system/sw/bin/docker";
};
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 = ''
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 = {
@@ -66,7 +63,7 @@
};
# LSP servers only relevant to work: C# (omnisharp) and Helm charts (helm_ls).
# The shared editor (lyrathorpe/home/editor.nix) carries the universal ones;
# The shared editor (home/editor.nix) carries the universal ones;
# these are gated to this host so the heavy omnisharp closure stays off the
# personal machines. Tree-sitter grammars (highlighting) remain global there.
programs.nixvim.plugins.lsp.servers = {
+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;
};
}
+28
View File
@@ -0,0 +1,28 @@
# User identity registry -- pure data, keyed by username. See README "Users".
# (`identity.username` is injected by mkHost, so it is not repeated here.)
{
lyrathorpe = {
fullName = "Lyra Thorpe";
email = "iam@emmathe.dev";
extraGroups = [
"wheel"
"docker"
];
sshAuthorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDxHvdMTOzpFWUFMtCP7C/4tIOUO3GIO2QPvaifSnWH lyrathorpe@Lyra-MBA"
];
signingKey = "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDxHvdMTOzpFWUFMtCP7C/4tIOUO3GIO2QPvaifSnWH lyrathorpe@Lyra-MBA";
};
emmathorpe = {
fullName = "Emma Thorpe";
email = "emma.thorpe@citrix.com";
extraGroups = [
"wheel"
"docker"
];
# No personal key on file yet; add one if SSH login as emmathorpe is wanted.
sshAuthorizedKeys = [ ];
signingKey = "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAJMVgeRKnfX1G8coU3nAobI485aeUpGTMqH7+zbKI8o emma.thorpe@cloud.com";
};
}