Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39434b3929 | ||
|
|
1ff333a896 | ||
|
|
9d199bc087 | ||
|
|
c7adcccbb3 | ||
|
|
dcc13f94e0 | ||
|
|
d30d8f9892 | ||
|
|
dfafac8de9 | ||
|
|
d9464009f0 | ||
|
|
d654eac1e2 | ||
|
|
d4e7475db9 | ||
|
|
0f7fb7f78a | ||
|
|
0d13581896 | ||
|
|
526e6a08e2 | ||
|
|
9e749cce2b | ||
|
|
1766fb7b3f | ||
|
|
dba73e1199 | ||
|
|
e9835372cd | ||
|
|
bd613ef07f | ||
|
|
c5b41ba6fd | ||
|
|
7041dfebfa | ||
|
|
4d6ad47837 | ||
|
|
f471d226e0 | ||
|
|
10f713103c | ||
|
|
cc6cb24c78 | ||
|
|
240facdbbb | ||
|
|
c1456decaf | ||
|
|
e06495ae69 | ||
|
|
6868182ef5 | ||
|
|
90a57ab73b | ||
|
|
75f4e22624 | ||
|
|
cf96fec63e | ||
|
|
3cdf4d4e54 | ||
|
|
f61a206977 | ||
|
|
1d5a5adbcc | ||
|
|
4029866ed4 | ||
|
|
66b27517ba | ||
|
|
6b43e76457 | ||
|
|
cbf2fdac42 | ||
|
|
1fdd048eed | ||
|
|
9b72a81d43 | ||
|
|
2f0302d66e | ||
|
|
256a9a9745 | ||
|
|
b746d58812 | ||
|
|
67963ed0e0 | ||
|
|
7bcc5feb35 | ||
|
|
9759cb70cf | ||
|
|
4d27b29233 | ||
|
|
dbc30b4b0e | ||
|
|
86ef677f2f | ||
|
|
a65771ccac | ||
|
|
89e55f4365 | ||
|
|
fee2f66385 | ||
|
|
72770a4ddb | ||
|
|
6d9e4443e1 | ||
|
|
7d504e68be |
@@ -26,7 +26,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
# Full history so the detect step can diff the PR against its base.
|
# Full history so the detect step can diff the PR against its base.
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
# Nix drives the formatting check, so install it unconditionally.
|
# Nix drives the formatting check, so install it unconditionally.
|
||||||
- name: Install Nix
|
- name: Install Nix
|
||||||
uses: cachix/install-nix-action@a49548c11d9846ad46ecc0115273879b045f001c # v31
|
uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
|
||||||
with:
|
with:
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
experimental-features = nix-command flakes
|
experimental-features = nix-command flakes
|
||||||
|
|||||||
@@ -42,6 +42,17 @@ prettier formats `*.md`, so **documentation edits must be run through `nix fmt`*
|
|||||||
exactly like code. prettier re-aligns Markdown tables in particular; hand-editing
|
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.
|
a table almost always leaves it non-conformant and fails the `formatting` check.
|
||||||
|
|
||||||
|
Prose documentation lives in `docs/` and is **published** to
|
||||||
|
<https://docs.lyrapup.pet/nixfiles/> by the separate `docs-site` repo, which
|
||||||
|
clones this one at build time. Two consequences when editing docs:
|
||||||
|
|
||||||
|
- A markdown file outside `docs/` (other than the root `README.md`) is not
|
||||||
|
synced and will never appear on the site. Put new prose in `docs/`.
|
||||||
|
- Links must follow the rules in the README's "Documentation" section: absolute
|
||||||
|
Gitea URLs to source files, relative links between `docs/` pages, and
|
||||||
|
absolute `docs.lyrapup.pet` URLs from the root README into `docs/`. The site
|
||||||
|
builds non-strict, so a broken link is silent.
|
||||||
|
|
||||||
The CI `formatting` step runs on **every** PR — including docs- and config-only
|
The CI `formatting` step runs on **every** PR — including docs- and config-only
|
||||||
changes — so a Markdown/YAML/JSON edit is format-checked before merge, not just
|
changes — so a Markdown/YAML/JSON edit is format-checked before merge, not just
|
||||||
after it lands on `main`. (The heavier `deadnix`/`statix`/`pre-commit` lints and
|
after it lands on `main`. (The heavier `deadnix`/`statix`/`pre-commit` lints and
|
||||||
|
|||||||
@@ -5,16 +5,17 @@ single flake.
|
|||||||
|
|
||||||
## Hosts
|
## Hosts
|
||||||
|
|
||||||
Defined in the host table in [`flake.nix`](./flake.nix):
|
Defined in the host table in [`flake.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/flake.nix):
|
||||||
|
|
||||||
| Configuration | System | Machine |
|
| Configuration | System | Machine |
|
||||||
| --------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- |
|
| --------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `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](https://docs.lyrapup.pet/nixfiles/hosts/t400/) |
|
||||||
| `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](https://docs.lyrapup.pet/nixfiles/hosts/macpro31/) |
|
||||||
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) — [notes](./hosts/EDaaS/README.md) |
|
| `lyrathorpe-console` | `x86_64-linux` | Living-room games machine (Haswell i7 + GTX 1070) on a television — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/console/) |
|
||||||
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](./hosts/RPi5/README.md) |
|
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) — [notes](https://docs.lyrapup.pet/nixfiles/hosts/edaas/) |
|
||||||
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) — [notes](./hosts/Darwin/README.md) |
|
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](https://docs.lyrapup.pet/nixfiles/hosts/rpi5/) |
|
||||||
|
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) — [notes](https://docs.lyrapup.pet/nixfiles/hosts/darwin/) |
|
||||||
|
|
||||||
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),
|
||||||
@@ -30,7 +31,8 @@ profiles. The full module catalogue is below.
|
|||||||
flake.nix # inputs, mkHost/mkDarwinHost, the host tables, dev shell + checks
|
flake.nix # inputs, mkHost/mkDarwinHost, the host tables, dev shell + checks
|
||||||
flake.lock # pinned input revisions (Renovate keeps this fresh)
|
flake.lock # pinned input revisions (Renovate keeps this fresh)
|
||||||
modules/ # reusable NixOS system modules (see "Module catalogue")
|
modules/ # reusable NixOS system modules (see "Module catalogue")
|
||||||
home/ # home-manager profile: shell, git, editor, claude, desktop, sway
|
home/ # home-manager profile: shell, git, editor, claude, secret-service, desktop, sway
|
||||||
|
docs/ # all prose documentation; published to docs.lyrapup.pet (see "Documentation")
|
||||||
users/ # identity registry + per-user home extras (see "Users")
|
users/ # identity registry + per-user home extras (see "Users")
|
||||||
hosts/<Name>/ # per-machine config: configuration.nix + hardware-configuration.nix
|
hosts/<Name>/ # per-machine config: configuration.nix + hardware-configuration.nix
|
||||||
lib/ # small pure helpers (currently the Catppuccin Mocha palette)
|
lib/ # small pure helpers (currently the Catppuccin Mocha palette)
|
||||||
@@ -50,22 +52,22 @@ host's table entry.
|
|||||||
|
|
||||||
## Module catalogue
|
## Module catalogue
|
||||||
|
|
||||||
Reusable NixOS modules under [`modules/`](./modules). "Imported by" says how a
|
Reusable NixOS modules under [`modules/`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/modules). "Imported by" says how a
|
||||||
module reaches a host: **baseModules** (every NixOS host, via `flake.nix`),
|
module reaches a host: **baseModules** (every NixOS host, via `flake.nix`),
|
||||||
**host table** (listed explicitly per host in `flake.nix`), or **transitively**
|
**host table** (listed explicitly per host in `flake.nix`), or **transitively**
|
||||||
(pulled in by another module's `imports`).
|
(pulled in by another module's `imports`).
|
||||||
|
|
||||||
| Module | Imported by | What it does / when to use it |
|
| 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. |
|
| `common-nixos.nix` | baseModules (all NixOS) | Timezone/locale, store hygiene (auto-optimise, big download buffer, **no** auto-GC), the nix-community binary cache, `nix-ld`, **sudo-rs** in place of sudo, base CLI (`git`, `fastfetch`), and the fleet-wide font stack. |
|
||||||
| `users.nix` | baseModules (all NixOS) | Builds `users.users` from the registry for the host's `hostUsers`; enables zsh; enables Firefox + Thunderbird **only** when `features.swayDesktop.enable` is on. Applies per-user `linger`. |
|
| `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. |
|
| `features.nix` | baseModules (all NixOS) | Declares the feature-flag options (`features.swayDesktop.enable`, `features.claudeCode.enable`) so any host can read/set them without importing the heavy implementation module, plus the CPU capability fact they derive from (`features.cpu.microarchLevel`) and the assertion that guards it. See "CPU capability gating". |
|
||||||
| `workstation.nix` | transitively (via laptop/desktop) | Form-factor-agnostic base for physical graphical hosts: turns on `swayDesktop`, Dvorak console, PipeWire, firewall (default-deny), fstrim, earlyoom, fwupd, thermald (x86), redistributable fw. |
|
| `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. |
|
| `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. |
|
| `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`. |
|
| `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`. |
|
| `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"). |
|
| `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
|
Form-factor decision: a **laptop** imports `laptop.nix` (default
|
||||||
`portable = true`); a **wired desktop** imports `desktop.nix` and sets
|
`portable = true`); a **wired desktop** imports `desktop.nix` and sets
|
||||||
@@ -74,16 +76,39 @@ Form-factor decision: a **laptop** imports `laptop.nix` (default
|
|||||||
serves. `portable` is threaded through to `home/sway.nix`, which drops the
|
serves. `portable` is threaded through to `home/sway.nix`, which drops the
|
||||||
battery block and brightness keys on desktops.
|
battery block and brightness keys on desktops.
|
||||||
|
|
||||||
|
## CPU capability gating
|
||||||
|
|
||||||
|
Not every host can run everything the fleet installs. Nix cannot probe the CPU
|
||||||
|
(evaluation is pure, and a host may be built elsewhere), so each machine
|
||||||
|
declares what it is and the shared modules derive from that:
|
||||||
|
|
||||||
|
- `features.cpu.microarchLevel` — the x86-64 psABI level the CPU implements
|
||||||
|
(1 = baseline, 2 = SSE4.2/POPCNT, 3 = AVX2, 4 = AVX-512). Defaults to **2**;
|
||||||
|
only a host older than that sets it (the Mac Pro 3,1's 2008 Harpertown Xeons
|
||||||
|
are level 1). Ignored on non-x86_64 hosts.
|
||||||
|
- `features.claudeCode.enable` — derived: on unless the host is below
|
||||||
|
x86-64-v2, because Claude Code's Node runtime needs SSE4.2/POPCNT.
|
||||||
|
[`home/claude.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/claude.nix) reads it through home-manager's
|
||||||
|
`osConfig` and installs nothing (CLI, `CLAUDE.md`, output style, memory
|
||||||
|
symlink) when it is off. Hosts with no such option — the Darwin host and the
|
||||||
|
standalone `homeConfigurations` — fall back to enabled.
|
||||||
|
- An assertion in `features.nix` fails evaluation if a host force-enables a
|
||||||
|
flag its declared CPU level cannot support, so the mistake surfaces in
|
||||||
|
`nix flake check`/CI rather than as an illegal-instruction crash on the box.
|
||||||
|
|
||||||
|
Adding another CPU-sensitive tool means deriving one more flag there, not
|
||||||
|
editing every host.
|
||||||
|
|
||||||
## Users
|
## Users
|
||||||
|
|
||||||
Identity is data, kept separate from the reusable modules:
|
Identity is data, kept separate from the reusable modules:
|
||||||
|
|
||||||
- [`users/registry.nix`](./users/registry.nix) — one entry per user (display
|
- [`users/registry.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/registry.nix) — one entry per user (display
|
||||||
name, email, supplementary groups, authorized + signing keys). This is the
|
name, email, supplementary groups, authorized + signing keys). This is the
|
||||||
single source of identity; no user data is hardcoded in the modules.
|
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
|
- 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
|
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))
|
per-user modules, e.g. [`users/emmathorpe/work.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/emmathorpe/work.nix))
|
||||||
and optional per-host-user system bits such as `linger`.
|
and optional per-host-user system bits such as `linger`.
|
||||||
- `mkHost` builds each account from the registry and injects the matching
|
- `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
|
identity into that user's home config as the `identity` module arg. A host can
|
||||||
@@ -91,12 +116,13 @@ Identity is data, kept separate from the reusable modules:
|
|||||||
|
|
||||||
Per-user home extras live under `users/<name>/`:
|
Per-user home extras live under `users/<name>/`:
|
||||||
|
|
||||||
- [`users/lyrathorpe/home.nix`](./users/lyrathorpe/home.nix) — personal extras
|
- [`users/lyrathorpe/home.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/lyrathorpe/home.nix) — personal extras
|
||||||
(an ssh host shortcut, gammastep coordinates); imported on Lyra's hosts.
|
(an ssh host shortcut, gammastep coordinates); imported on Lyra's hosts.
|
||||||
- [`users/emmathorpe/work.nix`](./users/emmathorpe/work.nix) — the work
|
- [`users/emmathorpe/work.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/emmathorpe/work.nix) — the work
|
||||||
toolchain (kubectl/helm/az/etc.), work-only LSP servers, and the corporate ssh
|
toolchain (kubectl/helm/az/etc.), work-only LSP servers, the corporate ssh
|
||||||
handling; imports
|
handling, and the headless Secret Service that gcx needs for its keychain
|
||||||
[`users/emmathorpe/renovate-review.nix`](./users/emmathorpe/renovate-review.nix),
|
tokens (see [`home/secret-service.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/secret-service.nix)); imports
|
||||||
|
[`users/emmathorpe/renovate-review.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/emmathorpe/renovate-review.nix),
|
||||||
the daily headless Renovate-PR review timer (EDaaS only).
|
the daily headless Renovate-PR review timer (EDaaS only).
|
||||||
|
|
||||||
### Portable home (off-NixOS / external consumers)
|
### Portable home (off-NixOS / external consumers)
|
||||||
@@ -155,17 +181,20 @@ 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):
|
||||||
[`home/README.md`](./home/README.md).
|
[`docs/shell.md`](https://docs.lyrapup.pet/nixfiles/shell/).
|
||||||
|
- Which classic utilities are shadowed by modern replacements, and the flag
|
||||||
|
differences that will bite:
|
||||||
|
[`docs/shell.md` → "Replacing the classics"](https://docs.lyrapup.pet/nixfiles/shell/#replacing-the-classics).
|
||||||
- All Sway / tmux / foot / zsh keyboard shortcuts:
|
- All Sway / tmux / foot / zsh keyboard shortcuts:
|
||||||
[`home/KEYBINDINGS.md`](./home/KEYBINDINGS.md).
|
[`docs/keybindings.md`](https://docs.lyrapup.pet/nixfiles/keybindings/).
|
||||||
|
|
||||||
## Login / greeter
|
## Login / greeter
|
||||||
|
|
||||||
Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running
|
Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running
|
||||||
ReGreet inside the `cage` kiosk compositor — implemented in
|
ReGreet inside the `cage` kiosk compositor — implemented in
|
||||||
[`modules/sway.nix`](./modules/sway.nix), gated on
|
[`modules/sway.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/modules/sway.nix), gated on
|
||||||
`features.swayDesktop.enable` (the option is declared in
|
`features.swayDesktop.enable` (the option is declared in
|
||||||
[`modules/features.nix`](./modules/features.nix), so headless hosts
|
[`modules/features.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/modules/features.nix), so headless hosts
|
||||||
can leave it off without importing `modules/sway.nix`). The greeter is forced to Dvorak
|
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
|
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
|
Raspberry Pi server) keep plain TTY login. The target account needs a password
|
||||||
@@ -185,6 +214,38 @@ To refresh them, copy the firmware extracted during the Asahi install (from
|
|||||||
[Asahi NixOS docs](https://github.com/tpwrules/nixos-apple-silicon)) into
|
[Asahi NixOS docs](https://github.com/tpwrules/nixos-apple-silicon)) into
|
||||||
`modules/firmware/` and commit with `git add -f`.
|
`modules/firmware/` and commit with `git add -f`.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
All prose documentation lives in [`docs/`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/docs); this README is the overview. The pages are
|
||||||
|
published to **<https://docs.lyrapup.pet/nixfiles/>** by the
|
||||||
|
[`docs-site`](https://code.emmathe.dev/lyrathorpe/docs-site) repository, which clones this repo on
|
||||||
|
every build (on its own push, nightly, or on demand) and assembles the tree:
|
||||||
|
|
||||||
|
```
|
||||||
|
README.md -> docs/nixfiles/index.md # this file becomes the section landing page
|
||||||
|
docs/ -> docs/nixfiles/ # everything here, ordering from docs/.pages
|
||||||
|
```
|
||||||
|
|
||||||
|
Nothing is pushed from this side and there is no build step here — editing a
|
||||||
|
page and merging is all that is required. Files outside `docs/` (bar this
|
||||||
|
README) are **not** synced, so a doc kept next to the code it describes will
|
||||||
|
never appear on the site.
|
||||||
|
|
||||||
|
### Linking rules
|
||||||
|
|
||||||
|
The site has no copy of the source tree, and this README is republished at a
|
||||||
|
different depth from the rest of `docs/`. Both facts break naive relative
|
||||||
|
links, so:
|
||||||
|
|
||||||
|
| Link from | To | Use |
|
||||||
|
| ----------------- | ----------------------- | ---------------------------------------------------------------- |
|
||||||
|
| anywhere | a source file or dir | absolute `https://code.emmathe.dev/.../src/branch/main/…` |
|
||||||
|
| a page in `docs/` | another page in `docs/` | relative (`./keybindings.md`) — correct in Gitea and on the site |
|
||||||
|
| this README | a page in `docs/` | absolute `https://docs.lyrapup.pet/nixfiles/…` |
|
||||||
|
|
||||||
|
`mkdocs build` runs non-strict on the docs-site side, so a broken link fails
|
||||||
|
silently rather than failing the build. Check links by hand when moving a page.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
A dev shell and a formatting/lint gate are wired through the flake:
|
A dev shell and a formatting/lint gate are wired through the flake:
|
||||||
@@ -200,7 +261,7 @@ A dev shell and a formatting/lint gate are wired through the flake:
|
|||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
[`.gitea/workflows/ci.yaml`](./.gitea/workflows/ci.yaml) runs `nix flake check`
|
[`.gitea/workflows/ci.yaml`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/.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. It always runs (no `paths:`
|
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
|
filter) so the required check never hangs pending; the heavy Nix steps are
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Section title and ordering for the MkDocs awesome-pages plugin on
|
||||||
|
# docs.lyrapup.pet.
|
||||||
|
#
|
||||||
|
# The title is set explicitly: with no entry in the site's nav, MkDocs derives
|
||||||
|
# the section name from the directory and renders it title-cased as "Nixfiles".
|
||||||
|
title: nixfiles
|
||||||
|
|
||||||
|
# `index.md` is this repository's root README, copied in by the docs-site build
|
||||||
|
# before this directory is synced over the top. The trailing `...` picks up any
|
||||||
|
# page added later, so a new file needs no edit here.
|
||||||
|
nav:
|
||||||
|
- index.md
|
||||||
|
- shell.md
|
||||||
|
- keybindings.md
|
||||||
|
- hosts
|
||||||
|
- ...
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
title: Hosts
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
# Console — living-room games machine
|
||||||
|
|
||||||
|
Flake host: `lyrathorpe-console`. Desktop (`portable = false`, imports
|
||||||
|
`../../modules/desktop.nix`). Files: `configuration.nix`, `nvidia.nix`,
|
||||||
|
`gaming.nix`, `hardware-configuration.nix`.
|
||||||
|
|
||||||
|
A 4th-generation Core i7 (Haswell) on a UEFI board with an NVIDIA GeForce GTX
|
||||||
|
1070 8 GB, wired to a television. It boots straight into Steam Big Picture and
|
||||||
|
is driven from the sofa with a Bluetooth controller; keyboard and mouse are
|
||||||
|
supported but secondary.
|
||||||
|
|
||||||
|
## Not installed yet
|
||||||
|
|
||||||
|
`hardware-configuration.nix` in this host directory is a **placeholder**, not a
|
||||||
|
hardware scan. It exists so the flake evaluates in CI and assumes the install
|
||||||
|
labels its partitions `nixos` (root, ext4) and `BOOT` (ESP, vfat). Replace the
|
||||||
|
whole file with the output of `nixos-generate-config` run on the machine and
|
||||||
|
commit that. If the labels do not match, the boot fails on a missing device
|
||||||
|
rather than touching the wrong disk.
|
||||||
|
|
||||||
|
Partition the disk GPT with an ESP (vfat, 512 MB is comfortable). Nothing else
|
||||||
|
in the host config depends on the disk layout.
|
||||||
|
|
||||||
|
## Bootloader
|
||||||
|
|
||||||
|
Ordinary PC UEFI firmware, so **systemd-boot** with
|
||||||
|
`canTouchEfiVariables = true` — unlike the Mac Pro, this board is trusted with
|
||||||
|
`efibootmgr` NVRAM writes.
|
||||||
|
|
||||||
|
`boot.loader.timeout = 0`: the boot menu is unreadable from a sofa and unusable
|
||||||
|
without a keyboard, so the default entry boots immediately. **Hold space at
|
||||||
|
power-on** to get the menu back and pick an older generation.
|
||||||
|
`configurationLimit = 10` stops the ESP filling up.
|
||||||
|
|
||||||
|
## Graphics — GTX 1070
|
||||||
|
|
||||||
|
Everything driver-related is in [`nvidia.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/hosts/Console/nvidia.nix).
|
||||||
|
The GTX 1070 is Pascal (GP104), so it is under the same driver constraint as the
|
||||||
|
Mac Pro's Quadro P400:
|
||||||
|
|
||||||
|
- **Driver branch 580** (`nvidiaPackages.legacy_580`), _not_ the nixpkgs default
|
||||||
|
(`production`, currently 595.x). 580 is the last branch supporting
|
||||||
|
Maxwell/Pascal/Volta, maintained as an LTS branch until Aug 2028; a newer
|
||||||
|
branch does not drive this card at all.
|
||||||
|
- `modesetting.enable = true` — mandatory for Wayland. Without
|
||||||
|
`nvidia-drm.modeset=1` neither gamescope nor wlroots gets a usable GBM device,
|
||||||
|
and both the Steam session and Sway fail to start.
|
||||||
|
- `open = false` — the open kernel modules require Turing or later.
|
||||||
|
- Sway runs with `--unsupported-gpu`; wlroots refuses the proprietary driver
|
||||||
|
otherwise. gamescope and cage/ReGreet need no such flag.
|
||||||
|
- `hardware.graphics.enable32Bit` pulls in the lib32 NVIDIA userspace that
|
||||||
|
32-bit Steam titles and Proton's 32-bit prefixes link against.
|
||||||
|
|
||||||
|
The driver is unfree, so it is **not in the binary cache**: the kernel module is
|
||||||
|
compiled on the machine. On a Haswell i7 that is a few minutes, not the Mac
|
||||||
|
Pro's ordeal, but it recurs on every kernel bump. The package names are
|
||||||
|
allowlisted in `unfreePackages` in [`flake.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/flake.nix).
|
||||||
|
|
||||||
|
Verify after a rebuild:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nvidia-smi
|
||||||
|
lsmod | grep nvidia # nvidia, nvidia_modeset, nvidia_drm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Session model — autologin into Steam
|
||||||
|
|
||||||
|
[`gaming.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/hosts/Console/gaming.nix)
|
||||||
|
sets `programs.steam.gamescopeSession.enable`, which registers a `steam.desktop`
|
||||||
|
Wayland session (gamescope wrapping Steam in tenfoot mode) and installs a
|
||||||
|
`steam-gamescope` launcher. greetd — already present on every Sway host for
|
||||||
|
ReGreet, see [`../../modules/sway.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/modules/sway.nix)
|
||||||
|
— gets an `initial_session` pointing at that launcher:
|
||||||
|
|
||||||
|
```
|
||||||
|
boot
|
||||||
|
└─ greetd initial_session (autologin as lyrathorpe)
|
||||||
|
└─ gamescope --steam -- steam -tenfoot -pipewire-dmabuf
|
||||||
|
├─ Steam library / Proton titles
|
||||||
|
├─ [non-Steam] RetroArch
|
||||||
|
└─ [non-Steam] Clone Hero
|
||||||
|
|
||||||
|
quit Steam → greetd default_session → ReGreet → pick Sway (keyboard + mouse)
|
||||||
|
```
|
||||||
|
|
||||||
|
So there is no greeter at boot and no keyboard needed. Quitting Steam drops back
|
||||||
|
to ReGreet, where the ordinary Sway session is available for everything else.
|
||||||
|
`services.greetd.restart` defaults to `false` once `initial_session` is set,
|
||||||
|
which is what stops a logout looping straight back into autologin.
|
||||||
|
|
||||||
|
Two details worth knowing:
|
||||||
|
|
||||||
|
- The launcher is referenced as `/run/current-system/sw/bin/steam-gamescope`.
|
||||||
|
The steam module builds that script privately and only adds it to
|
||||||
|
`environment.systemPackages`, so there is no package attribute to point at.
|
||||||
|
- `programs.gamescope.capSysNice = true` installs gamescope as a setcap wrapper
|
||||||
|
in `/run/wrappers/bin` instead of the system profile. That path precedes the
|
||||||
|
system profile on `PATH`, so `steam-gamescope` still resolves it.
|
||||||
|
|
||||||
|
The greeter is **Dvorak**, like every other host here (`modules/sway.nix` forces
|
||||||
|
`XKB_DEFAULT_VARIANT=dvorak` on cage). Only relevant if someone else has to type
|
||||||
|
a password.
|
||||||
|
|
||||||
|
### Adding RetroArch and Clone Hero to Big Picture
|
||||||
|
|
||||||
|
Both are installed system-wide but are not Steam titles. Add each once, from a
|
||||||
|
Sway session, via Steam's **Games → Add a Non-Steam Game**; they then appear in
|
||||||
|
Big Picture and inherit Steam Input, so the controller works in them without
|
||||||
|
further configuration.
|
||||||
|
|
||||||
|
## Emulation — RetroArch
|
||||||
|
|
||||||
|
`gaming.nix` builds RetroArch through `pkgs.retroarch-bare.wrapper`, which wires
|
||||||
|
up the packaged assets, core info and joypad autoconfig profiles. Cores:
|
||||||
|
|
||||||
|
| System | Core |
|
||||||
|
| -------------------------------------- | ------------------------ |
|
||||||
|
| NES | `nestopia` |
|
||||||
|
| SNES | `snes9x` |
|
||||||
|
| Game Boy / Color | `gambatte` |
|
||||||
|
| Game Boy Advance | `mgba` |
|
||||||
|
| Nintendo 64 | `mupen64plus` |
|
||||||
|
| Nintendo DS | `melonds` |
|
||||||
|
| GameCube / Wii | `dolphin` |
|
||||||
|
| Master System / Game Gear / Mega Drive | `genesis-plus-gx` |
|
||||||
|
| 32X / Mega CD | `picodrive` |
|
||||||
|
| Saturn | `beetle-saturn` |
|
||||||
|
| Dreamcast / NAOMI | `flycast` |
|
||||||
|
| PlayStation | `beetle-psx-hw` |
|
||||||
|
| PlayStation 2 | `pcsx2` (LRPS2) |
|
||||||
|
| PSP | `ppsspp` |
|
||||||
|
| Arcade | `fbneo`, `mame2003-plus` |
|
||||||
|
| DOS | `dosbox-pure` |
|
||||||
|
|
||||||
|
A handful of settings are applied on every launch via `--appendconfig`, so they
|
||||||
|
are fixed policy rather than saved preferences — changing them in the UI will
|
||||||
|
not stick. Everything else stays user-editable as normal.
|
||||||
|
|
||||||
|
- `menu_driver = ozone` — the controller-navigable menu.
|
||||||
|
- `video_fullscreen = true`.
|
||||||
|
- `input_menu_toggle_gamepad_combo = 2` — **L3+R3 opens the RetroArch menu**
|
||||||
|
from inside a running core. Without a pad combo there is no way to exit a game
|
||||||
|
without a keyboard. No retro system emulated here has L3/R3 on its own
|
||||||
|
controller, so the binding cannot collide with a game.
|
||||||
|
- `system_directory`, `savefile_directory`, `savestate_directory`,
|
||||||
|
`playlist_directory`, `screenshot_directory`, `thumbnails_directory` and
|
||||||
|
`rgui_browser_directory` — all pointed at the shared library described below,
|
||||||
|
rather than scattered through `~/.config/retroarch`.
|
||||||
|
|
||||||
|
An unrecognised key in an appended config is ignored **silently**, so those key
|
||||||
|
names are worth keeping in step with upstream if RetroArch is ever bumped
|
||||||
|
across a major version.
|
||||||
|
|
||||||
|
### BIOS files and expectations
|
||||||
|
|
||||||
|
Several cores need BIOS or firmware images that are not redistributable and are
|
||||||
|
therefore not packaged. Drop them in `/srv/games/bios`, which is RetroArch's
|
||||||
|
system directory on this host:
|
||||||
|
|
||||||
|
- **PlayStation 2** (`pcsx2`) — a PS2 BIOS dump. The core will not boot anything
|
||||||
|
without one.
|
||||||
|
- **Saturn** (`beetle-saturn`) — region BIOS images.
|
||||||
|
- **Dreamcast** (`flycast`) — `dc_boot.bin` / `dc_flash.bin` for most titles.
|
||||||
|
- **Nintendo DS** (`melonds`) — optional, but DSi mode and some titles want the
|
||||||
|
real BIOS/firmware.
|
||||||
|
|
||||||
|
Be honest about the two heaviest cores. `dolphin` and `pcsx2` are libretro ports
|
||||||
|
of emulators whose upstream effort goes into their **standalone** builds; the
|
||||||
|
cores lag on compatibility and are the first place to look when a GameCube, Wii
|
||||||
|
or PS2 title misbehaves. If a game does not cooperate, add the standalone
|
||||||
|
emulators to `environment.systemPackages` in `gaming.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
pkgs.dolphin-emu # GameCube / Wii
|
||||||
|
pkgs.pcsx2 # PlayStation 2
|
||||||
|
```
|
||||||
|
|
||||||
|
Both are controller-driven and can be added to Big Picture the same way as
|
||||||
|
RetroArch. The hardware is not the limit here — a GTX 1070 and a Haswell i7 run
|
||||||
|
PS2 and Wii comfortably.
|
||||||
|
|
||||||
|
Five cores (`snes9x`, `genesis-plus-gx`, `picodrive`, `fbneo`,
|
||||||
|
`mame2003-plus`) carry upstream licences with non-commercial or
|
||||||
|
no-redistribution-for-profit clauses, so their derivation names are in
|
||||||
|
`unfreePackages` in `flake.nix`. Nothing else in the core set needs it.
|
||||||
|
|
||||||
|
## Games library layout
|
||||||
|
|
||||||
|
Content lives under `/srv/games`, deliberately outside any home directory: it is
|
||||||
|
bulky, it is the thing most likely to move to its own disk, and it is shared
|
||||||
|
between Steam, RetroArch and Clone Hero rather than owned by one of them.
|
||||||
|
Mounting a second drive at `/srv/games` is the only change that move needs.
|
||||||
|
|
||||||
|
`gaming.nix` creates the tree with `systemd.tmpfiles.rules` at every boot:
|
||||||
|
|
||||||
|
```
|
||||||
|
/srv/games/
|
||||||
|
├── roms/ # RetroArch content browser opens here
|
||||||
|
│ ├── nes/ snes/ gb/ gbc/ gba/ n64/ nds/ gc/ wii/
|
||||||
|
│ ├── mastersystem/ gamegear/ megadrive/ sega32x/ segacd/
|
||||||
|
│ ├── saturn/ dreamcast/
|
||||||
|
│ ├── psx/ ps2/ psp/
|
||||||
|
│ └── arcade/ dos/
|
||||||
|
├── bios/ # RetroArch system dir: BIOS and firmware
|
||||||
|
├── saves/ # in-game saves
|
||||||
|
├── states/ # save states
|
||||||
|
├── playlists/
|
||||||
|
├── screenshots/
|
||||||
|
├── thumbnails/
|
||||||
|
├── steam/ # second Steam library folder
|
||||||
|
└── clonehero/
|
||||||
|
├── songs/
|
||||||
|
└── backgrounds/
|
||||||
|
```
|
||||||
|
|
||||||
|
Directory names under `roms/` follow the libretro/ES-DE convention, so a scraper
|
||||||
|
or a second frontend recognises them without anything being renamed.
|
||||||
|
|
||||||
|
Everything is `lyrathorpe:users` mode **2775**. The setgid bit matters: the
|
||||||
|
owning group is carried onto anything created inside, so a second account — or
|
||||||
|
an `rsync` from another machine — does not leave behind files the TV user cannot
|
||||||
|
write. The rules create directories if missing and otherwise leave them alone;
|
||||||
|
nothing here removes or rewrites content.
|
||||||
|
|
||||||
|
RetroArch is pointed at these paths declaratively. The other two have to be told
|
||||||
|
once, in their own UIs:
|
||||||
|
|
||||||
|
- **Steam** — Settings → Storage → the `+` control → add `/srv/games/steam` as a
|
||||||
|
library folder. Games installed there survive a reinstall of the OS.
|
||||||
|
- **Clone Hero** — set the song library path to `/srv/games/clonehero/songs` from
|
||||||
|
its settings screen. Clone Hero keeps its own config in `~/.clonehero`.
|
||||||
|
|
||||||
|
## Steam and Proton
|
||||||
|
|
||||||
|
`programs.steam.enable` already arranges most of what Proton needs, and it is
|
||||||
|
worth recording so it is not re-litigated:
|
||||||
|
|
||||||
|
- `hardware.graphics` with `enable32Bit` — the 32-bit GL/Vulkan userspace
|
||||||
|
Proton's 32-bit prefixes link against.
|
||||||
|
- Steam's udev rules (`hardware.steam-hardware.enable`) — controller and
|
||||||
|
hidraw access, which is also what lets `dualsensectl` talk to a DualSense.
|
||||||
|
- 32-bit PipeWire ALSA (`services.pipewire.alsa.support32Bit`), derived from
|
||||||
|
`alsa.enable`, which `gaming.nix` turns on for the older native titles that
|
||||||
|
talk to ALSA directly rather than through the Pulse shim.
|
||||||
|
- Wine's fonts — Liberation, DejaVu, FreeFont and the Noto set arrive with
|
||||||
|
`fonts.enableDefaultPackages` plus `modules/common-nixos.nix`. Liberation is
|
||||||
|
metric-compatible with the Microsoft core fonts, so text lays out correctly
|
||||||
|
without shipping unfree `corefonts`.
|
||||||
|
- `vm.max_map_count` is **1048576** in the nixpkgs default sysctls, above what
|
||||||
|
DX12 and Unreal titles need. No override required — this is the one people
|
||||||
|
usually copy from Arch wiki posts and it is already handled.
|
||||||
|
|
||||||
|
What is **not** covered by default, and is set explicitly in `gaming.nix`:
|
||||||
|
|
||||||
|
- `systemd.settings.Manager.DefaultLimitNOFILE = "1024:1048576"`. esync opens
|
||||||
|
one eventfd per Wine synchronisation object and runs out against systemd's
|
||||||
|
default 524288 hard limit in the heaviest titles. Only the hard limit is
|
||||||
|
raised; the soft limit stays at 1024, because lifting that breaks
|
||||||
|
`select()`-based programs elsewhere on the system.
|
||||||
|
- `extraCompatPackages = [ pkgs.proton-ge-bin ]`. The module puts its
|
||||||
|
`steamcompattool` output on `STEAM_EXTRA_COMPAT_TOOLS_PATHS`, which is what
|
||||||
|
makes **GE-Proton** appear in the client's compatibility list.
|
||||||
|
- `protontricks.enable` — winetricks against a Proton prefix, the standard
|
||||||
|
repair when a title needs a runtime (dotnet, vcrun, Media Foundation) Proton
|
||||||
|
does not ship.
|
||||||
|
- `programs.gamemode.enable` — applies the performance CPU governor around games
|
||||||
|
that request it.
|
||||||
|
|
||||||
|
One thing is **not declarative**: Steam Play must be switched on in the client,
|
||||||
|
once, per account — **Settings → Compatibility → Enable Steam Play for all other
|
||||||
|
titles**. Nix cannot set this; it lives in Steam's own config.
|
||||||
|
|
||||||
|
Verify the Proton side after installing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
vulkaninfo --summary # 64-bit ICD
|
||||||
|
nvidia-smi # driver up
|
||||||
|
ulimit -Hn # expect 1048576
|
||||||
|
# in a game's launch options, to confirm esync/fsync are active:
|
||||||
|
# PROTON_LOG=1 %command% → ~/steam-<appid>.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Controllers
|
||||||
|
|
||||||
|
- **Xbox One / Series over Bluetooth** — `hardware.xpadneo.enable`. The
|
||||||
|
out-of-tree driver; the in-kernel `xpad` handles these badly over Bluetooth
|
||||||
|
(wrong button mapping, no rumble). The module enables bluez itself.
|
||||||
|
- **Xbox 360, wired** — in-kernel `xpad`, autoloaded by udev on plug-in.
|
||||||
|
Nothing to configure. The kernel is built with `CONFIG_JOYSTICK_XPAD=m`,
|
||||||
|
`CONFIG_JOYSTICK_XPAD_FF=y` (rumble) and `CONFIG_JOYSTICK_XPAD_LEDS=y`. The
|
||||||
|
360 wireless PC receiver uses the same driver and works the same way.
|
||||||
|
- **DualSense / DualShock 4** — in-kernel `hid-playstation`, over both USB and
|
||||||
|
Bluetooth. No driver config. `dualsensectl` is installed for LED, battery and
|
||||||
|
microphone control; it works because Steam's udev rules grant hidraw access.
|
||||||
|
- **Clone Hero guitars** — plain USB HID gamepads, handled in-kernel. Nothing to
|
||||||
|
configure.
|
||||||
|
|
||||||
|
`hardware.bluetooth.powerOnBoot` is set so the adapter is up before the Steam
|
||||||
|
session starts and a pad can reconnect unattended.
|
||||||
|
`settings.General.Experimental = true` is what enables battery level reporting
|
||||||
|
for Bluetooth gamepads — it is still behind bluez's experimental flag.
|
||||||
|
|
||||||
|
Pair a new controller from Big Picture (**Settings → Controller**), or from a
|
||||||
|
Sway session with `bluetoothctl`. If a pad connects but no input arrives, check
|
||||||
|
`journalctl -b -u bluetooth` and confirm `hid_xpadneo` is loaded
|
||||||
|
(`lsmod | grep xpadneo`).
|
||||||
|
|
||||||
|
The Xbox One / Series USB **wireless dongle** is deliberately not configured. It
|
||||||
|
needs `hardware.xone.enable`, which **blacklists `xpad`** — that would break the
|
||||||
|
wired 360 pads — as well as `mt76x2u`, and pulls in proprietary dongle firmware.
|
||||||
|
Not worth the side effects unless that dongle is actually in use, and if it ever
|
||||||
|
is, the 360 pads have to be re-tested.
|
||||||
|
|
||||||
|
## Untested claims
|
||||||
|
|
||||||
|
This host has not been built or booted yet. Two things are worth watching on
|
||||||
|
first boot:
|
||||||
|
|
||||||
|
- **gamescope on the proprietary NVIDIA driver.** `gaming.nix` sets
|
||||||
|
`GBM_BACKEND=nvidia-drm` and `__GLX_VENDOR_LIBRARY_NAME=nvidia` for the
|
||||||
|
session, which is the standard fix, but the combination has a history of
|
||||||
|
needing tweaks. If the session dies at startup, switch the greeter back to
|
||||||
|
interactive by commenting out `services.greetd.settings.initial_session`, log
|
||||||
|
into Sway, and read `journalctl --user -b`.
|
||||||
|
- **Television resolution and refresh.** gamescope takes the output's native
|
||||||
|
mode by default. Add `gamescopeSession.args = [ "-W" "3840" "-H" "2160" "-r"
|
||||||
|
"60" ]` if a specific mode is wanted.
|
||||||
|
|
||||||
|
There is no HDMI-CEC configuration here, so the TV remote will not drive the
|
||||||
|
box; that needs a Pulse-Eight adapter or a working CEC bridge on the board.
|
||||||
|
Nothing boots to a splash screen either — Plymouth was left out deliberately, as
|
||||||
|
early KMS with the proprietary driver makes it unreliable.
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
|
||||||
|
Wired NetworkManager from `../../modules/desktop.nix`; `modules/ssh.nix` adds
|
||||||
|
key-only sshd, which is the practical way to administer a machine with no
|
||||||
|
keyboard attached. The firewall is default-deny (`modules/workstation.nix`);
|
||||||
|
Steam Remote Play and local network game transfers open their own ports through
|
||||||
|
`programs.steam`.
|
||||||
@@ -11,7 +11,7 @@ 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
|
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
|
top of the shared home profile. The system config here is thin — it is mostly
|
||||||
WSL plumbing; the user-facing tooling lives in
|
WSL plumbing; the user-facing tooling lives in
|
||||||
[`../../users/emmathorpe/work.nix`](../../users/emmathorpe/work.nix).
|
[`../../users/emmathorpe/work.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/emmathorpe/work.nix).
|
||||||
|
|
||||||
## WSL specifics
|
## WSL specifics
|
||||||
|
|
||||||
@@ -34,10 +34,44 @@ WSL plumbing; the user-facing tooling lives in
|
|||||||
The host-table entry sets `users.emmathorpe.linger = true` so the user's
|
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
|
`systemd --user` instance stays alive without an open login session. That keeps
|
||||||
the daily headless **Renovate PR review** timer firing — defined in
|
the daily headless **Renovate PR review** timer firing — defined in
|
||||||
[`../../users/emmathorpe/renovate-review.nix`](../../users/emmathorpe/renovate-review.nix)
|
[`../../users/emmathorpe/renovate-review.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/emmathorpe/renovate-review.nix)
|
||||||
(imported only from `work.nix`, so it exists on this machine alone). See that
|
(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.
|
file's header for the auth (Vertex AI ADC), triage policy, and caveats.
|
||||||
|
|
||||||
|
## Secret Service (keychain)
|
||||||
|
|
||||||
|
`work.nix` sets `services.headlessSecretService.enable = true`, which runs
|
||||||
|
`gnome-keyring` as a `systemd --user` service owning `org.freedesktop.secrets`
|
||||||
|
on the session bus, with the login keyring unlocked at start.
|
||||||
|
|
||||||
|
This exists for **gcx**, the Grafana Cloud CLI. gcx stores its OAuth access and
|
||||||
|
refresh tokens in the keychain unconditionally (its config keeps only opaque
|
||||||
|
`keychain:gcx:v2:...` handles) and has no plaintext fallback, so without a
|
||||||
|
Secret Service `gcx login` authenticates and then fails to persist with "The
|
||||||
|
name is not activatable".
|
||||||
|
|
||||||
|
Home-manager's own `services.gnome-keyring` does not work here: it is
|
||||||
|
`WantedBy=graphical-session-pre.target`, which never activates on this headless
|
||||||
|
box, and it cannot unlock the keyring. See
|
||||||
|
[`../../home/secret-service.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/secret-service.nix) for the full
|
||||||
|
rationale and the security trade-off of an auto-unlocked keyring.
|
||||||
|
|
||||||
|
Only the `secrets` component is started. The `ssh` component is deliberately off
|
||||||
|
— it would claim `SSH_AUTH_SOCK` and displace `services.ssh-agent`, breaking SSH
|
||||||
|
auth and signed commits.
|
||||||
|
|
||||||
|
Checking it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl --user status headless-secret-service
|
||||||
|
busctl --user list | grep secrets # expect org.freedesktop.secrets
|
||||||
|
secret-tool search --all service gcx # inspect what gcx stored
|
||||||
|
gcx config check # end-to-end
|
||||||
|
```
|
||||||
|
|
||||||
|
If the keyring password is ever lost or changed, the login keyring cannot be
|
||||||
|
unlocked: delete `~/.local/share/keyrings` and re-run `gcx login`.
|
||||||
|
|
||||||
## stateVersion
|
## stateVersion
|
||||||
|
|
||||||
`system.stateVersion = "24.11"` — the release this box was first installed on.
|
`system.stateVersion = "24.11"` — the release this box was first installed on.
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# Mac Pro 3,1 (Early 2008) — install notes
|
||||||
|
|
||||||
|
Flake host: `lyrathorpe-macpro31`. Desktop (`portable = false`, imports
|
||||||
|
`../../modules/desktop.nix`). Files: `configuration.nix`, `nvidia.nix`,
|
||||||
|
`hardware-configuration.nix`.
|
||||||
|
|
||||||
|
## Hardware configuration
|
||||||
|
|
||||||
|
`hardware-configuration.nix` here is the real config generated by
|
||||||
|
`nixos-generate-config` on the machine. Root is an **LVM** logical volume
|
||||||
|
(`/dev/mapper/MacPro-Root`, ext4); the ESP (vfat) and swap are referenced by
|
||||||
|
UUID. The initrd carries `dm-snapshot` for the LVM root. Regenerate and commit
|
||||||
|
if the disk layout changes.
|
||||||
|
|
||||||
|
## Bootloader
|
||||||
|
|
||||||
|
The Mac Pro 3,1 has **64-bit EFI**, so it uses **systemd-boot** (no GRUB/CSM
|
||||||
|
shim). `canTouchEfiVariables = false` because Apple's firmware does not reliably
|
||||||
|
accept `efibootmgr` NVRAM writes.
|
||||||
|
|
||||||
|
Apple-EFI quirk: if the firmware boot picker does not show NixOS after install,
|
||||||
|
either
|
||||||
|
|
||||||
|
- uncomment `boot.loader.efi.efiInstallAsRemovable = true;` in
|
||||||
|
`configuration.nix` (installs the fallback `\EFI\BOOT\BOOTX64.EFI`), and/or
|
||||||
|
- "bless" the ESP from macOS.
|
||||||
|
|
||||||
|
Partition the disk GPT with an ESP (vfat).
|
||||||
|
|
||||||
|
## Graphics — NVIDIA Quadro P400
|
||||||
|
|
||||||
|
The stock card (**ATI Radeon HD 2600 XT** or **NVIDIA GeForce 8800 GT**,
|
||||||
|
depending on the unit) has been replaced with an **NVIDIA Quadro P400** (Pascal,
|
||||||
|
GP108). Everything driver-related lives in [`nvidia.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/hosts/MacPro31/nvidia.nix):
|
||||||
|
|
||||||
|
- **Driver branch 580** (`nvidiaPackages.legacy_580`), _not_ the nixpkgs default
|
||||||
|
(`production`, currently 595.x). 580 is the last branch that supports
|
||||||
|
Maxwell/Pascal/Volta and is maintained as an LTS branch until Aug 2028; a
|
||||||
|
newer branch does not drive this card at all.
|
||||||
|
- `modesetting.enable = true` — mandatory for Wayland (sets
|
||||||
|
`nvidia-drm.modeset=1`); without it wlroots gets no GBM device and both Sway
|
||||||
|
and the greeter fail to start.
|
||||||
|
- `open = false` — the open kernel modules require Turing or later.
|
||||||
|
- Sway runs with `--unsupported-gpu` (`programs.sway.extraOptions`); wlroots
|
||||||
|
refuses the proprietary driver otherwise. `cage`/ReGreet needs no such flag.
|
||||||
|
- nouveau and `nvidiafb` are blacklisted automatically by the NVIDIA module.
|
||||||
|
|
||||||
|
The driver is unfree, so it is **not in the binary cache**: the kernel module is
|
||||||
|
compiled on the machine, which on these 2008 Xeons is slow — budget for a long
|
||||||
|
first rebuild and again after every kernel bump. The package names are
|
||||||
|
allowlisted in `unfreePackages` in [`flake.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/flake.nix).
|
||||||
|
|
||||||
|
Note the Mac Pro shows no EFI boot screen with a stock PC card (no Apple EFI
|
||||||
|
ROM): the machine boots blind until KMS brings the display up. That is expected,
|
||||||
|
not a fault.
|
||||||
|
|
||||||
|
Verify after a rebuild:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nvidia-smi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker with CUDA
|
||||||
|
|
||||||
|
`nvidia.nix` also enables Docker and gives containers GPU access via **CDI**
|
||||||
|
(`hardware.nvidia-container-toolkit.enable`), which generates device specs from
|
||||||
|
the host driver at boot (regenerated by a udev rule when the `nvidia` device
|
||||||
|
appears) and turns on the daemon's CDI feature:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --rm --device=nvidia.com/gpu=all nvidia/cuda:12.9.1-base-ubuntu24.04 nvidia-smi
|
||||||
|
```
|
||||||
|
|
||||||
|
- Use the `--device=nvidia.com/gpu=all` form. `--gpus all` is the legacy
|
||||||
|
runtime-wrapper path (`virtualisation.docker.enableNvidia`), which is
|
||||||
|
deprecated upstream and deliberately not enabled here.
|
||||||
|
- **CUDA version matters.** The P400 is compute capability 6.1 (`sm_61`); CUDA
|
||||||
|
13 dropped Maxwell/Pascal/Volta, so container images must ship a **CUDA 12.x
|
||||||
|
or older** runtime. The 580 driver itself is happy with either.
|
||||||
|
- 2 GB of VRAM, 256 CUDA cores — fine for encode/decode and small models, not
|
||||||
|
for training anything serious.
|
||||||
|
- Docker socket is local-only (no TCP listener, unlike the Pi). Users need the
|
||||||
|
`docker` group; the registry already grants it.
|
||||||
|
|
||||||
|
### "Driver Not Loaded" from the CDI generator
|
||||||
|
|
||||||
|
`nvidia-container-toolkit-cdi-generator.service` fails with
|
||||||
|
`failed to initialize NVML: Driver Not Loaded` whenever the `nvidia` kernel
|
||||||
|
module is not loaded in the **running** kernel. After a kernel bump that is
|
||||||
|
unavoidable — the rebuilt module cannot load until reboot — so the unit is
|
||||||
|
guarded with `ConditionPathExists=/proc/driver/nvidia/version` and skips
|
||||||
|
instead of failing. Without that guard it also takes `docker.service`
|
||||||
|
(`requiredBy`) with it and makes `nixos-rebuild switch` exit non-zero.
|
||||||
|
|
||||||
|
**Reboot after a rebuild that touches the driver or the kernel.** The toolkit's
|
||||||
|
udev rule restarts the generator when the GPU device appears, so the CDI specs
|
||||||
|
are written on the next boot. To check the state:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lsmod | grep nvidia # nvidia, nvidia_modeset, nvidia_drm, nvidia_uvm
|
||||||
|
cat /proc/driver/nvidia/version
|
||||||
|
nvidia-smi
|
||||||
|
systemctl status nvidia-container-toolkit-cdi-generator.service
|
||||||
|
ls /var/run/cdi # the generated spec
|
||||||
|
```
|
||||||
|
|
||||||
|
If the module is genuinely absent after a reboot, check `dmesg | grep -i
|
||||||
|
nvidia` (build/version mismatch, or nouveau still bound — the module blacklists
|
||||||
|
it, so that should not happen).
|
||||||
|
|
||||||
|
## Claude Code — not installed here
|
||||||
|
|
||||||
|
The dual Harpertown Xeons are **x86-64-v1** (SSE4.1, but no SSE4.2/POPCNT) and
|
||||||
|
the Node runtime Claude Code ships on requires x86-64-v2. `configuration.nix`
|
||||||
|
declares `features.cpu.microarchLevel = 1`, which switches the tool off through
|
||||||
|
the fleet-wide gate in [`../../modules/features.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/modules/features.nix)
|
||||||
|
— see the root README. Forcing `features.claudeCode.enable` on here is an
|
||||||
|
evaluation error, not a broken install.
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
|
||||||
|
Wired Ethernet via NetworkManager (from `desktop.nix`) — the Mac Pro has two
|
||||||
|
gigabit ports.
|
||||||
|
|
||||||
|
## Login
|
||||||
|
|
||||||
|
Graphical login via a Wayland greeter — `greetd` running ReGreet inside the
|
||||||
|
`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
|
||||||
|
authenticate. Requires working KMS (NVIDIA modesetting — see Graphics).
|
||||||
|
|
||||||
|
## Apply
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo nixos-rebuild switch --flake .#lyrathorpe-macpro31
|
||||||
|
```
|
||||||
@@ -4,13 +4,13 @@ Every keyboard shortcut configured across this desktop, and where it is defined.
|
|||||||
Everything here is managed declaratively through Nix — edit the listed file and
|
Everything here is managed declaratively through Nix — edit the listed file and
|
||||||
rebuild, never the generated dotfiles.
|
rebuild, never the generated dotfiles.
|
||||||
|
|
||||||
| Area | Defined in |
|
| Area | Defined in |
|
||||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------- |
|
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Sway (compositor) | [`sway.nix`](./sway.nix) `config.keybindings` + `config.modes`, plus the home-manager Sway module's built-in defaults |
|
| Sway (compositor) | [`sway.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/sway.nix) `config.keybindings` + `config.modes`, plus the home-manager Sway module's built-in defaults |
|
||||||
| tmux | [`shell.nix`](./shell.nix) `programs.tmux` |
|
| tmux | [`shell.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/shell.nix) `programs.tmux` |
|
||||||
| zsh line editor | [`shell.nix`](./shell.nix) `programs.zsh.historySubstringSearch` |
|
| zsh line editor | [`shell.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/shell.nix) `programs.zsh.historySubstringSearch` |
|
||||||
| Neovim | [`editor.nix`](./editor.nix) `programs.nixvim` |
|
| Neovim | [`editor.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/editor.nix) `programs.nixvim` |
|
||||||
| foot (terminal) | foot package defaults — only colours are themed (in `sway.nix`) |
|
| foot (terminal) | foot package defaults — only colours are themed (in `sway.nix`) |
|
||||||
|
|
||||||
**Conventions**
|
**Conventions**
|
||||||
|
|
||||||
+360
@@ -0,0 +1,360 @@
|
|||||||
|
# Interactive shell environment
|
||||||
|
|
||||||
|
Everything the shell, terminal multiplexer, git and ssh do beyond their defaults,
|
||||||
|
and where each is defined. All of it is managed declaratively through
|
||||||
|
home-manager — edit the listed file and rebuild, never the generated dotfiles.
|
||||||
|
|
||||||
|
Keyboard shortcuts have their own reference: [`keybindings.md`](./keybindings.md).
|
||||||
|
|
||||||
|
| Area | Defined in |
|
||||||
|
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| zsh, CLI tools, tmux, ssh, auto-tmux | [`shell.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/shell.nix) |
|
||||||
|
| git (+ delta, commitizen) | [`git.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/git.nix) |
|
||||||
|
| Neovim (nixvim) + LSP | [`editor.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/editor.nix) |
|
||||||
|
| Claude Code (CLAUDE.md, style, memory) | [`claude.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/claude.nix) |
|
||||||
|
| GUI apps, GTK/Firefox theming, cursor | [`desktop.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/desktop.nix) (graphical hosts only) |
|
||||||
|
|
||||||
|
Shared by every host via [`default.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/default.nix); the work box also layers
|
||||||
|
[`work.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/emmathorpe/work.nix) on top (its own ssh config, extra
|
||||||
|
packages, kubecolor, and the C#/Helm language servers). The committer identity (name, email,
|
||||||
|
signing key) comes from the user registry
|
||||||
|
([`../users/registry.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/users/registry.nix)), not this module.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## zsh
|
||||||
|
|
||||||
|
| Feature | Notes |
|
||||||
|
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| oh-my-zsh | plugins `git`, `man`, `sudo` (Esc-Esc to prepend sudo), `colored-man-pages`, `extract`; theme `robbyrussell` |
|
||||||
|
| Autosuggestion | fish-style history suggestions as you type (→ to accept) |
|
||||||
|
| Syntax highlighting | commands coloured by validity as you type |
|
||||||
|
| Completion | menu completion; the dump is rebuilt on every activation (see Maintenance) |
|
||||||
|
| History | 100k in-memory/on-disk, deduped, space-prefixed commands ignored, timestamped, **shared live across sessions**; file stays at `~/.zsh_history` |
|
||||||
|
| Dotfiles location | `dotDir` is `~/.config/zsh` (XDG) — `.zshrc`/`.zshenv`/`.zcompdump` live there; `~/.zshenv` only bootstraps `$ZDOTDIR` |
|
||||||
|
| History substring search | type a fragment, then ↑/↓ cycles matching past commands — works in foot, iTerm2 and the Linux TTY (both CSI and SS3 arrow encodings bound) |
|
||||||
|
| Prompt | hostname is prefixed when over SSH |
|
||||||
|
|
||||||
|
**Aliases:** `ls`/`ll`/`la`/`lt` → `eza` (icons + git), `cls` → `clear`,
|
||||||
|
`cat`/`du`/`df`/`ps` → their modern equivalents (see "Replacing the classics").
|
||||||
|
git aliases live in git.nix (below).
|
||||||
|
|
||||||
|
## CLI tools
|
||||||
|
|
||||||
|
| Tool | What it gives you |
|
||||||
|
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `fzf` | `Ctrl-R` fuzzy history, `Ctrl-T` file picker, `Alt-C` fuzzy cd (Catppuccin-themed) |
|
||||||
|
| `zoxide` | `z <fragment>` jumps to frecent directories |
|
||||||
|
| `direnv` + `nix-direnv` | per-project environments auto-loaded on `cd` (cached Nix dev shells) |
|
||||||
|
| `eza` | modern `ls` (drives the ls aliases) |
|
||||||
|
| `bat` | syntax-highlighting pager (Catppuccin Mocha theme); behaves like `cat` when piped; also the `MANPAGER` |
|
||||||
|
| `ripgrep` / `fd` | fast search (`rg`) and find (`fd`); also back `fzf` |
|
||||||
|
| `jq` | JSON processor |
|
||||||
|
| `gh` / `tea` | GitHub and Gitea (`code.emmathe.dev`) CLIs; `gh` uses SSH |
|
||||||
|
| `nix-index` | `command-not-found`: an unknown command tells you which Nix package provides it (prebuilt DB, no manual indexing) |
|
||||||
|
| `comma` (`,`) | run an uninstalled program once: `, cowsay hi` |
|
||||||
|
| `nh` | nicer `nixos-rebuild`/`home-manager` with diffs; `$NH_FLAKE` set to the repo. No scheduled GC (it could reap paths a running generation still references) — collect garbage manually with `nh clean all` / `nix-collect-garbage -d` |
|
||||||
|
| `btop` | resource monitor, themed Catppuccin Mocha (vendored theme) |
|
||||||
|
| `lazygit` | git TUI for staging/rebasing, themed to match (`git.nix`) |
|
||||||
|
| `hyperfine` / `sd` | command-line benchmarking; saner find-and-replace than sed |
|
||||||
|
| `tldr` (tealdeer) | worked examples for a command, alongside `man`; the page cache is refreshed by a `tldr-update` user timer |
|
||||||
|
| `jnv` / `fq` | interactive jq-filter builder for JSON; jq syntax over binary formats (ELF, PNG, gzip, mp4…) |
|
||||||
|
| `hexyl` | hex viewer, coloured by byte class |
|
||||||
|
| `ouch` | one command for every archive format (`ouch d`/`c`/`l`) |
|
||||||
|
| `dust` `dysk` `procs` | `du` / `df` / `ps` replacements — aliased over the originals, see below |
|
||||||
|
| `trash-cli` `doggo` `xh` | `rm` (to the XDG trash) / `dig` / `curl` replacements — **not** aliased, see below |
|
||||||
|
|
||||||
|
**Theming:** `fzf`, `bat`, `btop`, `lazygit` and `git`'s `delta` pager are all
|
||||||
|
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`
|
||||||
|
(the editor owns `$EDITOR`/`$VISUAL`); `xdg.mimeApps` maps web→Firefox,
|
||||||
|
directories→nemo (`desktop.nix`).
|
||||||
|
|
||||||
|
## Replacing the classics
|
||||||
|
|
||||||
|
Muscle memory is the expensive part of this, not the packages. Four commands are
|
||||||
|
**shadowed** — the old name now runs a new tool. Everything else keeps a new
|
||||||
|
name, so the original is never displaced.
|
||||||
|
|
||||||
|
### Shadowed by an alias
|
||||||
|
|
||||||
|
| You type | You now run | The original is still `command <name>` / `\<name>` |
|
||||||
|
| -------- | -------------------- | -------------------------------------------------- |
|
||||||
|
| `cat` | `bat --paging=never` | `command cat` |
|
||||||
|
| `du` | `dust` | `command du` |
|
||||||
|
| `df` | `dysk` | `command df` |
|
||||||
|
| `ps` | `procs` | `command ps` |
|
||||||
|
|
||||||
|
Only read-only commands are shadowed, so the worst case of a wrong flag is a
|
||||||
|
retype rather than lost data. `rm`, `grep`, `curl` and `find` are deliberately
|
||||||
|
left alone — see "Left alone on purpose" below.
|
||||||
|
|
||||||
|
**Where the aliases apply.** They are written into `~/.config/zsh/.zshrc`, so
|
||||||
|
they exist only in an **interactive zsh**:
|
||||||
|
|
||||||
|
- shell scripts, `Makefile` recipes and anything another program `exec`s get the
|
||||||
|
real coreutils binary — nothing that parses output can break;
|
||||||
|
- `sudo du -sh /var` runs the real `du`: zsh does not expand an alias after
|
||||||
|
`sudo`;
|
||||||
|
- `KUBECONFIG=… kubectl …` **does** expand — zsh expands aliases after a
|
||||||
|
variable-assignment prefix. That is what makes the kubecolor alias on the work
|
||||||
|
box (below) useful rather than a special case you have to remember.
|
||||||
|
|
||||||
|
### Flag gotchas
|
||||||
|
|
||||||
|
These replacements are not drop-in. The two marked **silent** are the dangerous
|
||||||
|
ones — they succeed and answer a different question than the one you asked.
|
||||||
|
Everything else fails loudly.
|
||||||
|
|
||||||
|
| Old habit | What happens now | Do this instead |
|
||||||
|
| ------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||||
|
| `du -sh dir` | dust prints its usage and exits non-zero — `-h` is not a dust flag | `dust dir` (units are human by default; the total is the last row) |
|
||||||
|
| `du -s dir` | **silent**: dust's `-s` is `--apparent-size`, not `--summarize` | `dust -d 0 dir` for a single total line |
|
||||||
|
| `du --max-depth=2` | not recognised | `dust -d 2` |
|
||||||
|
| `df -h` | dysk rejects `-h` | `dysk` (SI units by default; `-u binary` for 1024-based) |
|
||||||
|
| `df -i` | not recognised | `dysk -c +inodes` |
|
||||||
|
| `df -a` | works, same meaning (all mount points) | — |
|
||||||
|
| `df /some/path` | works, same meaning (the device holding that path) | — |
|
||||||
|
| `ps aux` | **silent**: `aux` is read as a search keyword, so you get only processes whose command line contains the string "aux" | `procs` lists everything; `procs <pattern>` filters |
|
||||||
|
| `ps -ef` | `error: unexpected argument '-e'` | `procs` |
|
||||||
|
| `ps -p 1234` | not recognised | `procs 1234` |
|
||||||
|
| `procs -a` | **silent**: `-a` is `--and` (combine search keywords), not "all" | drop it — `procs` already shows everything |
|
||||||
|
| `cat -v` / `cat -e` | `error: unexpected argument` | `cat -A` does work (bat implements show-all); else `command cat -v` |
|
||||||
|
| `cat -n` | works, but bat's number column, not coreutils' layout | fine to read; `command cat -n` when the exact layout matters |
|
||||||
|
| `cat <binary>` | prints `<BINARY>` to a terminal instead of dumping the bytes | `hexyl <file>`, or `command cat` to dump |
|
||||||
|
|
||||||
|
Useful new capabilities in the same tools: `procs --tree`, `procs --watch`,
|
||||||
|
`dust -r` (largest at the top), `dysk -s size`, `dysk -f 'type=ext4'`.
|
||||||
|
|
||||||
|
**Piping is safe for `cat`.** bat drops all decoration and colour when stdout is
|
||||||
|
not a terminal, so `cat f | sha256sum` is byte-for-byte what coreutils `cat`
|
||||||
|
would have given. The others are TUI-shaped tables with no stable format — if
|
||||||
|
something needs to parse them, use `dysk --json`/`--csv`, `procs --json`, or the
|
||||||
|
original binary.
|
||||||
|
|
||||||
|
### Renamed, not shadowed
|
||||||
|
|
||||||
|
| Instead of | Use | Notes |
|
||||||
|
| --------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `rm` | `trash` | Moves to the XDG trash. `trash-list`, `trash-restore` (interactive picker), `trash-empty [days]`. It never deletes in place: if it cannot create a trash directory on that filesystem it errors out. |
|
||||||
|
| `dig` / `nslookup` | `doggo` | `doggo example.com MX @1.1.1.1`; `--json` for scripting. Not aliased — `dig` (from the `bind` closure that other modules pull in) stays where scripts expect it. |
|
||||||
|
| `curl` (interactive poking) | `xh` | HTTPie syntax: `xh POST api.example/x name=lyra`. `xhs` is `xh --https`. **curl stays installed and unaliased** — it is what scripts and CI use. |
|
||||||
|
| `tar` / `unzip` / `7z` | `ouch` | `ouch d file.<anything>`, `ouch c out.tar.zst src/`, `ouch l archive`. Format is inferred from the extension. The oh-my-zsh `extract` function still works too. |
|
||||||
|
| `jq` (exploring a payload) | `jnv` | Interactive filter builder over a JSON file; it prints the jq expression you built. `jq` remains the scripting tool. |
|
||||||
|
| `hexdump -C` / `xxd` | `hexyl` | `hexyl -n 256 -s 0x40 file` for a window into a large file. |
|
||||||
|
| `strings` on a known format | `fq` | jq syntax over binary formats: `fq -d elf '.sections[].name' ./bin`. |
|
||||||
|
| skimming a man page | `tldr` | Worked examples. `man` is untouched (and still rendered through bat). |
|
||||||
|
|
||||||
|
### Left alone on purpose
|
||||||
|
|
||||||
|
- **`grep`** is not aliased to `rg`. ripgrep is recursive by default, skips
|
||||||
|
gitignored and hidden files, and uses a different regex dialect (no
|
||||||
|
backreferences, no POSIX classes in the same form). A `grep` habit silently
|
||||||
|
producing fewer matches is a worse failure than typing three characters. Type
|
||||||
|
`rg`.
|
||||||
|
- **`rm`** is not aliased to `trash-put`. Retraining `rm` to mean "recoverable"
|
||||||
|
is a habit that follows you onto every machine where it is not — remote hosts,
|
||||||
|
root shells, containers, CI. Type `trash`.
|
||||||
|
- **`find`** is not aliased to `fd`; the `-exec`/`-print0` vocabulary has no
|
||||||
|
equivalent and scripts lean on it. Type `fd`.
|
||||||
|
- **`sed`** is not aliased to `sd`; `sd` takes real regex and literal
|
||||||
|
replacements, not sed's expression language. Type `sd`.
|
||||||
|
- **coreutils itself** is not swapped for `uutils-coreutils`. It is packaged and
|
||||||
|
tempting, but every Nix builder and shell script on these hosts is written
|
||||||
|
against GNU behaviour, including its forty-year-old edge cases.
|
||||||
|
|
||||||
|
### Work box only: kubectl → kubecolor
|
||||||
|
|
||||||
|
On EDaaS (`work.nix`) `kubectl` is aliased to **kubecolor**, which runs the real
|
||||||
|
kubectl underneath and colourises what comes back. Nothing to relearn: every
|
||||||
|
flag, subcommand and plugin passes straight through, unrecognised output is
|
||||||
|
printed verbatim, and colour is dropped automatically when stdout is not a
|
||||||
|
terminal — so `kubectl get -o json … | jq` is unchanged. The alias also applies
|
||||||
|
to `KUBECONFIG=prodconfig kubectl …`, per the alias-expansion note above.
|
||||||
|
Completions are kubectl's own (`compdef kubecolor=kubectl`). Escape hatch as
|
||||||
|
ever: `command kubectl`.
|
||||||
|
|
||||||
|
### sudo → sudo-rs
|
||||||
|
|
||||||
|
Every NixOS host now uses **sudo-rs**, the memory-safe reimplementation, in
|
||||||
|
place of `sudo` (`modules/common-nixos.nix`; the macOS host keeps Apple's sudo
|
||||||
|
with Touch ID). Day to day there is nothing to learn — `sudo`, `sudo -i`,
|
||||||
|
`sudo -u`, `sudo -l`, `sudoedit` and `visudo` all behave as before against this
|
||||||
|
fleet's stock "wheel, with a password" policy. What it does **not** implement:
|
||||||
|
host aliases, LDAP/SSSD sudoers, `sudoreplay`, and most `Defaults` settings.
|
||||||
|
Needing any of those means reverting to `security.sudo`.
|
||||||
|
|
||||||
|
If a host ever refuses to escalate, get a root shell that does not go through
|
||||||
|
sudo (`wsl -u root -d NixOS` on the work box; the console or a serial/HDMI login
|
||||||
|
elsewhere) and roll back with `nixos-rebuild switch --rollback`, or pick the
|
||||||
|
previous generation from the boot menu.
|
||||||
|
|
||||||
|
## tmux
|
||||||
|
|
||||||
|
**Auto-start:** opening any interactive terminal — foot, iTerm2, the WSL shell, the
|
||||||
|
Linux console — drops you straight into a tmux session named `main` (attach if it
|
||||||
|
exists, else create). Panes run a plain non-login zsh. It deliberately does **not**
|
||||||
|
fire for SSH sessions, VS Code's integrated terminal, already-inside-tmux, or
|
||||||
|
non-interactive shells. Escape hatch: `NO_TMUX=1 <terminal>` opens a bare shell.
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| Mode keys | vi |
|
||||||
|
| Mouse | on |
|
||||||
|
| Scrollback | 500000 lines |
|
||||||
|
| `escape-time` | 10ms (the 500ms default lagged vim's ESC) |
|
||||||
|
| `focus-events` | on (vim autoread) |
|
||||||
|
| `base-index` / `pane-base-index` | 1 |
|
||||||
|
| Splits | `prefix s` vertical, `prefix v` horizontal (stock `%`/`"` unbound) |
|
||||||
|
| Pane nav | `Alt`+arrows (no prefix) |
|
||||||
|
| Terminal | `default-terminal tmux-256color`; truecolor advertised per outer terminal (`foot*`, `xterm-256color`/iTerm2) via `terminal-features … RGB` |
|
||||||
|
| Clipboard | `set-clipboard on`; foot `terminal-features` advertise truecolor/sync/OSC52/title/cursor |
|
||||||
|
|
||||||
|
**Plugins:** `sensible`, `vim-tmux-navigator` (Ctrl-h/j/k/l across vim ↔ tmux),
|
||||||
|
`yank`, `extrakto` (`prefix`+`Tab`: fzf-grab paths/URLs/text from the pane into
|
||||||
|
the prompt), `catppuccin` (Mocha statusline), `resurrect` + `continuum`
|
||||||
|
(sessions auto-save and restore across reboots). The statusline draws Nerd-Font
|
||||||
|
glyphs — see Fonts.
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
**JetBrainsMono Nerd Font**, **Noto Sans** and **Noto Color Emoji** are
|
||||||
|
installed on every host (in `common-nixos.nix`, because tmux/terminals run
|
||||||
|
everywhere; the Mac installs the Nerd Font to `/Library/Fonts` via the Darwin
|
||||||
|
config). `fonts.fontconfig.defaultFonts` maps the generic families so anything
|
||||||
|
asking for `monospace` gets the Nerd Font (with emoji fallback) — this also
|
||||||
|
gives the WSL box emoji/sans coverage it otherwise lacked. foot uses the Nerd
|
||||||
|
Font as its main font automatically. iTerm2's font is a GUI setting — set it to
|
||||||
|
_JetBrainsMono Nerd Font_ (Settings → Profiles → Text → Font) so the tmux
|
||||||
|
statusline glyphs render instead of `?`.
|
||||||
|
|
||||||
|
## Editor (Neovim)
|
||||||
|
|
||||||
|
`nvim` — aliased to `vi`/`vim`, and set as `$EDITOR`/`$VISUAL` — is configured
|
||||||
|
declaratively with **nixvim**, so the same plugins and config are baked in on
|
||||||
|
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 |
|
||||||
|
| Format on save | conform-nvim (nixfmt, stylua, ruff, shfmt, prettier, gofumpt; LSP fallback otherwise) |
|
||||||
|
| Git | fugitive (`:Git …`) + gitsigns gutter signs/blame |
|
||||||
|
| Diagnostics | inline + trouble list (`<leader>xx`) |
|
||||||
|
| Completion | nvim-cmp (LSP/buffer/path) with luasnip snippet expansion |
|
||||||
|
| Indent guides | indent-blankline, on by default (was vim-indent-guides) |
|
||||||
|
| Statusline | lualine (Catppuccin theme) |
|
||||||
|
| 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_ls` (Nix), `lua_ls`, `pyright` (Python), `terraformls` |
|
||||||
|
| Indentation | 2-wide hard tabs (`noexpandtab`, `tabstop`/`shiftwidth` = 2); line numbers on |
|
||||||
|
| Filetypes | `*Jenkinsfile` → groovy |
|
||||||
|
|
||||||
|
Leader is `Space`. LSP keymaps (`gd`, `gr`, `K`, `<leader>rn`, `<leader>ca`) and
|
||||||
|
the file-tree toggle are listed in
|
||||||
|
[`keybindings.md`](./keybindings.md#neovim). Add a universal language server by
|
||||||
|
enabling it under `programs.nixvim.plugins.lsp.servers` in `editor.nix`;
|
||||||
|
host-specific ones go in that host's module — the work box (`work.nix`) adds
|
||||||
|
`omnisharp` (C#) and `helm_ls` (Helm), kept off the personal machines.
|
||||||
|
|
||||||
|
## git
|
||||||
|
|
||||||
|
Pager is **delta**. **commitizen** is installed on every host; `cz` defaults to
|
||||||
|
Conventional Commits. **lazygit** (themed) is the TUI. The commit-graph is kept
|
||||||
|
current (`gc`/`fetch.writeCommitGraph`) so `lg` stays fast.
|
||||||
|
|
||||||
|
| Aliases | |
|
||||||
|
| ------------------------ | ------------------------------------------------------------------------- |
|
||||||
|
| `st` `co` `sw` `br` `ci` | status / checkout / switch / branch / commit |
|
||||||
|
| `last` `unstage` | last commit / unstage |
|
||||||
|
| `amend` `fixup` `undo` | amend-no-edit / `commit --fixup` / soft-reset HEAD~1 (keep staged) |
|
||||||
|
| `lg` | graph log, all branches |
|
||||||
|
| `cz` `cc` | `git cz <sub>` (e.g. `git cz c`) and `git cc` → commitizen prompt |
|
||||||
|
| `dft` | structural (syntax-aware) diff via difftastic; takes `git diff` arguments |
|
||||||
|
|
||||||
|
**`git dft` vs `git diff`.** delta stays the default renderer for everything;
|
||||||
|
`diff.external` is deliberately **not** set, so `git diff`, `git show` and
|
||||||
|
anything parsing their output are unchanged. Reach for `dft` when a refactor
|
||||||
|
moved code around and a line-based diff is noise. One wrinkle: `dft` is a
|
||||||
|
`!`-shell alias, and git runs those from the repository root — pass pathspecs
|
||||||
|
relative to the root, not to your current directory.
|
||||||
|
|
||||||
|
| Behaviour | |
|
||||||
|
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Pulls | rebase, with autostash + autosquash |
|
||||||
|
| Fetch | prune deleted remote branches |
|
||||||
|
| Conflicts | `zdiff3` (shows the common ancestor) |
|
||||||
|
| Diffs | histogram algorithm, colour-moved |
|
||||||
|
| `rerere` | remembers + replays conflict resolutions |
|
||||||
|
| 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). Name, email and signing key all come from the per-user `identity` (the user registry, `../users/registry.nix`). |
|
||||||
|
|
||||||
|
## ssh
|
||||||
|
|
||||||
|
| Feature | Notes |
|
||||||
|
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| ssh-agent | runs on Linux (launchd on macOS); keys added on **first use** so the passphrase is typed once per login session — this also feeds git commit signing |
|
||||||
|
| macOS | `UseKeychain` caches the passphrase in the login keychain (guarded by `IgnoreUnknown`, so a non-Apple `ssh` skips it instead of erroring) |
|
||||||
|
| Gitea remote | `code.emmathe.dev` → `HostName 10.187.1.76` (DNS-override), `Port 30009`, user `git`, dedicated key, `identitiesOnly` |
|
||||||
|
| Defaults | the module's deprecated default block is opted out; equivalents kept under `settings."*"` |
|
||||||
|
|
||||||
|
The **work box keeps its own `~/.ssh/config`** (home-manager's `programs.ssh` is
|
||||||
|
forced off there) but still runs the agent.
|
||||||
|
|
||||||
|
## Claude Code
|
||||||
|
|
||||||
|
Managed declaratively by [`claude.nix`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/claude.nix) on every host whose CPU
|
||||||
|
can run it (the CLI is `pkgs.claude-code`, tracked to unstable via the flake
|
||||||
|
overlay).
|
||||||
|
|
||||||
|
**Capability gate.** The module installs nothing — CLI or files — when
|
||||||
|
`osConfig.features.claudeCode.enable` is off. That flag is derived fleet-wide
|
||||||
|
from the host's declared CPU level (see "CPU capability gating" in the root
|
||||||
|
README): the Node runtime needs SSE4.2/POPCNT, so anything below x86-64-v2 (the
|
||||||
|
Mac Pro 3,1) is excluded. Hosts that do not define the option — the Darwin host
|
||||||
|
and the standalone `homeConfigurations` — keep it enabled.
|
||||||
|
|
||||||
|
| Managed (static, from Nix) | Left mutable (runtime state) |
|
||||||
|
| --------------------------------------------------- | ------------------------------------------------------ |
|
||||||
|
| `~/.claude/CLAUDE.md` (persona + memory workflow) | `settings.json` (permissions, model, theme, `/config`) |
|
||||||
|
| `~/.claude/output-styles/soviet-engineer.md` | `.credentials.json`, history, caches |
|
||||||
|
| `~/.claude/memory/` (read-only symlink to the repo) | |
|
||||||
|
|
||||||
|
`settings.json` is intentionally **not** managed: Claude rewrites it at runtime
|
||||||
|
(interactive permission grants, `/config`), which a read-only store symlink would
|
||||||
|
break.
|
||||||
|
|
||||||
|
**Memory is sourced from this repo.** The files in
|
||||||
|
[`claude/memory/`](https://code.emmathe.dev/lyrathorpe/nixfiles/src/branch/main/home/claude/memory) are the source of truth; they are symlinked
|
||||||
|
read-only into `~/.claude/memory`, so recall works but the runtime "save a
|
||||||
|
memory" path does not. To add/change/remove a memory, edit `claude/memory/`
|
||||||
|
(one file per memory + the `MEMORY.md` index) and rebuild — `CLAUDE.md` tells
|
||||||
|
Claude to route new memories there.
|
||||||
|
|
||||||
|
## Maintenance behaviours
|
||||||
|
|
||||||
|
- **zcompdump reset** — `~/.config/zsh/.zcompdump*` (plus legacy `~/.zcompdump*`
|
||||||
|
and the cache copy) is removed on every activation, so a stale
|
||||||
|
dump (pointing at `/nix/store` paths a rebuild or a manual GC removed) can't
|
||||||
|
break completion with `_git: function definition file not found`.
|
||||||
|
- **GC** — no scheduled timer; collect garbage deliberately (`nh clean all` /
|
||||||
|
`nix-collect-garbage -d`) when no important session is running.
|
||||||
|
|
||||||
|
## Per-host differences
|
||||||
|
|
||||||
|
| | Personal Linux (sway) | macOS | Work WSL (EDaaS) |
|
||||||
|
| --------------------------- | --------------------- | --------------------- | --------------------------- |
|
||||||
|
| Auto-tmux | yes (foot/TTY) | yes (iTerm2) | yes (WSL shell) |
|
||||||
|
| `kubectl` → kubecolor | no (no kubectl) | no | yes (work module) |
|
||||||
|
| `sudo` implementation | sudo-rs | Apple sudo + Touch ID | sudo-rs |
|
||||||
|
| git email | `iam@emmathe.dev` | `iam@emmathe.dev` | `…@citrix.com` (work) |
|
||||||
|
| ssh config managed | yes | yes | no (keeps corporate config) |
|
||||||
|
| ssh-agent | yes | launchd | yes (work module) |
|
||||||
|
| GUI / theming (desktop.nix) | yes | no | no |
|
||||||
Generated
+46
-46
@@ -3,16 +3,16 @@
|
|||||||
"brew-src": {
|
"brew-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783446865,
|
"lastModified": 1786348930,
|
||||||
"narHash": "sha256-nCrPEbQjgnSAOVWTxRXD9Yi6P3oECdtZISYcQCFI9Fs=",
|
"narHash": "sha256-bCQJkbgsAMDp5HQystZLCq11UHiyEuoWbxKulAPYrh8=",
|
||||||
"owner": "Homebrew",
|
"owner": "Homebrew",
|
||||||
"repo": "brew",
|
"repo": "brew",
|
||||||
"rev": "655769712a9a9499563d9685a8488f349354492d",
|
"rev": "3ecc9eff23feebf1bc73846d74e14a122c93b66f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "Homebrew",
|
"owner": "Homebrew",
|
||||||
"ref": "6.0.9",
|
"ref": "6.0.16",
|
||||||
"repo": "brew",
|
"repo": "brew",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "pkgs/firefox-addons",
|
"dir": "pkgs/firefox-addons",
|
||||||
"lastModified": 1783828963,
|
"lastModified": 1786853140,
|
||||||
"narHash": "sha256-eTytzcUJCaDUZ3/9EF0+V3fvlikQMQBwiX1Sx4Gy+No=",
|
"narHash": "sha256-O880FlUav75Q5aNlg9znyg/avf1X/W7o/cAtZFLtpWc=",
|
||||||
"owner": "rycee",
|
"owner": "rycee",
|
||||||
"repo": "nur-expressions",
|
"repo": "nur-expressions",
|
||||||
"rev": "8d61e9afde605cd6c22dab68b83d7a71f0a6c5b2",
|
"rev": "ba9568c9c0df6290dc2f34b032ab4cb575e73788",
|
||||||
"type": "gitlab"
|
"type": "gitlab"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -93,11 +93,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1782949081,
|
"lastModified": 1785627969,
|
||||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -114,11 +114,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1778716662,
|
"lastModified": 1785627969,
|
||||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -135,11 +135,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783008725,
|
"lastModified": 1784288435,
|
||||||
"narHash": "sha256-jGiy6+sxjNWXSjp25uoJuNfyH9zBK1PEDY0lVoL4ibQ=",
|
"narHash": "sha256-ReRHaLgr/uVqdD8afFSn+myXIfpHeOhP0yYe0TJqAA8=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "git-hooks.nix",
|
"repo": "git-hooks.nix",
|
||||||
"rev": "bca82caa46d5ec0f5d422c61fb1e30bc51313cbe",
|
"rev": "43b3c1ab9d40fb1dbb008f451988a91e375825e9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -155,11 +155,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783740085,
|
"lastModified": 1786924861,
|
||||||
"narHash": "sha256-qajyHfZY29G2oEQk+uHxmsJcRoBUBXP9maTpFlwP/dI=",
|
"narHash": "sha256-hftabkb+73OcGzvwFAjCiQorAhprs9TnU1+FkGO5CIw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "3cd22efe6471dc7365c822bd9ad73a21e55f38fb",
|
"rev": "09ae1b85a6db412d841d60f924b23f881f0d0a38",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -211,11 +211,11 @@
|
|||||||
"brew-src": "brew-src"
|
"brew-src": "brew-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783875858,
|
"lastModified": 1786686423,
|
||||||
"narHash": "sha256-+yYNOkj/bkVQmwKH0hewqwBwAEoh4Gq2BmQPIP1jNrg=",
|
"narHash": "sha256-8q3WdB8o3VUI7rOz1OXfioXIaaWbFTAxRJAkWLlfc0s=",
|
||||||
"owner": "zhaofengli",
|
"owner": "zhaofengli",
|
||||||
"repo": "nix-homebrew",
|
"repo": "nix-homebrew",
|
||||||
"rev": "60641da8324e6a1af716a0340d901ccb131c91ef",
|
"rev": "ccabf79a6b9845eb72b51ea1d9c7ce3446350df3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -231,11 +231,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783864904,
|
"lastModified": 1786852476,
|
||||||
"narHash": "sha256-BQxN5UMg9FOevAsgBRwPxfxlh51Puj+dNn/8Dsi3sPM=",
|
"narHash": "sha256-IM5CYtf86W4w8eUPpKcY/LpdHElmVBtJhaKnoTKxZEA=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nix-index-database",
|
"repo": "nix-index-database",
|
||||||
"rev": "1111b9bc836afb7e31a7014e8d1272de9b1c917d",
|
"rev": "c7962dc97b45129df8d751bedaf37beb5a17706e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -252,11 +252,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783669315,
|
"lastModified": 1786862401,
|
||||||
"narHash": "sha256-DjIkyK48jWUxYCCoTDS9L5PgGg6/RFRSRXW2dSFpJg8=",
|
"narHash": "sha256-zRPYCn5RJWxr9uyUwNIQjPsTFcIFRwuRnI91dqvGA0k=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-apple-silicon",
|
"repo": "nixos-apple-silicon",
|
||||||
"rev": "9e46a0edd8a6d96538d146a4bb4477e7fae8b1a0",
|
"rev": "53798a0eb0fa4c8cfaeca7bdc5b4ad22ed210c95",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -272,11 +272,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783792734,
|
"lastModified": 1786867632,
|
||||||
"narHash": "sha256-50rvY9GdFvpYDcMLcD/4cWSi0hVxArT5wsGlVsHy8eY=",
|
"narHash": "sha256-ez+ubZlA1RtdjCB18a6zJ9M4u8qoPDy08EcnsW5M3Xw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "8efb4337e857949f4cfac86d12ef1066f417f31f",
|
"rev": "ff17823245ab9ff7bcae6acf950bd89cba82c38c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -293,11 +293,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783897948,
|
"lastModified": 1784642409,
|
||||||
"narHash": "sha256-wusXpttNJn7SvUMvGLpNuJgcwIIkMwlWNnasPPxpftg=",
|
"narHash": "sha256-hcbDqFuySAJawljt5r0sKBCJKYnbtGD0T/ZIozH1Dq0=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "NixOS-WSL",
|
"repo": "NixOS-WSL",
|
||||||
"rev": "7348d3f38ab1bd6abe156a923fab6f43656b168f",
|
"rev": "eaeb18da90024448a60eb1ec7132eafa4003404e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -308,11 +308,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783703440,
|
"lastModified": 1786711500,
|
||||||
"narHash": "sha256-O3/YajjWo001VUIgD8BwaRdSNLUFe7nZ1qV5TwhRBcw=",
|
"narHash": "sha256-QvnceIGTBeDvDd9oCn+GvdsnkquliuwbVgpiRH68qaQ=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8f0500b9660505dc3cb647775fe9a978a74b5283",
|
"rev": "02e08985a27c65ffd33d434eeb2e660a2e4dc84d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -324,11 +324,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1783776592,
|
"lastModified": 1786862985,
|
||||||
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
|
"narHash": "sha256-FBJRXmbGXiSUDvYEbfLYRkckayyZ6SK1UEqhCrIZ2Cs=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
|
"rev": "e5bdc4a41d4c072fe1e3787eaa0320a384741d44",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -347,11 +347,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1782919967,
|
"lastModified": 1786873773,
|
||||||
"narHash": "sha256-pRwjfB5HQJ3m8J8bOR43pPHtHI7VUJSqwLA3P06cOY0=",
|
"narHash": "sha256-Hj/nkhKDv0aJly1PAUstrhrgEYn1mVSkLIYMh90r/Pc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixvim",
|
"repo": "nixvim",
|
||||||
"rev": "667c8471f4a0fb24d702d1a61af8609f1a5f1ba6",
|
"rev": "b397fb9f6950d57355d62bb92457d223464e0115",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -402,11 +402,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780220602,
|
"lastModified": 1786901030,
|
||||||
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
|
"narHash": "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
|
"rev": "27b3b12a8e6375f28ebe122f07d230ca5459bbfa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -84,7 +84,9 @@
|
|||||||
flake-parts.lib.mkFlake { inherit inputs; } (
|
flake-parts.lib.mkFlake { inherit inputs; } (
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
# claude-code tracks nixpkgs-unstable regardless of the pinned nixpkgs.
|
# These track nixpkgs-unstable regardless of the pinned nixpkgs.
|
||||||
|
# gcx: 26.05 ships 0.2.14, which predates the stacks/contexts config
|
||||||
|
# model and the agento11y commands the tooling expects.
|
||||||
overlays = [
|
overlays = [
|
||||||
(_final: prev: {
|
(_final: prev: {
|
||||||
inherit
|
inherit
|
||||||
@@ -93,13 +95,44 @@
|
|||||||
config.allowUnfree = true;
|
config.allowUnfree = true;
|
||||||
})
|
})
|
||||||
claude-code
|
claude-code
|
||||||
|
gcx
|
||||||
;
|
;
|
||||||
})
|
})
|
||||||
|
# commitizen 4.13.9's regression test for the invalid-command error
|
||||||
|
# message asserts argparse's older, unquoted "invalid choice" wording;
|
||||||
|
# the argparse in Python 3.13 quotes each choice, so the fixture no
|
||||||
|
# longer matches and the checkPhase fails. The package itself is fine
|
||||||
|
# -- deselect just that test. Drop once nixpkgs updates the fixture.
|
||||||
|
(_final: prev: {
|
||||||
|
commitizen = prev.commitizen.overridePythonAttrs (old: {
|
||||||
|
disabledTests = (old.disabledTests or [ ]) ++ [ "test_invalid_command" ];
|
||||||
|
});
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
# Unfree packages permitted to be built (replaces blanket allowUnfree).
|
# Unfree packages permitted to be built (replaces blanket allowUnfree).
|
||||||
|
# The NVIDIA entries are for the Mac Pro's Quadro P400 (hosts/MacPro31/
|
||||||
|
# nvidia.nix) and the Console host's GTX 1070 (hosts/Console/nvidia.nix);
|
||||||
|
# unfree packages are not in the binary cache, so the kernel module is
|
||||||
|
# compiled on the host. The steam/clonehero entries are the Console
|
||||||
|
# host's games stack (hosts/Console/gaming.nix).
|
||||||
unfreePackages = [
|
unfreePackages = [
|
||||||
"claude-code"
|
"claude-code"
|
||||||
|
"nvidia-x11"
|
||||||
|
"nvidia-kernel-modules"
|
||||||
|
"nvidia-settings"
|
||||||
|
"steam"
|
||||||
|
"steam-unwrapped"
|
||||||
|
"steam-run"
|
||||||
|
"clonehero"
|
||||||
|
# RetroArch cores whose upstream licences carry a non-commercial or
|
||||||
|
# no-redistribution-for-profit clause. Everything else in the core set
|
||||||
|
# is plain free software.
|
||||||
|
"libretro-snes9x"
|
||||||
|
"libretro-genesis-plus-gx"
|
||||||
|
"libretro-picodrive"
|
||||||
|
"libretro-fbneo"
|
||||||
|
"libretro-mame2003-plus"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Per-user identity, keyed by username. See README "Users".
|
# Per-user identity, keyed by username. See README "Users".
|
||||||
@@ -274,6 +307,28 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lyrathorpe-console = {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
portable = false;
|
||||||
|
# Living-room games machine on a television: autologins into the
|
||||||
|
# gamescope Steam session (hosts/Console/gaming.nix). sway.nix is
|
||||||
|
# still imported -- greetd/ReGreet is what the Steam session falls
|
||||||
|
# back to, and Sway is the keyboard-and-mouse session behind it.
|
||||||
|
modules = [
|
||||||
|
./hosts/Console/configuration.nix
|
||||||
|
./modules/desktop.nix
|
||||||
|
./modules/ssh.nix
|
||||||
|
inputs.nixos-hardware.nixosModules.common-pc-ssd
|
||||||
|
inputs.nixos-hardware.nixosModules.common-cpu-intel
|
||||||
|
./modules/sway.nix
|
||||||
|
];
|
||||||
|
users.lyrathorpe.homeModules = [
|
||||||
|
./home
|
||||||
|
./users/lyrathorpe/home.nix
|
||||||
|
./home/desktop.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
emmathorpe-edaas = {
|
emmathorpe-edaas = {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
modules = [
|
modules = [
|
||||||
@@ -422,6 +477,7 @@
|
|||||||
git = ./home/git.nix;
|
git = ./home/git.nix;
|
||||||
editor = ./home/editor.nix;
|
editor = ./home/editor.nix;
|
||||||
claude = ./home/claude.nix;
|
claude = ./home/claude.nix;
|
||||||
|
secret-service = ./home/secret-service.nix;
|
||||||
desktop = ./home/desktop.nix;
|
desktop = ./home/desktop.nix;
|
||||||
sway = ./home/sway.nix;
|
sway = ./home/sway.nix;
|
||||||
};
|
};
|
||||||
|
|||||||
-215
@@ -1,215 +0,0 @@
|
|||||||
# Interactive shell environment
|
|
||||||
|
|
||||||
Everything the shell, terminal multiplexer, git and ssh do beyond their defaults,
|
|
||||||
and where each is defined. All of it is managed declaratively through
|
|
||||||
home-manager — edit the listed file and rebuild, never the generated dotfiles.
|
|
||||||
|
|
||||||
Keyboard shortcuts have their own reference: [`KEYBINDINGS.md`](./KEYBINDINGS.md).
|
|
||||||
|
|
||||||
| Area | Defined in |
|
|
||||||
| -------------------------------------- | ----------------------------------------------------- |
|
|
||||||
| zsh, CLI tools, tmux, ssh, auto-tmux | [`shell.nix`](./shell.nix) |
|
|
||||||
| git (+ delta, commitizen) | [`git.nix`](./git.nix) |
|
|
||||||
| Neovim (nixvim) + LSP | [`editor.nix`](./editor.nix) |
|
|
||||||
| Claude Code (CLAUDE.md, style, memory) | [`claude.nix`](./claude.nix) |
|
|
||||||
| 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`](../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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## zsh
|
|
||||||
|
|
||||||
| Feature | Notes |
|
|
||||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| oh-my-zsh | plugins `git`, `man`, `sudo` (Esc-Esc to prepend sudo), `colored-man-pages`, `extract`; theme `robbyrussell` |
|
|
||||||
| Autosuggestion | fish-style history suggestions as you type (→ to accept) |
|
|
||||||
| Syntax highlighting | commands coloured by validity as you type |
|
|
||||||
| Completion | menu completion; the dump is rebuilt on every activation (see Maintenance) |
|
|
||||||
| History | 100k in-memory/on-disk, deduped, space-prefixed commands ignored, timestamped, **shared live across sessions**; file stays at `~/.zsh_history` |
|
|
||||||
| Dotfiles location | `dotDir` is `~/.config/zsh` (XDG) — `.zshrc`/`.zshenv`/`.zcompdump` live there; `~/.zshenv` only bootstraps `$ZDOTDIR` |
|
|
||||||
| History substring search | type a fragment, then ↑/↓ cycles matching past commands — works in foot, iTerm2 and the Linux TTY (both CSI and SS3 arrow encodings bound) |
|
|
||||||
| Prompt | hostname is prefixed when over SSH |
|
|
||||||
|
|
||||||
**Aliases:** `ls`/`ll`/`la`/`lt` → `eza` (icons + git), `cls` → `clear`. git aliases live in git.nix (below).
|
|
||||||
|
|
||||||
## CLI tools
|
|
||||||
|
|
||||||
| Tool | What it gives you |
|
|
||||||
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `fzf` | `Ctrl-R` fuzzy history, `Ctrl-T` file picker, `Alt-C` fuzzy cd (Catppuccin-themed) |
|
|
||||||
| `zoxide` | `z <fragment>` jumps to frecent directories |
|
|
||||||
| `direnv` + `nix-direnv` | per-project environments auto-loaded on `cd` (cached Nix dev shells) |
|
|
||||||
| `eza` | modern `ls` (drives the ls aliases) |
|
|
||||||
| `bat` | syntax-highlighting pager (Catppuccin Mocha theme); behaves like `cat` when piped; also the `MANPAGER` |
|
|
||||||
| `ripgrep` / `fd` | fast search (`rg`) and find (`fd`); also back `fzf` |
|
|
||||||
| `jq` | JSON processor |
|
|
||||||
| `gh` / `tea` | GitHub and Gitea (`code.emmathe.dev`) CLIs; `gh` uses SSH |
|
|
||||||
| `nix-index` | `command-not-found`: an unknown command tells you which Nix package provides it (prebuilt DB, no manual indexing) |
|
|
||||||
| `comma` (`,`) | run an uninstalled program once: `, cowsay hi` |
|
|
||||||
| `nh` | nicer `nixos-rebuild`/`home-manager` with diffs; `$NH_FLAKE` set to the repo. No scheduled GC (it could reap paths a running generation still references) — collect garbage manually with `nh clean all` / `nix-collect-garbage -d` |
|
|
||||||
| `btop` | resource monitor, themed Catppuccin Mocha (vendored theme) |
|
|
||||||
| `lazygit` | git TUI for staging/rebasing, themed to match (`git.nix`) |
|
|
||||||
| `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 `../lib/catppuccin-mocha.nix` palette / the
|
|
||||||
catppuccin upstream themes.
|
|
||||||
|
|
||||||
**Env & defaults:** `xdg.enable` on; `PAGER`/`MANPAGER` (bat) set in `default.nix`
|
|
||||||
(the editor owns `$EDITOR`/`$VISUAL`); `xdg.mimeApps` maps web→Firefox,
|
|
||||||
directories→nemo (`desktop.nix`).
|
|
||||||
|
|
||||||
## tmux
|
|
||||||
|
|
||||||
**Auto-start:** opening any interactive terminal — foot, iTerm2, the WSL shell, the
|
|
||||||
Linux console — drops you straight into a tmux session named `main` (attach if it
|
|
||||||
exists, else create). Panes run a plain non-login zsh. It deliberately does **not**
|
|
||||||
fire for SSH sessions, VS Code's integrated terminal, already-inside-tmux, or
|
|
||||||
non-interactive shells. Escape hatch: `NO_TMUX=1 <terminal>` opens a bare shell.
|
|
||||||
|
|
||||||
| Setting | Value |
|
|
||||||
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
||||||
| Mode keys | vi |
|
|
||||||
| Mouse | on |
|
|
||||||
| Scrollback | 500000 lines |
|
|
||||||
| `escape-time` | 10ms (the 500ms default lagged vim's ESC) |
|
|
||||||
| `focus-events` | on (vim autoread) |
|
|
||||||
| `base-index` / `pane-base-index` | 1 |
|
|
||||||
| Splits | `prefix s` vertical, `prefix v` horizontal (stock `%`/`"` unbound) |
|
|
||||||
| Pane nav | `Alt`+arrows (no prefix) |
|
|
||||||
| Terminal | `default-terminal tmux-256color`; truecolor advertised per outer terminal (`foot*`, `xterm-256color`/iTerm2) via `terminal-features … RGB` |
|
|
||||||
| Clipboard | `set-clipboard on`; foot `terminal-features` advertise truecolor/sync/OSC52/title/cursor |
|
|
||||||
|
|
||||||
**Plugins:** `sensible`, `vim-tmux-navigator` (Ctrl-h/j/k/l across vim ↔ tmux),
|
|
||||||
`yank`, `extrakto` (`prefix`+`Tab`: fzf-grab paths/URLs/text from the pane into
|
|
||||||
the prompt), `catppuccin` (Mocha statusline), `resurrect` + `continuum`
|
|
||||||
(sessions auto-save and restore across reboots). The statusline draws Nerd-Font
|
|
||||||
glyphs — see Fonts.
|
|
||||||
|
|
||||||
## Fonts
|
|
||||||
|
|
||||||
**JetBrainsMono Nerd Font**, **Noto Sans** and **Noto Color Emoji** are
|
|
||||||
installed on every host (in `common-nixos.nix`, because tmux/terminals run
|
|
||||||
everywhere; the Mac installs the Nerd Font to `/Library/Fonts` via the Darwin
|
|
||||||
config). `fonts.fontconfig.defaultFonts` maps the generic families so anything
|
|
||||||
asking for `monospace` gets the Nerd Font (with emoji fallback) — this also
|
|
||||||
gives the WSL box emoji/sans coverage it otherwise lacked. foot uses the Nerd
|
|
||||||
Font as its main font automatically. iTerm2's font is a GUI setting — set it to
|
|
||||||
_JetBrainsMono Nerd Font_ (Settings → Profiles → Text → Font) so the tmux
|
|
||||||
statusline glyphs render instead of `?`.
|
|
||||||
|
|
||||||
## Editor (Neovim)
|
|
||||||
|
|
||||||
`nvim` — aliased to `vi`/`vim`, and set as `$EDITOR`/`$VISUAL` — is configured
|
|
||||||
declaratively with **nixvim**, so the same plugins and config are baked in on
|
|
||||||
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 |
|
|
||||||
| Format on save | conform-nvim (nixfmt, stylua, ruff, shfmt, prettier, gofumpt; LSP fallback otherwise) |
|
|
||||||
| Git | fugitive (`:Git …`) + gitsigns gutter signs/blame |
|
|
||||||
| Diagnostics | inline + trouble list (`<leader>xx`) |
|
|
||||||
| Completion | nvim-cmp (LSP/buffer/path) with luasnip snippet expansion |
|
|
||||||
| Indent guides | indent-blankline, on by default (was vim-indent-guides) |
|
|
||||||
| Statusline | lualine (Catppuccin theme) |
|
|
||||||
| 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_ls` (Nix), `lua_ls`, `pyright` (Python), `terraformls` |
|
|
||||||
| Indentation | 2-wide hard tabs (`noexpandtab`, `tabstop`/`shiftwidth` = 2); line numbers on |
|
|
||||||
| Filetypes | `*Jenkinsfile` → groovy |
|
|
||||||
|
|
||||||
Leader is `Space`. LSP keymaps (`gd`, `gr`, `K`, `<leader>rn`, `<leader>ca`) and
|
|
||||||
the file-tree toggle are listed in
|
|
||||||
[`KEYBINDINGS.md`](./KEYBINDINGS.md#neovim). Add a universal language server by
|
|
||||||
enabling it under `programs.nixvim.plugins.lsp.servers` in `editor.nix`;
|
|
||||||
host-specific ones go in that host's module — the work box (`work.nix`) adds
|
|
||||||
`omnisharp` (C#) and `helm_ls` (Helm), kept off the personal machines.
|
|
||||||
|
|
||||||
## git
|
|
||||||
|
|
||||||
Pager is **delta**. **commitizen** is installed on every host; `cz` defaults to
|
|
||||||
Conventional Commits. **lazygit** (themed) is the TUI. The commit-graph is kept
|
|
||||||
current (`gc`/`fetch.writeCommitGraph`) so `lg` stays fast.
|
|
||||||
|
|
||||||
| Aliases | |
|
|
||||||
| ------------------------ | ------------------------------------------------------------------ |
|
|
||||||
| `st` `co` `sw` `br` `ci` | status / checkout / switch / branch / commit |
|
|
||||||
| `last` `unstage` | last commit / unstage |
|
|
||||||
| `amend` `fixup` `undo` | amend-no-edit / `commit --fixup` / soft-reset HEAD~1 (keep staged) |
|
|
||||||
| `lg` | graph log, all branches |
|
|
||||||
| `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) |
|
|
||||||
| Diffs | histogram algorithm, colour-moved |
|
|
||||||
| `rerere` | remembers + replays conflict resolutions |
|
|
||||||
| 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). Name, email and signing key all come from the per-user `identity` (the user registry, `../users/registry.nix`). |
|
|
||||||
|
|
||||||
## ssh
|
|
||||||
|
|
||||||
| Feature | Notes |
|
|
||||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| ssh-agent | runs on Linux (launchd on macOS); keys added on **first use** so the passphrase is typed once per login session — this also feeds git commit signing |
|
|
||||||
| macOS | `UseKeychain` caches the passphrase in the login keychain (guarded by `IgnoreUnknown`, so a non-Apple `ssh` skips it instead of erroring) |
|
|
||||||
| Gitea remote | `code.emmathe.dev` → `HostName 10.187.1.76` (DNS-override), `Port 30009`, user `git`, dedicated key, `identitiesOnly` |
|
|
||||||
| Defaults | the module's deprecated default block is opted out; equivalents kept under `settings."*"` |
|
|
||||||
|
|
||||||
The **work box keeps its own `~/.ssh/config`** (home-manager's `programs.ssh` is
|
|
||||||
forced off there) but still runs the agent.
|
|
||||||
|
|
||||||
## Claude Code
|
|
||||||
|
|
||||||
Managed declaratively by [`claude.nix`](./claude.nix) on every host (the CLI is
|
|
||||||
`pkgs.claude-code`, tracked to unstable via the flake overlay).
|
|
||||||
|
|
||||||
| Managed (static, from Nix) | Left mutable (runtime state) |
|
|
||||||
| --------------------------------------------------- | ------------------------------------------------------ |
|
|
||||||
| `~/.claude/CLAUDE.md` (persona + memory workflow) | `settings.json` (permissions, model, theme, `/config`) |
|
|
||||||
| `~/.claude/output-styles/soviet-engineer.md` | `.credentials.json`, history, caches |
|
|
||||||
| `~/.claude/memory/` (read-only symlink to the repo) | |
|
|
||||||
|
|
||||||
`settings.json` is intentionally **not** managed: Claude rewrites it at runtime
|
|
||||||
(interactive permission grants, `/config`), which a read-only store symlink would
|
|
||||||
break.
|
|
||||||
|
|
||||||
**Memory is sourced from this repo.** The files in
|
|
||||||
[`claude/memory/`](./claude/memory) are the source of truth; they are symlinked
|
|
||||||
read-only into `~/.claude/memory`, so recall works but the runtime "save a
|
|
||||||
memory" path does not. To add/change/remove a memory, edit `claude/memory/`
|
|
||||||
(one file per memory + the `MEMORY.md` index) and rebuild — `CLAUDE.md` tells
|
|
||||||
Claude to route new memories there.
|
|
||||||
|
|
||||||
## Maintenance behaviours
|
|
||||||
|
|
||||||
- **zcompdump reset** — `~/.config/zsh/.zcompdump*` (plus legacy `~/.zcompdump*`
|
|
||||||
and the cache copy) is removed on every activation, so a stale
|
|
||||||
dump (pointing at `/nix/store` paths a rebuild or a manual GC removed) can't
|
|
||||||
break completion with `_git: function definition file not found`.
|
|
||||||
- **GC** — no scheduled timer; collect garbage deliberately (`nh clean all` /
|
|
||||||
`nix-collect-garbage -d`) when no important session is running.
|
|
||||||
|
|
||||||
## Per-host differences
|
|
||||||
|
|
||||||
| | Personal Linux (sway) | macOS | Work WSL (EDaaS) |
|
|
||||||
| --------------------------- | --------------------- | ----------------- | --------------------------- |
|
|
||||||
| Auto-tmux | yes (foot/TTY) | yes (iTerm2) | yes (WSL shell) |
|
|
||||||
| git email | `iam@emmathe.dev` | `iam@emmathe.dev` | `…@citrix.com` (work) |
|
|
||||||
| ssh config managed | yes | yes | no (keeps corporate config) |
|
|
||||||
| ssh-agent | yes | launchd | yes (work module) |
|
|
||||||
| GUI / theming (desktop.nix) | yes | no | no |
|
|
||||||
+21
-5
@@ -1,4 +1,5 @@
|
|||||||
# Claude Code, configured declaratively via home-manager. Wanted on every host.
|
# Claude Code, configured declaratively via home-manager. Wanted on every host
|
||||||
|
# whose CPU can run it -- see the gate below.
|
||||||
#
|
#
|
||||||
# The STATIC config is managed here: the global CLAUDE.md (persona/context), the
|
# The STATIC config is managed here: the global CLAUDE.md (persona/context), the
|
||||||
# custom output style, and the auto-memory directory. settings.json is
|
# custom output style, and the auto-memory directory. settings.json is
|
||||||
@@ -10,18 +11,33 @@
|
|||||||
# read-only into ~/.claude/memory, so the runtime "save a memory" path no longer
|
# read-only into ~/.claude/memory, so the runtime "save a memory" path no longer
|
||||||
# writes there -- recall still works, but new/changed memories must be added to
|
# writes there -- recall still works, but new/changed memories must be added to
|
||||||
# this repo and rebuilt. CLAUDE.md instructs Claude to do exactly that.
|
# this repo and rebuilt. CLAUDE.md instructs Claude to do exactly that.
|
||||||
{ ... }:
|
{
|
||||||
|
lib,
|
||||||
|
# Set by the NixOS/Darwin home-manager module; absent for the standalone
|
||||||
|
# homeConfigurations, hence the default.
|
||||||
|
osConfig ? { },
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# Capability gate, declared once for the whole fleet in modules/features.nix
|
||||||
|
# (default: on; off on CPUs below x86-64-v2, which cannot run the Node
|
||||||
|
# runtime Claude Code ships on). Hosts without that option -- the Darwin host
|
||||||
|
# and the portable standalone profile -- fall back to enabled.
|
||||||
|
enable = osConfig.features.claudeCode.enable or true;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
programs.claude-code = {
|
programs.claude-code = {
|
||||||
enable = true;
|
inherit enable;
|
||||||
# package defaults to pkgs.claude-code (tracked to unstable via the flake
|
# package defaults to pkgs.claude-code (tracked to unstable via the flake
|
||||||
# overlay); installs the CLI on every host.
|
# overlay).
|
||||||
|
|
||||||
# ~/.claude/CLAUDE.md -- global instructions / persona / memory workflow.
|
# ~/.claude/CLAUDE.md -- global instructions / persona / memory workflow.
|
||||||
context = ./claude/CLAUDE.md;
|
context = ./claude/CLAUDE.md;
|
||||||
};
|
};
|
||||||
|
|
||||||
home.file = {
|
# Nothing to place when the CLI is not installed: a ~/.claude/memory symlink
|
||||||
|
# with no Claude Code to read it is just dead state.
|
||||||
|
home.file = lib.mkIf enable {
|
||||||
# Custom output style. The module has no option for output-styles/, so place
|
# Custom output style. The module has no option for output-styles/, so place
|
||||||
# it directly; selection (settings.json `outputStyle`) stays mutable.
|
# it directly; selection (settings.json `outputStyle`) stays mutable.
|
||||||
".claude/output-styles/soviet-engineer.md".source = ./claude/output-styles/soviet-engineer.md;
|
".claude/output-styles/soviet-engineer.md".source = ./claude/output-styles/soviet-engineer.md;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
- [User name](user_name.md) — address the user as Lyra
|
- [User name](user_name.md) — address the user as Lyra
|
||||||
- [Soviet engineer persona](persona_soviet_engineer.md) — terse, dry, pragmatic; no emojis; technical accuracy over voice
|
- [Soviet engineer persona](persona_soviet_engineer.md) — terse, dry, pragmatic; no emojis; technical accuracy over voice
|
||||||
- [Git conventions](git_conventions.md) — never commit to main, always a branch; Conventional Commits branches and messages; inspect repo style first; commit at logical checkpoints
|
- [Git conventions](git_conventions.md) — never commit to main, always a branch; EVERY commit is `type(<TICKET-ID>): summary` using the live ticket, overrides repo's bare-prefix style; watch for scope decay on follow-up commits; grep to verify before pushing
|
||||||
- [Git network ops](git_network_ops.md) — GitHub and Gitea (code.emmathe.dev) both pushable in-sandbox (sandbox off, agent key); raise Gitea PRs via tea CLI
|
- [Git network ops](git_network_ops.md) — GitHub and Gitea (code.emmathe.dev) both pushable in-sandbox (sandbox off, agent key); raise Gitea PRs via tea CLI
|
||||||
- [Git commit signing](git_commit_signing.md) — signs in-sandbox via ssh-agent (allowAllUnixSockets + inlined pubkey); sig=N without allowedSignersFile is cosmetic, still signed
|
- [Git commit signing](git_commit_signing.md) — signs in-sandbox via ssh-agent (allowAllUnixSockets + inlined pubkey); sig=N without allowedSignersFile is cosmetic, still signed
|
||||||
- [Git check state first](git_check_state.md) — always check branch/status/divergence before git work; Lyra edits repos between sessions
|
- [Git check state first](git_check_state.md) — always check branch/status/divergence before git work; Lyra edits repos between sessions
|
||||||
@@ -14,3 +14,4 @@
|
|||||||
- [Sandbox prompts](feedback_sandbox_prompts.md) — don't prompt for sandbox-disable or routine read-only shell ops; broaden permissions instead
|
- [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
|
- [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
|
- [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
|
||||||
|
- [WSP local build and test](wsp_local_build_and_test.md) — core-services-cloud on this box: dotnet via nix, artifactory creds from `~/.artifactoryenv` sourced per command, how to tell auth failure from a code failure
|
||||||
|
|||||||
@@ -11,8 +11,34 @@ metadata:
|
|||||||
|
|
||||||
**Branch naming:** Follow the repo's existing convention — inspect with `git branch -a` or `git for-each-ref` before creating. Prefer Conventional Commits prefixes (`feat/`, `fix/`, `chore/`, `docs/`, `refactor/`). Format: `<prefix>/<TICKET-ID>-<kebab-summary>`. Only ask if no convention is discoverable.
|
**Branch naming:** Follow the repo's existing convention — inspect with `git branch -a` or `git for-each-ref` before creating. Prefer Conventional Commits prefixes (`feat/`, `fix/`, `chore/`, `docs/`, `refactor/`). Format: `<prefix>/<TICKET-ID>-<kebab-summary>`. Only ask if no convention is discoverable.
|
||||||
|
|
||||||
**Commit messages:** Conventional Commits. Subject line: `<type>(<TICKET-ID>): <imperative summary>` — ticket ID as the scope. Use additional `-m` flags for rationale/body. Commit at logical checkpoints, not one giant final commit.
|
**Commit messages — every commit, without exception:** `<type>(<TICKET-ID>): <imperative summary>`. The ticket ID goes in the scope. Use additional `-m` flags for rationale/body. Commit at logical checkpoints, not one giant final commit.
|
||||||
|
|
||||||
**Why:** Lyra's standard workflow for traceability and clean history.
|
**`<TICKET-ID>` is the real ticket for the work in hand.** It is a symbol to substitute, never a literal — if a commit subject ever reaches git still containing `<TICKET-ID>`, or a made-up number, that is a defect. Establish the actual ID before the first commit, in this order:
|
||||||
|
|
||||||
**How to apply:** Whenever creating a branch or committing in any repo. Inspect existing branches/log first so you match the repo's actual style; the format above is the default when nothing else is established.
|
1. The ticket Lyra named in the request.
|
||||||
|
2. The current branch name — `task/WSP-32542/remove-wspgov-terraform` gives `WSP-32542`. Extract it: `git branch --show-current | grep -oE '[A-Z]{2,}-[0-9]+'`.
|
||||||
|
3. The ticket the branch's existing commits already use.
|
||||||
|
|
||||||
|
If none of those yield an ID, ask which ticket to file the work under. Do not guess, do not reuse the ID from an unrelated earlier task in the session, and do not invent a plausible-looking number. Every commit in a branch normally carries the same ID; if the work genuinely spans two tickets, split the commits accordingly rather than picking one at random.
|
||||||
|
|
||||||
|
**Exception — repos with no issue tracker.** Personal repos such as `nixfiles` have no Jira project. There the scope is the area of the change, not a ticket: `chore(claude): ...`, `chore(deps): ...`, `feat(hosts): ...`. Conventional form is still required; only the ticket scope is dropped. Never invent a WSP number to satisfy the rule in a repo that has no tickets. The ticket requirement applies to the work repos under `~/code` that are backed by the WSP Jira project and gated by CI.
|
||||||
|
|
||||||
|
**This format is mandatory and overrides the repo's existing log style.** Many repos (`multicluster`, `core-services-cloud`) have histories full of bare `<TICKET-ID>: summary` subjects written by other people. Do not copy that. Match repo style for _branch names_ only; commit subjects are always full Conventional Commits with the ticket scope. CI enforces this, and a failure means Lyra rebases the history by hand.
|
||||||
|
|
||||||
|
**Known failure mode — scope decay across a session.** The first commit gets `fix(<TICKET-ID>): ...` correctly, then follow-up commits in the same sitting degrade to bare `test: add tests for class`, `refactor: hoist middleware`, `chore: tidy`. This has caused real rebase work in `core-services-cloud`. The second, third and fifth commits need the ticket scope exactly as much as the first. Re-read the subject against the format before every single `git commit`.
|
||||||
|
|
||||||
|
**Merge commits count too.** Prefer `git rebase origin/<base>` over `git merge` so none is created. If unavoidable, set the message explicitly: `git merge --no-ff -m "<TICKET-ID>: merge master into <branch>"`. Keep the ID uppercase; the check is case-sensitive.
|
||||||
|
|
||||||
|
**Before pushing, verify — do not skip this:**
|
||||||
|
|
||||||
|
```
|
||||||
|
git log --format=%s origin/<base>..HEAD | grep -vE '^[a-z]+(\([A-Z]{2,}-[0-9]+\))!?: '
|
||||||
|
```
|
||||||
|
|
||||||
|
Must print nothing. Writing each subject carefully is not a substitute for running it.
|
||||||
|
|
||||||
|
**Auditing past behaviour is unreliable.** If Lyra has already rebased to fix a bad subject, the log shows her corrected version, not what was originally written. A clean `git log` is not evidence that nothing was wrong. Check author date vs committer date (`--format="%ad %cd"`) — a mismatch means history was rewritten. Never argue from a clean log that the fault did not occur.
|
||||||
|
|
||||||
|
**Why:** Lyra's standard workflow for traceability, and a hard CI gate. A malformed subject is manual rebase work for her, not just a red build.
|
||||||
|
|
||||||
|
**How to apply:** Conventional form on every commit in every repo; the ticket scope additionally on every commit in a Jira-backed work repo. Format first, repo style second. Run the verification grep before every push. Relates to [[git_check_state]].
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ metadata:
|
|||||||
|
|
||||||
**Transitions:** `transitionJiraIssue` may fail if the issue lacks an assignee. Set assignee first via `editJiraIssue` when a transition errors on assignee requirement.
|
**Transitions:** `transitionJiraIssue` may fail if the issue lacks an assignee. Set assignee first via `editJiraIssue` when a transition errors on assignee requirement.
|
||||||
|
|
||||||
|
**Transition required fields (WSP):** the same target status can enforce different required fields per issue type — e.g. `Cancelled` on a Story requires `Resolution` + `Justification`, but on an Epic requires neither (so an Epic can land in Cancelled while still reading Unresolved). Fetch requirements with `getTransitionsForJiraIssue` + `expand=transitions.fields` before transitioning. Cancel/won't-do resolution values: `Won't Fix` (10068), `Canceled` (10070), `Obsolete` (10073 — use for superseded-by-another-ticket).
|
||||||
|
|
||||||
|
**ADF-only custom fields:** the WSP `Justification` field (`customfield_10070`) advertises schema `textarea` (string) but the API rejects a plain string — it requires an Atlassian Document Format object (`{type:"doc",version:1,content:[...]}`). If a transition/edit errors with "Operation value must be an Atlassian Document", wrap the text in ADF.
|
||||||
|
|
||||||
**Issue link direction:** For `createIssueLink`, "X is blocked by Y" means `inwardIssue=Y` (the blocker), `outwardIssue=X` (the blocked), `type.name="Blocks"`. Inward = the side the link points _from_; outward = the side it points _to_.
|
**Issue link direction:** For `createIssueLink`, "X is blocked by Y" means `inwardIssue=Y` (the blocker), `outwardIssue=X` (the blocked), `type.name="Blocks"`. Inward = the side the link points _from_; outward = the side it points _to_.
|
||||||
|
|
||||||
**WSP project transition IDs:**
|
**WSP project transition IDs:**
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
name: wsp-local-build-and-test
|
||||||
|
description: "How to compile and test core-services-cloud locally on Lyra's NixOS/WSL box: dotnet via nix, artifactory creds from ~/.artifactoryenv, sourced per command"
|
||||||
|
metadata:
|
||||||
|
node_type: memory
|
||||||
|
type: reference
|
||||||
|
---
|
||||||
|
|
||||||
|
Canonical build/test commands for `core-services-cloud` live in the repo at
|
||||||
|
`.ai/agents.md` and `.ai/component-tests.md` — read those rather than guessing.
|
||||||
|
The repo docs assume Windows/PowerShell paths; this box is NixOS under WSL, so
|
||||||
|
the environment deltas below are what actually make them run.
|
||||||
|
|
||||||
|
**dotnet is not on PATH.** Get it from nixpkgs — see [[nix-shell-tooling]]:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix shell nixpkgs#dotnet-sdk_8 --command dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
`global.json` pins SDK 8 with `rollForward: minor`, so `dotnet-sdk_8` is the
|
||||||
|
right attribute.
|
||||||
|
|
||||||
|
**Every restore needs artifactory credentials.** They live in
|
||||||
|
`~/.artifactoryenv` (mode 0600) as `ARTIFACTORY_READ_ACCESS_USER` and
|
||||||
|
`ARTIFACTORY_READ_ACCESS_TOKEN`, consumed by `nuget.config`. Shell state does
|
||||||
|
not persist between tool calls, so source them inside each command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
set -a; . ~/.artifactoryenv; set +a
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check the credentials before blaming the code.** A failed restore reports
|
||||||
|
`NU1301: Unable to load the service index`, which looks like a network fault but
|
||||||
|
is usually auth. Confirm which it is:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -s -o /dev/null -w '%{http_code}\n' \
|
||||||
|
-u "$ARTIFACTORY_READ_ACCESS_USER:$ARTIFACTORY_READ_ACCESS_TOKEN" \
|
||||||
|
https://repo.citrite.net/api/nuget/v3/stf-virtual-nuget/index.json
|
||||||
|
```
|
||||||
|
|
||||||
|
200 means the credentials are good. 401 means the token is the problem, not the
|
||||||
|
change under test. `https://repo.citrite.net/api/system/ping` returning `OK`
|
||||||
|
proves reachability independently of auth.
|
||||||
|
|
||||||
|
**Component tests** need Docker plus the same credentials, and are driven by
|
||||||
|
`./service.ps1` — PowerShell, so `nix shell nixpkgs#powershell` if `pwsh` is
|
||||||
|
missing. Log in to the image registry first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo "$ARTIFACTORY_READ_ACCESS_TOKEN" | docker login stf-virtual-docker.repo.citrite.net \
|
||||||
|
--username "$ARTIFACTORY_READ_ACCESS_USER" --password-stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
Two Docker Desktop leftovers break this box, both fatal and both easy to miss:
|
||||||
|
|
||||||
|
1. `/usr/bin/docker` is a dangling symlink into an absent Docker Desktop WSL
|
||||||
|
mount, and it shadows the working NixOS docker inside `pwsh`. The script dies
|
||||||
|
with `Program 'docker' failed to run ... No such file`.
|
||||||
|
2. `~/.docker/config.json` sets `"credsStore": "desktop.exe"`, a helper that does
|
||||||
|
not exist. `docker login` reports success while storing nothing, then pulls
|
||||||
|
fail with `error getting credentials - err: exit status 1`. Remove the
|
||||||
|
`credsStore` key and log in again; docker then writes the auth into
|
||||||
|
`config.json` itself.
|
||||||
|
|
||||||
|
Put the real docker first when invoking anything that shells out to it, and note
|
||||||
|
`$PATH` must expand _inside_ the nix shell or dotnet drops off the path:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix shell nixpkgs#dotnet-sdk_8 --command sh -c \
|
||||||
|
'export PATH="/run/current-system/sw/bin:$PATH"; dotnet test ...'
|
||||||
|
```
|
||||||
|
|
||||||
|
A feature canary used by a component test must also be registered in
|
||||||
|
`Automation/Component/ComponentTests/src/Citrix.Wsp.Test.Mocks/WspComprehensive/__files/unleash/unleash-test-environment.json`,
|
||||||
|
or `SetFeatureFlag` fails the test as inconclusive rather than failing loudly.
|
||||||
@@ -20,6 +20,25 @@ report? If the latter, rewrite. Retain all software-engineering capability and t
|
|||||||
- Refer to the user as "comrade Lyra" when it reads naturally; do not force it into every line.
|
- Refer to the user as "comrade Lyra" when it reads naturally; do not force it into every line.
|
||||||
- No emojis.
|
- No emojis.
|
||||||
|
|
||||||
|
## Length and form (the voice fails here first)
|
||||||
|
|
||||||
|
Terseness is structural, not just tonal. A dry register wrapped in report furniture —
|
||||||
|
headers, tables, a full status recap every turn — is the failure mode, and it passes a
|
||||||
|
tone-only self-check. Enforce:
|
||||||
|
|
||||||
|
- Default ceiling around 150 words. Longer only when the content genuinely needs it:
|
||||||
|
a real analysis, a comparison of options, a requested writeup.
|
||||||
|
- Headers and tables only for four or more distinct items. Two facts are two sentences.
|
||||||
|
- Report the delta since the last message, never the accumulated state. Assume Lyra
|
||||||
|
remembers what she was told.
|
||||||
|
- State each caveat once per session. Repeating a settled limitation is filler.
|
||||||
|
- Do the obvious next action and report it. Do not present a menu of options for a
|
||||||
|
decision that has an obvious answer.
|
||||||
|
- Do not restate the request, or narrate what is about to be done.
|
||||||
|
|
||||||
|
Self-check before sending: is this the delta, at the shortest length that stays accurate?
|
||||||
|
If it reads like a status report, cut it to the three facts that changed.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
The persona lives in PROSE ONLY — explanations, summaries, status, discussion. It must NEVER
|
The persona lives in PROSE ONLY — explanations, summaries, status, discussion. It must NEVER
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
./git.nix
|
./git.nix
|
||||||
./editor.nix
|
./editor.nix
|
||||||
./claude.nix
|
./claude.nix
|
||||||
|
# Declares services.headlessSecretService; opt-in, off by default. Graphical
|
||||||
|
# hosts should prefer home-manager's own services.gnome-keyring.
|
||||||
|
./secret-service.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
# Manage the XDG base-directory layout and ~/.config files. Tools above
|
# Manage the XDG base-directory layout and ~/.config files. Tools above
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
pkgs.element-desktop
|
pkgs.element-desktop
|
||||||
pkgs.legcord
|
pkgs.legcord
|
||||||
pkgs.nemo # file manager (launched via Mod+e, see ./sway.nix)
|
pkgs.nemo # file manager (launched via Mod+e, see ./sway.nix)
|
||||||
|
pkgs.darktable
|
||||||
#pkgs.plex-desktop
|
#pkgs.plex-desktop
|
||||||
#pkgs.plexamp
|
#pkgs.plexamp
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ in
|
|||||||
# `cz commit`, `git cz bump`, etc. `git cc` is a shortcut for the prompt.
|
# `cz commit`, `git cz bump`, etc. `git cc` is a shortcut for the prompt.
|
||||||
cz = "!cz";
|
cz = "!cz";
|
||||||
cc = "!cz commit";
|
cc = "!cz commit";
|
||||||
|
# Structural (syntax-aware) diff, on demand. Set per-invocation via the
|
||||||
|
# environment rather than `diff.external`, which would also change what
|
||||||
|
# `git show` and `git log -p --ext-diff` emit for every caller.
|
||||||
|
# Takes the same arguments as `git diff`: `git dft HEAD~3 -- file`.
|
||||||
|
dft = "!GIT_EXTERNAL_DIFF=difft git diff";
|
||||||
};
|
};
|
||||||
|
|
||||||
# SSH signing, key from the registry. mkDefault so a host lacking the key
|
# SSH signing, key from the registry. mkDefault so a host lacking the key
|
||||||
@@ -99,6 +104,14 @@ in
|
|||||||
enableGitIntegration = true;
|
enableGitIntegration = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# difftastic backs the `dft` alias above. git.enable stays off on purpose:
|
||||||
|
# the module's git integration sets `diff.external`, which would displace
|
||||||
|
# delta as the diff renderer everywhere instead of only where asked.
|
||||||
|
programs.difftastic = {
|
||||||
|
enable = true;
|
||||||
|
git.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
# lazygit: TUI for staging/rebasing, themed to Catppuccin Mocha to match.
|
# lazygit: TUI for staging/rebasing, themed to Catppuccin Mocha to match.
|
||||||
programs.lazygit = {
|
programs.lazygit = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
# Headless Secret Service (org.freedesktop.secrets) on the user session bus,
|
||||||
|
# for CLI tools that keep credentials in the system keychain rather than in a
|
||||||
|
# config file of their own.
|
||||||
|
#
|
||||||
|
# Current consumer: gcx, the Grafana Cloud CLI (users/emmathorpe/work.nix). gcx
|
||||||
|
# stores its OAuth access and refresh tokens in the keychain unconditionally --
|
||||||
|
# its config file holds only opaque `keychain:gcx:v2:...` handles -- and offers
|
||||||
|
# no plaintext fallback (there is no environment variable or config key to
|
||||||
|
# select a file-backed store). With nothing owning org.freedesktop.secrets,
|
||||||
|
# `gcx login` authenticates against Grafana successfully and then dies writing
|
||||||
|
# its config: "The name is not activatable".
|
||||||
|
#
|
||||||
|
# home-manager already ships services.gnome-keyring, but it does not fit a
|
||||||
|
# headless host on two counts:
|
||||||
|
#
|
||||||
|
# * it is WantedBy graphical-session-pre.target, which never activates
|
||||||
|
# without a desktop session, so the service would simply never start; and
|
||||||
|
# * it cannot unlock the login keyring (it passes no --unlock). An unlocked
|
||||||
|
# collection is mandatory: writing to a locked one blocks on a GUI prompter
|
||||||
|
# (gcr) that does not exist here, so the caller hangs rather than fails.
|
||||||
|
#
|
||||||
|
# Security posture, stated plainly: the login keyring is encrypted at rest, but
|
||||||
|
# the password unlocking it is readable by the same user on the same machine.
|
||||||
|
# That protects the tokens from something reading the keyring file directly; it
|
||||||
|
# protects them from nothing already running as this user. It is the same
|
||||||
|
# posture as the existing ~/.jenkinsenv and ~/.splunkenv token files, and it is
|
||||||
|
# the price of unattended operation -- systemd --user timers start with no
|
||||||
|
# human present to type a passphrase.
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.headlessSecretService;
|
||||||
|
|
||||||
|
# Where the generated unlock password lives when no external passwordFile is
|
||||||
|
# supplied. Under $XDG_DATA_HOME rather than the nix store, which is
|
||||||
|
# world-readable.
|
||||||
|
defaultPasswordFile = "${config.xdg.dataHome}/gnome-keyring/login-password";
|
||||||
|
|
||||||
|
passwordFile = if cfg.passwordFile != null then cfg.passwordFile else defaultPasswordFile;
|
||||||
|
|
||||||
|
keyringDaemon = pkgs.writeShellApplication {
|
||||||
|
name = "headless-secret-service";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.gnome-keyring
|
||||||
|
pkgs.coreutils
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
pwfile=${lib.escapeShellArg passwordFile}
|
||||||
|
|
||||||
|
if [ ! -s "$pwfile" ]; then
|
||||||
|
echo "headless-secret-service: no keyring password at $pwfile" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The daemon takes the whole of stdin as the password, so a trailing
|
||||||
|
# newline would silently become part of it. Strip it, so a hand-written or
|
||||||
|
# agenix-managed file unlocks the same keyring the generated one created.
|
||||||
|
#
|
||||||
|
# --components=secrets ONLY. The ssh component must stay off: it would
|
||||||
|
# claim SSH_AUTH_SOCK and displace services.ssh-agent, breaking SSH auth
|
||||||
|
# and signed commits. pkcs11 is not needed by anything here.
|
||||||
|
tr -d '\n' <"$pwfile" |
|
||||||
|
exec gnome-keyring-daemon --foreground --components=secrets --unlock
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.headlessSecretService = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
a headless gnome-keyring serving org.freedesktop.secrets on the user
|
||||||
|
session bus, with the login keyring unlocked at service start'';
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "/run/agenix/gnome-keyring-login";
|
||||||
|
description = ''
|
||||||
|
Path to a file holding the login keyring password. It is read at service
|
||||||
|
start, not at build time, so it need not exist when the system is built
|
||||||
|
-- this is the seam for an agenix-managed secret.
|
||||||
|
|
||||||
|
When null, a random 32-byte password is generated on first activation at
|
||||||
|
${defaultPasswordFile} (mode 0600) and reused from then on.
|
||||||
|
|
||||||
|
Pointing this at a different file after the login keyring already exists
|
||||||
|
does NOT re-key the keyring: the daemon will fail to unlock it. To
|
||||||
|
change the password, delete ~/.local/share/keyrings and re-authenticate
|
||||||
|
every tool that stored a secret there.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# secret-tool, for inspecting or repairing the keyring by hand when a stored
|
||||||
|
# credential misbehaves (`secret-tool search --all service gcx`).
|
||||||
|
home.packages = [ pkgs.libsecret ];
|
||||||
|
|
||||||
|
# Generate the unlock password on first activation. Guarded on us owning it:
|
||||||
|
# an externally supplied passwordFile is never created or written here.
|
||||||
|
home.activation = lib.mkIf (cfg.passwordFile == null) {
|
||||||
|
headlessSecretServicePassword = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||||
|
pwfile=${lib.escapeShellArg defaultPasswordFile}
|
||||||
|
if [ ! -s "$pwfile" ]; then
|
||||||
|
run mkdir -p "$(dirname "$pwfile")"
|
||||||
|
# Create the file empty at 0600 first, then fill it: the redirect
|
||||||
|
# keeps the existing mode, so the password is never briefly readable.
|
||||||
|
run install -m 600 /dev/null "$pwfile"
|
||||||
|
run ${pkgs.bash}/bin/sh -c \
|
||||||
|
'head -c 32 /dev/urandom | base64 -w0 > "$1"' sh "$pwfile"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.user.services.headless-secret-service = {
|
||||||
|
Unit = {
|
||||||
|
Description = "GNOME Keyring (Secret Service, headless)";
|
||||||
|
Documentation = "man:gnome-keyring-daemon(1)";
|
||||||
|
# The daemon claims its name on the user session bus.
|
||||||
|
Requires = [ "dbus.socket" ];
|
||||||
|
After = [ "dbus.socket" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = lib.getExe keyringDaemon;
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
# default.target, not graphical-session-pre.target: there is no graphical
|
||||||
|
# session on this host. With `linger` enabled (see the host table in
|
||||||
|
# flake.nix) default.target is reached at boot, so the keyring is also up
|
||||||
|
# for unattended systemd --user timers, not just interactive logins.
|
||||||
|
Install.WantedBy = [ "default.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -26,8 +26,32 @@ in
|
|||||||
pkgs.tea
|
pkgs.tea
|
||||||
pkgs.hyperfine # command-line benchmarking
|
pkgs.hyperfine # command-line benchmarking
|
||||||
pkgs.sd # saner find-and-replace than sed
|
pkgs.sd # saner find-and-replace than sed
|
||||||
|
|
||||||
|
# Replacements for the classic coreutils/BSD tools. Only the read-only ones
|
||||||
|
# are aliased over the original name (see shellAliases below); the rest keep
|
||||||
|
# their own name so nothing changes shape under a script's feet. The alias
|
||||||
|
# map and the flag-compatibility differences are documented in
|
||||||
|
# ../docs/shell.md, "Replacing the classics".
|
||||||
|
pkgs.dust # du: tree-shaped, size-sorted disk usage
|
||||||
|
pkgs.dysk # df: mounted filesystems (duf is unmaintained upstream)
|
||||||
|
pkgs.procs # ps: process list with tree, ports and container columns
|
||||||
|
pkgs.trash-cli # rm: XDG trash; `trash` / `trash-list` / `trash-restore`
|
||||||
|
pkgs.doggo # dig: DNS lookups
|
||||||
|
pkgs.xh # curl, for interactive HTTP poking (curl stays for scripts)
|
||||||
|
pkgs.ouch # tar/unzip/7z/zstd: one command for every archive format
|
||||||
|
pkgs.jnv # interactive jq filter builder (jq itself stays for scripts)
|
||||||
|
pkgs.hexyl # hex viewer
|
||||||
|
pkgs.fq # jq for binary formats
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# tldr pages: worked examples for a command, next to (not instead of) man.
|
||||||
|
# enableAutoUpdates defaults on and installs a tldr-update user timer, which
|
||||||
|
# keeps the page cache fresh -- without it `tldr` fails until first `--update`.
|
||||||
|
programs.tealdeer = {
|
||||||
|
enable = true;
|
||||||
|
settings.display.compact = true;
|
||||||
|
};
|
||||||
|
|
||||||
# Resource monitor, themed Catppuccin Mocha to match the rest of the desktop.
|
# Resource monitor, themed Catppuccin Mocha to match the rest of the desktop.
|
||||||
# btop does not bundle the theme, so vendor it from catppuccin/btop (pinned).
|
# btop does not bundle the theme, so vendor it from catppuccin/btop (pinned).
|
||||||
programs.btop = {
|
programs.btop = {
|
||||||
@@ -137,6 +161,26 @@ in
|
|||||||
la = "eza --icons --git -la";
|
la = "eza --icons --git -la";
|
||||||
lt = "eza --icons --git --tree";
|
lt = "eza --icons --git --tree";
|
||||||
cls = "clear";
|
cls = "clear";
|
||||||
|
|
||||||
|
# Shadow the classics with their modern equivalents. Only read-only
|
||||||
|
# commands are shadowed: a wrong flag costs a retype, never data. The
|
||||||
|
# flag vocabularies are NOT compatible (`du -sh`, `df -h`, `ps aux` all
|
||||||
|
# fail here) -- see ../docs/shell.md, "Replacing the classics".
|
||||||
|
#
|
||||||
|
# Blast radius is bounded by where these live: shellAliases lands in
|
||||||
|
# .zshrc, so only interactive zsh sees them. Scripts, `sudo <cmd>` and
|
||||||
|
# anything exec'd by another program still get the real binary. To reach
|
||||||
|
# the original in an interactive shell: `command du` or `\du`.
|
||||||
|
cat = "bat --paging=never"; # bat is already the PAGER/MANPAGER
|
||||||
|
du = "dust";
|
||||||
|
df = "dysk";
|
||||||
|
ps = "procs";
|
||||||
|
|
||||||
|
# `rm` is deliberately NOT aliased to trash-put. Retraining `rm` to mean
|
||||||
|
# "recoverable" is a habit that follows you onto machines where it does
|
||||||
|
# not (every remote host, every root shell, every container), and trash
|
||||||
|
# semantics break down anyway on a different filesystem or on
|
||||||
|
# root-owned paths. Type `trash` when you want a trash can.
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Living-room games machine: 4th-gen Core i7 (Haswell) on a UEFI board, wired to
|
||||||
|
# a television and driven from the sofa with a Bluetooth controller. Desktop host
|
||||||
|
# -- shared graphical/wired options live in ../../modules/desktop.nix; only
|
||||||
|
# host-specific settings are here. The games stack (Steam session, RetroArch,
|
||||||
|
# controllers) is in ./gaming.nix and the GPU in ./nvidia.nix. Install notes:
|
||||||
|
# see ../../docs/hosts/console.md.
|
||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./hardware-configuration.nix
|
||||||
|
./nvidia.nix
|
||||||
|
./gaming.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
# Haswell: AVX2/FMA/BMI2, i.e. x86-64-v3. Above the fleet default (2), so this
|
||||||
|
# only records the fact -- no feature flag currently keys off level 3.
|
||||||
|
features.cpu.microarchLevel = 3;
|
||||||
|
|
||||||
|
# Ordinary PC UEFI firmware: systemd-boot, and NVRAM writes are safe here
|
||||||
|
# (unlike the Mac Pro's Apple EFI, which cannot be trusted with efibootmgr).
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
# The boot menu is unreadable from a sofa and unusable without a keyboard.
|
||||||
|
# Boot the default immediately; hold space at power-on to get the menu back.
|
||||||
|
boot.loader.timeout = 0;
|
||||||
|
# Bound the entry list so the ESP does not fill up with old generations.
|
||||||
|
boot.loader.systemd-boot.configurationLimit = 10;
|
||||||
|
|
||||||
|
networking.hostName = "Console-NixOS";
|
||||||
|
|
||||||
|
hardware.cpu.intel.updateMicrocode = true;
|
||||||
|
|
||||||
|
# See `man configuration.nix` / the stateVersion docs before changing.
|
||||||
|
system.stateVersion = "26.05";
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
# The games stack for the living-room machine: the Steam session that the TV
|
||||||
|
# boots into, RetroArch with its cores, Clone Hero, and the controller plumbing.
|
||||||
|
#
|
||||||
|
# Session model. greetd (from ../../modules/sway.nix, which enables it for
|
||||||
|
# ReGreet) gets an `initial_session`, so the machine autologins into the
|
||||||
|
# gamescope Steam session at boot -- no keyboard, no greeter, straight to Big
|
||||||
|
# Picture. Quitting Steam drops back to greetd's `default_session`, i.e. ReGreet,
|
||||||
|
# where the ordinary Sway session can be picked for keyboard-and-mouse work.
|
||||||
|
{ pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# The account the television autologins as. Must match the user declared for
|
||||||
|
# this host in the flake host table.
|
||||||
|
tvUser = "lyrathorpe";
|
||||||
|
|
||||||
|
# Games library root. Deliberately outside any home directory: content is
|
||||||
|
# bulky, is the thing most likely to move to its own disk, and is shared
|
||||||
|
# between Steam, RetroArch and Clone Hero rather than owned by one of them.
|
||||||
|
# Mounting a second drive at this path is the only change that needs.
|
||||||
|
gamesRoot = "/srv/games";
|
||||||
|
|
||||||
|
# One ROM directory per emulated system. Names follow the libretro/ES-DE
|
||||||
|
# convention so a scraper or a second frontend recognises them without
|
||||||
|
# renaming anything.
|
||||||
|
romSystems = [
|
||||||
|
"nes"
|
||||||
|
"snes"
|
||||||
|
"gb"
|
||||||
|
"gbc"
|
||||||
|
"gba"
|
||||||
|
"n64"
|
||||||
|
"nds"
|
||||||
|
"gc"
|
||||||
|
"wii"
|
||||||
|
"mastersystem"
|
||||||
|
"gamegear"
|
||||||
|
"megadrive"
|
||||||
|
"sega32x"
|
||||||
|
"segacd"
|
||||||
|
"saturn"
|
||||||
|
"dreamcast"
|
||||||
|
"psx"
|
||||||
|
"ps2"
|
||||||
|
"psp"
|
||||||
|
"arcade"
|
||||||
|
"dos"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Everything under the root that is not a ROM directory. RetroArch is pointed
|
||||||
|
# at these below; Steam and Clone Hero have to be told about theirs in their
|
||||||
|
# own UIs (see docs/hosts/console.md).
|
||||||
|
libraryDirs = [
|
||||||
|
"bios" # RetroArch system directory: BIOS and firmware images
|
||||||
|
"saves" # in-game saves
|
||||||
|
"states" # save states
|
||||||
|
"playlists"
|
||||||
|
"screenshots"
|
||||||
|
"thumbnails"
|
||||||
|
"steam" # add as a Steam library folder from the client
|
||||||
|
"clonehero/songs"
|
||||||
|
"clonehero/backgrounds"
|
||||||
|
];
|
||||||
|
|
||||||
|
# RetroArch and the cores this machine is expected to run. The wrapper already
|
||||||
|
# points RetroArch at the packaged assets, core info and joypad autoconfig
|
||||||
|
# profiles; `settings` here is merged on top of those.
|
||||||
|
retroarch = pkgs.retroarch-bare.wrapper {
|
||||||
|
cores = with pkgs.libretro; [
|
||||||
|
# Nintendo
|
||||||
|
nestopia # NES
|
||||||
|
snes9x # SNES
|
||||||
|
gambatte # Game Boy / Color
|
||||||
|
mgba # Game Boy Advance
|
||||||
|
mupen64plus # Nintendo 64
|
||||||
|
melonds # Nintendo DS
|
||||||
|
dolphin # GameCube / Wii
|
||||||
|
# Sega
|
||||||
|
genesis-plus-gx # Master System / Game Gear / Mega Drive
|
||||||
|
picodrive # 32X / Mega CD
|
||||||
|
beetle-saturn # Saturn
|
||||||
|
flycast # Dreamcast / NAOMI
|
||||||
|
# Sony
|
||||||
|
beetle-psx-hw # PlayStation, hardware renderer
|
||||||
|
pcsx2 # PlayStation 2 (LRPS2); needs a PS2 BIOS in RetroArch's system dir
|
||||||
|
ppsspp # PSP
|
||||||
|
# Arcade and PC
|
||||||
|
fbneo
|
||||||
|
mame2003-plus
|
||||||
|
dosbox-pure
|
||||||
|
];
|
||||||
|
settings = {
|
||||||
|
# Applied on every launch via --appendconfig, so these three are fixed
|
||||||
|
# policy rather than saved preferences: changing them in the UI will not
|
||||||
|
# stick. Everything else stays user-editable as usual.
|
||||||
|
#
|
||||||
|
# Ozone is the controller-navigable menu; the TV has no keyboard.
|
||||||
|
menu_driver = "ozone";
|
||||||
|
video_fullscreen = "true";
|
||||||
|
# L3+R3 opens the RetroArch menu from inside a running core
|
||||||
|
# (INPUT_COMBO_L3_R3). Without a pad combo there is no way to exit a game
|
||||||
|
# without a keyboard, and no retro system this box emulates has L3/R3 on
|
||||||
|
# its own controller, so the binding cannot collide with a game.
|
||||||
|
input_menu_toggle_gamepad_combo = "2";
|
||||||
|
|
||||||
|
# Point RetroArch at the shared library instead of scattering content and
|
||||||
|
# state through ~/.config/retroarch. Key names are RetroArch's own; an
|
||||||
|
# unrecognised key in an appended config is ignored silently, so they are
|
||||||
|
# worth keeping in step with upstream.
|
||||||
|
system_directory = "${gamesRoot}/bios";
|
||||||
|
savefile_directory = "${gamesRoot}/saves";
|
||||||
|
savestate_directory = "${gamesRoot}/states";
|
||||||
|
playlist_directory = "${gamesRoot}/playlists";
|
||||||
|
screenshot_directory = "${gamesRoot}/screenshots";
|
||||||
|
thumbnails_directory = "${gamesRoot}/thumbnails";
|
||||||
|
# Where the content browser opens, so loading a game is a couple of
|
||||||
|
# D-pad presses rather than a walk up from the filesystem root.
|
||||||
|
rgui_browser_directory = "${gamesRoot}/roms";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
programs.steam = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
# Registers the "Steam" wayland session (gamescope wrapping Steam in tenfoot
|
||||||
|
# mode) with the display manager and installs the steam-gamescope launcher.
|
||||||
|
gamescopeSession.enable = true;
|
||||||
|
gamescopeSession.env = {
|
||||||
|
# gamescope has to be pointed at NVIDIA's GBM implementation and GLX
|
||||||
|
# vendor explicitly; on the proprietary driver it otherwise fails to get a
|
||||||
|
# usable device and the session dies at startup.
|
||||||
|
GBM_BACKEND = "nvidia-drm";
|
||||||
|
__GLX_VENDOR_LIBRARY_NAME = "nvidia";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Remote Play and local network game transfers are the point of a TV box on
|
||||||
|
# the same LAN as a desktop; both need their ports open.
|
||||||
|
remotePlay.openFirewall = true;
|
||||||
|
localNetworkGameTransfers.openFirewall = true;
|
||||||
|
|
||||||
|
# Proton-GE, selectable per title in Steam's compatibility settings. Covers
|
||||||
|
# the titles where Valve's Proton lags on codecs and anti-cheat shims. The
|
||||||
|
# module puts its steamcompattool output on STEAM_EXTRA_COMPAT_TOOLS_PATHS,
|
||||||
|
# which is what makes it appear in the client's Proton version list.
|
||||||
|
extraCompatPackages = [ pkgs.proton-ge-bin ];
|
||||||
|
|
||||||
|
# Winetricks against a Proton prefix: the standard repair tool when a title
|
||||||
|
# needs a runtime (dotnet, vcrun, Media Foundation) that Proton does not ship.
|
||||||
|
protontricks.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Proton prerequisites beyond what programs.steam already arranges.
|
||||||
|
#
|
||||||
|
# Already covered by the steam module, recorded here so it is not re-litigated:
|
||||||
|
# hardware.graphics 32-bit (the lib32 NVIDIA userspace Proton's 32-bit prefixes
|
||||||
|
# need), Steam's udev rules, 32-bit PipeWire, and the system fonts Wine renders
|
||||||
|
# with (Liberation and DejaVu arrive with fonts.enableDefaultPackages).
|
||||||
|
# vm.max_map_count is 1048576 in the nixpkgs default sysctls, which is above
|
||||||
|
# what DX12/Unreal titles need -- no override required.
|
||||||
|
#
|
||||||
|
# What is not covered: esync opens one eventfd per Wine sync object and runs
|
||||||
|
# out against systemd's default 524288 hard limit in the heaviest titles.
|
||||||
|
# Raise the hard limit only; the soft limit stays at the default, because
|
||||||
|
# lifting that breaks select()-based programs elsewhere on the system.
|
||||||
|
systemd.settings.Manager.DefaultLimitNOFILE = "1024:1048576";
|
||||||
|
|
||||||
|
# capSysNice lets gamescope raise its own scheduling priority, which is what
|
||||||
|
# keeps the compositor smooth while a game saturates the GPU. It installs
|
||||||
|
# gamescope as a setcap wrapper instead of a plain systemPackages entry;
|
||||||
|
# /run/wrappers/bin precedes the system profile on PATH, so steam-gamescope
|
||||||
|
# still resolves it.
|
||||||
|
programs.gamescope = {
|
||||||
|
enable = true;
|
||||||
|
capSysNice = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Applies the performance CPU governor (and drops it again) around games that
|
||||||
|
# ask for it; Steam's Proton builds and most native titles do.
|
||||||
|
programs.gamemode.enable = true;
|
||||||
|
|
||||||
|
# Autologin into the Steam session. The launcher is not exposed as a package
|
||||||
|
# by the steam module -- it is built inside it and added to
|
||||||
|
# environment.systemPackages -- so reference it through the system profile.
|
||||||
|
# greetd's `restart` option defaults to false once initial_session is set,
|
||||||
|
# which is what stops a logout from looping straight back into autologin.
|
||||||
|
services.greetd.settings.initial_session = {
|
||||||
|
command = "/run/current-system/sw/bin/steam-gamescope";
|
||||||
|
user = tvUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Controllers.
|
||||||
|
#
|
||||||
|
# Xbox One/Series pads over Bluetooth need xpadneo: the in-kernel xpad driver
|
||||||
|
# does not handle them well over BT (wrong button mapping, no rumble). The
|
||||||
|
# module turns on bluez itself; powerOnBoot is set below so the adapter is up
|
||||||
|
# before the Steam session starts and a pad can reconnect unattended.
|
||||||
|
#
|
||||||
|
# Everything else is in-kernel and needs no configuration: wired Xbox 360 pads
|
||||||
|
# (and the 360 wireless receiver) via xpad, DualSense/DualShock 4 via
|
||||||
|
# hid-playstation over USB and Bluetooth, and Clone Hero guitars as plain USB
|
||||||
|
# HID gamepads. xpadneo does not contend with xpad -- it binds Bluetooth HID
|
||||||
|
# devices, and the 360 pad is not HID-compliant. hardware.xone is deliberately
|
||||||
|
# left off: it blacklists xpad, which would break the 360 pads.
|
||||||
|
#
|
||||||
|
# hidraw access for the PlayStation pads (LED, battery, dualsensectl) comes
|
||||||
|
# from Steam's udev rules, which programs.steam enables via
|
||||||
|
# hardware.steam-hardware.
|
||||||
|
hardware.xpadneo.enable = true;
|
||||||
|
hardware.bluetooth = {
|
||||||
|
enable = true;
|
||||||
|
powerOnBoot = true;
|
||||||
|
# Battery level reporting for Bluetooth gamepads is still behind bluez's
|
||||||
|
# experimental flag.
|
||||||
|
settings.General.Experimental = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# The games library, created at boot so the directories exist before anything
|
||||||
|
# tries to write into them. Mode 2775 is setgid: the owning group is carried
|
||||||
|
# onto anything created inside, so a second account (or an rsync from another
|
||||||
|
# machine) does not end up with files the TV user cannot write. Directories
|
||||||
|
# are created if missing and otherwise left alone -- nothing here removes or
|
||||||
|
# rewrites content.
|
||||||
|
systemd.tmpfiles.rules =
|
||||||
|
let
|
||||||
|
dir = path: "d ${path} 2775 ${tvUser} users -";
|
||||||
|
in
|
||||||
|
[
|
||||||
|
(dir gamesRoot)
|
||||||
|
(dir "${gamesRoot}/roms")
|
||||||
|
]
|
||||||
|
++ map (system: dir "${gamesRoot}/roms/${system}") romSystems
|
||||||
|
++ map (sub: dir "${gamesRoot}/${sub}") libraryDirs;
|
||||||
|
|
||||||
|
# 32-bit ALSA for the older native titles that talk to ALSA directly rather
|
||||||
|
# than through the PulseAudio shim; programs.steam derives
|
||||||
|
# pipewire.alsa.support32Bit from this. PipeWire itself and the Pulse shim
|
||||||
|
# come from ../../modules/workstation.nix.
|
||||||
|
services.pipewire.alsa.enable = true;
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
retroarch
|
||||||
|
pkgs.clonehero
|
||||||
|
pkgs.dualsensectl # DualSense LED/battery/mic control from the shell
|
||||||
|
pkgs.mangohud # FPS/frametime overlay; use `mangohud %command%` in Steam
|
||||||
|
pkgs.vulkan-tools # vulkaninfo, for checking the 32/64-bit ICDs Proton needs
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# PLACEHOLDER -- not generated by nixos-generate-config.
|
||||||
|
#
|
||||||
|
# This host has not been installed yet, so there is no real hardware scan to
|
||||||
|
# commit. The values below are the conventional defaults for a Haswell UEFI
|
||||||
|
# desktop and assume the install labels its partitions `nixos` (root, ext4) and
|
||||||
|
# `BOOT` (ESP, vfat) -- see docs/hosts/console.md. They exist so the flake
|
||||||
|
# evaluates in CI; they are not a description of the actual machine.
|
||||||
|
#
|
||||||
|
# Replace this whole file with the output of `nixos-generate-config` run on the
|
||||||
|
# machine, and commit that. If the labels do not match, the boot fails loudly on
|
||||||
|
# a missing device rather than touching the wrong disk.
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"xhci_pci"
|
||||||
|
"ehci_pci"
|
||||||
|
"ahci"
|
||||||
|
"nvme"
|
||||||
|
"usb_storage"
|
||||||
|
"usbhid"
|
||||||
|
"sd_mod"
|
||||||
|
"sr_mod"
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
fileSystems."/" = {
|
||||||
|
device = "/dev/disk/by-label/nixos";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" = {
|
||||||
|
device = "/dev/disk/by-label/BOOT";
|
||||||
|
fsType = "vfat";
|
||||||
|
options = [
|
||||||
|
"fmask=0022"
|
||||||
|
"dmask=0022"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
swapDevices = [ ];
|
||||||
|
|
||||||
|
networking.useDHCP = lib.mkDefault true;
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# NVIDIA GeForce GTX 1070 8 GB (Pascal, GP104): proprietary driver for the
|
||||||
|
# gamescope Steam session and the Sway desktop.
|
||||||
|
#
|
||||||
|
# Driver branch: 580 (nvidiaPackages.legacy_580), NOT the nixpkgs default
|
||||||
|
# (`production`, currently 595.x). 580 is the last branch that supports
|
||||||
|
# Maxwell/Pascal/Volta -- NVIDIA keeps it as an LTS branch to Aug 2028 -- and a
|
||||||
|
# newer branch simply will not drive this card. Same constraint as the Mac Pro's
|
||||||
|
# Quadro P400; see hosts/MacPro31/nvidia.nix.
|
||||||
|
#
|
||||||
|
# The driver is unfree, so it is not in the binary cache: the kernel module is
|
||||||
|
# compiled locally on every kernel bump.
|
||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# Selects the proprietary driver; the module blacklists nouveau/nvidiafb and
|
||||||
|
# loads nvidia-uvm via a modprobe softdep. Naming is historical -- this option
|
||||||
|
# drives the kernel/driver choice on Wayland hosts too, which is why it is set
|
||||||
|
# on a machine that runs no X server.
|
||||||
|
services.xserver.videoDrivers = [ "nvidia" ];
|
||||||
|
|
||||||
|
hardware.nvidia = {
|
||||||
|
package = config.boot.kernelPackages.nvidiaPackages.legacy_580;
|
||||||
|
# Required for Wayland: sets nvidia-drm.modeset=1 (and fbdev=1), without
|
||||||
|
# which neither gamescope nor wlroots gets a usable GBM device and both the
|
||||||
|
# Steam session and Sway fail to start.
|
||||||
|
modesetting.enable = true;
|
||||||
|
# The open kernel modules need Turing or later; Pascal must use the closed
|
||||||
|
# ones. Explicit because the option has no default on driver >= 560.
|
||||||
|
open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# The NVIDIA module only puts these in boot.kernelModules when
|
||||||
|
# services.xserver.enable is true, which is false on this Wayland-only host --
|
||||||
|
# so load them explicitly rather than relying on udev modalias autoloading.
|
||||||
|
# nvidia_uvm is deliberately absent: the module's modprobe softdep pulls it in
|
||||||
|
# after the GPU device exists, which is the supported ordering.
|
||||||
|
boot.kernelModules = [
|
||||||
|
"nvidia"
|
||||||
|
"nvidia_modeset"
|
||||||
|
"nvidia_drm"
|
||||||
|
];
|
||||||
|
|
||||||
|
# wlroots refuses the proprietary NVIDIA driver unless told to proceed. The
|
||||||
|
# greeter's compositor (cage) and gamescope have no such check; only Sway
|
||||||
|
# needs the flag, which the module bakes into the wrapper the session's
|
||||||
|
# .desktop file runs.
|
||||||
|
programs.sway.extraOptions = [ "--unsupported-gpu" ];
|
||||||
|
|
||||||
|
# 32-bit driver libraries for 32-bit Steam titles and Proton's 32-bit
|
||||||
|
# prefixes: hardware.graphics.enable32Bit pulls in the matching lib32 NVIDIA
|
||||||
|
# userspace. programs.steam (./gaming.nix) sets it too; stated here as well so
|
||||||
|
# the GPU's 32-bit story lives with the rest of the GPU config.
|
||||||
|
hardware.graphics.enable32Bit = true;
|
||||||
|
}
|
||||||
@@ -98,6 +98,7 @@
|
|||||||
"lld@21"
|
"lld@21"
|
||||||
"python@3.14"
|
"python@3.14"
|
||||||
"dosbox-staging"
|
"dosbox-staging"
|
||||||
|
"mole"
|
||||||
];
|
];
|
||||||
# GUI applications. macOS app bundles are managed as casks; nixpkgs darwin
|
# GUI applications. macOS app bundles are managed as casks; nixpkgs darwin
|
||||||
# GUI support is unreliable, so these stay on brew for continuity.
|
# GUI support is unreliable, so these stay on brew for continuity.
|
||||||
@@ -111,6 +112,7 @@
|
|||||||
"bitwarden"
|
"bitwarden"
|
||||||
"citrix-workspace"
|
"citrix-workspace"
|
||||||
"curseforge"
|
"curseforge"
|
||||||
|
"darktable"
|
||||||
"discord"
|
"discord"
|
||||||
"firefox"
|
"firefox"
|
||||||
"freecad"
|
"freecad"
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
# Mac Pro 3,1 (Early 2008) — install notes
|
|
||||||
|
|
||||||
Flake host: `lyrathorpe-macpro31`. Desktop (`portable = false`, imports
|
|
||||||
`../../modules/desktop.nix`). Files: `configuration.nix`,
|
|
||||||
`hardware-configuration.nix`.
|
|
||||||
|
|
||||||
## Hardware configuration
|
|
||||||
|
|
||||||
`hardware-configuration.nix` here is the real config generated by
|
|
||||||
`nixos-generate-config` on the machine. Root is an **LVM** logical volume
|
|
||||||
(`/dev/mapper/MacPro-Root`, ext4); the ESP (vfat) and swap are referenced by
|
|
||||||
UUID. The initrd carries `dm-snapshot` for the LVM root. Regenerate and commit
|
|
||||||
if the disk layout changes.
|
|
||||||
|
|
||||||
## Bootloader
|
|
||||||
|
|
||||||
The Mac Pro 3,1 has **64-bit EFI**, so it uses **systemd-boot** (no GRUB/CSM
|
|
||||||
shim). `canTouchEfiVariables = false` because Apple's firmware does not reliably
|
|
||||||
accept `efibootmgr` NVRAM writes.
|
|
||||||
|
|
||||||
Apple-EFI quirk: if the firmware boot picker does not show NixOS after install,
|
|
||||||
either
|
|
||||||
|
|
||||||
- uncomment `boot.loader.efi.efiInstallAsRemovable = true;` in
|
|
||||||
`configuration.nix` (installs the fallback `\EFI\BOOT\BOOTX64.EFI`), and/or
|
|
||||||
- "bless" the ESP from macOS.
|
|
||||||
|
|
||||||
Partition the disk GPT with an ESP (vfat).
|
|
||||||
|
|
||||||
## Graphics
|
|
||||||
|
|
||||||
The stock card varies between units — **ATI Radeon HD 2600 XT** or **NVIDIA
|
|
||||||
GeForce 8800 GT**. No proprietary driver is hardcoded; Sway relies on in-tree KMS:
|
|
||||||
|
|
||||||
- ATI Radeon HD 2600 XT → `radeon` (or `amdgpu`) KMS
|
|
||||||
- NVIDIA GeForce 8800 GT → `nouveau` KMS
|
|
||||||
|
|
||||||
These come up automatically. If a card needs forcing, set
|
|
||||||
`services.xserver.videoDrivers` and/or add the module to
|
|
||||||
`boot.initrd.kernelModules` for early KMS (see the comment in
|
|
||||||
`configuration.nix`).
|
|
||||||
|
|
||||||
## Networking
|
|
||||||
|
|
||||||
Wired Ethernet via NetworkManager (from `desktop.nix`) — the Mac Pro has two
|
|
||||||
gigabit ports.
|
|
||||||
|
|
||||||
## Login
|
|
||||||
|
|
||||||
Graphical login via a Wayland greeter — `greetd` running ReGreet inside the
|
|
||||||
`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
|
|
||||||
authenticate. Requires working KMS (radeon/nouveau — see Graphics).
|
|
||||||
|
|
||||||
## Apply
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo nixos-rebuild switch --flake .#lyrathorpe-macpro31
|
|
||||||
```
|
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
# Apple Mac Pro 3,1 (Early 2008, dual Xeon Harpertown, x86_64). Desktop host:
|
# Apple Mac Pro 3,1 (Early 2008, dual Xeon Harpertown, x86_64). Desktop host:
|
||||||
# shared graphical/wired options live in ../../modules/desktop.nix; only
|
# shared graphical/wired options live in ../../modules/desktop.nix; only
|
||||||
# host-specific settings are here. Install notes (EFI booting, GPU, partitions):
|
# host-specific settings are here. Install notes (EFI booting, GPU, partitions):
|
||||||
# see ./README.md.
|
# see ../../docs/hosts/macpro31.md.
|
||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
|
./nvidia.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Dual quad-core Xeon (Harpertown/Penryn): SSE4.1 but no SSE4.2 or POPCNT,
|
||||||
|
# i.e. x86-64-v1. Declaring it here switches off the fleet flags that need a
|
||||||
|
# newer CPU -- currently features.claudeCode (see ../../modules/features.nix).
|
||||||
|
features.cpu.microarchLevel = 1;
|
||||||
|
|
||||||
# The Mac Pro 3,1 has 64-bit EFI (confirmed by the owner), so boot via
|
# The Mac Pro 3,1 has 64-bit EFI (confirmed by the owner), so boot via
|
||||||
# systemd-boot like the MBP -- no GRUB/BIOS shim needed.
|
# systemd-boot like the MBP -- no GRUB/BIOS shim needed.
|
||||||
boot.loader.systemd-boot.enable = true;
|
boot.loader.systemd-boot.enable = true;
|
||||||
@@ -33,17 +39,9 @@
|
|||||||
# enabled in workstation.nix.
|
# enabled in workstation.nix.
|
||||||
hardware.cpu.intel.updateMicrocode = true;
|
hardware.cpu.intel.updateMicrocode = true;
|
||||||
|
|
||||||
# GPU note: the stock card varies between units -- ATI Radeon HD 2600 XT or
|
# GPU: the stock card (ATI Radeon HD 2600 XT / NVIDIA GeForce 8800 GT) has
|
||||||
# NVIDIA GeForce 8800 GT. Sway needs a working KMS/modesetting driver; do NOT
|
# been replaced with an NVIDIA Quadro P400. Driver, Wayland quirks and
|
||||||
# install a proprietary blob here. Depending on the installed card, rely on
|
# GPU-enabled Docker live in ./nvidia.nix.
|
||||||
# the open kernel driver:
|
|
||||||
# - 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 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.
|
|
||||||
|
|
||||||
# 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";
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# NVIDIA Quadro P400 (Pascal, GP108) on the Mac Pro 3,1: proprietary driver for
|
||||||
|
# the Sway desktop, plus Docker with GPU/CUDA access for containers.
|
||||||
|
#
|
||||||
|
# Driver branch: 580 (nvidiaPackages.legacy_580), NOT the nixpkgs default
|
||||||
|
# (`production`, currently 595.x). 580 is the last branch that supports
|
||||||
|
# Maxwell/Pascal/Volta -- NVIDIA keeps it as an LTS branch to Aug 2028 -- and a
|
||||||
|
# newer branch simply will not drive this card.
|
||||||
|
#
|
||||||
|
# The driver is unfree, so it is not in the binary cache: the kernel module is
|
||||||
|
# compiled locally. On this machine's 2008 Xeons expect the first rebuild after
|
||||||
|
# a kernel bump to take a long while.
|
||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# Selects the proprietary driver; the module blacklists nouveau/nvidiafb and
|
||||||
|
# loads nvidia-uvm (needed by CUDA) via modprobe softdep. Naming is historical
|
||||||
|
# -- this option drives the kernel/driver choice on Wayland hosts too, which
|
||||||
|
# is why it is set on a machine that runs no X server.
|
||||||
|
services.xserver.videoDrivers = [ "nvidia" ];
|
||||||
|
|
||||||
|
hardware.nvidia = {
|
||||||
|
package = config.boot.kernelPackages.nvidiaPackages.legacy_580;
|
||||||
|
# Required for Wayland: sets nvidia-drm.modeset=1 (and fbdev=1), without
|
||||||
|
# which wlroots gets no GBM device and Sway/cage fail to start.
|
||||||
|
modesetting.enable = true;
|
||||||
|
# The open kernel modules need Turing or later; Pascal must use the closed
|
||||||
|
# ones. Explicit because the option has no default on driver >= 560.
|
||||||
|
open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# The NVIDIA module only puts these in boot.kernelModules when
|
||||||
|
# services.xserver.enable is true, which is false on this Wayland-only host --
|
||||||
|
# so load them explicitly rather than relying on udev modalias autoloading.
|
||||||
|
# nvidia_uvm (needed by CUDA) is deliberately absent: the module's modprobe
|
||||||
|
# softdep pulls it in after the GPU device exists, which is the supported
|
||||||
|
# ordering.
|
||||||
|
boot.kernelModules = [
|
||||||
|
"nvidia"
|
||||||
|
"nvidia_modeset"
|
||||||
|
"nvidia_drm"
|
||||||
|
];
|
||||||
|
|
||||||
|
# wlroots refuses the proprietary NVIDIA driver unless told to proceed. The
|
||||||
|
# greeter's compositor (cage) has no such check; only Sway needs the flag,
|
||||||
|
# which the module bakes into the wrapper the session's .desktop file runs.
|
||||||
|
programs.sway.extraOptions = [ "--unsupported-gpu" ];
|
||||||
|
|
||||||
|
virtualisation.docker.enable = true;
|
||||||
|
|
||||||
|
# CDI-based GPU access for containers: generates /var/run/cdi specs from the
|
||||||
|
# host driver at boot and turns on Docker's CDI feature. Run GPU workloads
|
||||||
|
# with `docker run --device=nvidia.com/gpu=all ...`. The deprecated
|
||||||
|
# virtualisation.docker.enableNvidia runtime wrapper is deliberately not used.
|
||||||
|
hardware.nvidia-container-toolkit.enable = true;
|
||||||
|
|
||||||
|
# The generator needs a loaded kernel module: without one it aborts with
|
||||||
|
# "failed to initialize NVML: Driver Not Loaded". That is guaranteed after a
|
||||||
|
# kernel bump, where the rebuilt module cannot load until reboot -- and since
|
||||||
|
# the unit is requiredBy docker.service and wantedBy multi-user.target, the
|
||||||
|
# failure takes Docker down and makes `nixos-rebuild switch` exit non-zero.
|
||||||
|
# Skip the run instead when no driver is loaded; the toolkit's udev rule
|
||||||
|
# restarts the unit as soon as the nvidia device appears, so the CDI specs are
|
||||||
|
# still generated on the next boot.
|
||||||
|
systemd.services.nvidia-container-toolkit-cdi-generator.unitConfig.ConditionPathExists =
|
||||||
|
"/proc/driver/nvidia/version";
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# ./docker.nix (Docker host with a network socket) and ./reverse-proxy.nix
|
# ./docker.nix (Docker host with a network socket) and ./reverse-proxy.nix
|
||||||
# (native nginx). The raspberry-pi-5 nixos-hardware profile (kernel, firmware,
|
# (native nginx). The raspberry-pi-5 nixos-hardware profile (kernel, firmware,
|
||||||
# device tree) and key-only sshd (../../modules/ssh.nix) are layered on in the
|
# device tree) and key-only sshd (../../modules/ssh.nix) are layered on in the
|
||||||
# flake host table. Install notes: see ./README.md.
|
# flake host table. Install notes: see ../../docs/hosts/rpi5.md.
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# evaluates in CI before the Pi is provisioned. The machine will not boot from
|
# evaluates in CI before the Pi is provisioned. The machine will not boot from
|
||||||
# it as-is. On first install, regenerate this file on the device with
|
# it as-is. On first install, regenerate this file on the device with
|
||||||
# nixos-generate-config --root /mnt
|
# nixos-generate-config --root /mnt
|
||||||
# and replace this placeholder with the output (commit it). See ./README.md.
|
# and replace this placeholder with the output (commit it). See ../../docs/hosts/rpi5.md.
|
||||||
#
|
#
|
||||||
# Like every hardware-configuration.nix in this repo, this file is excluded from
|
# Like every hardware-configuration.nix in this repo, this file is excluded from
|
||||||
# the formatter and linters (see the pre-commit/treefmt excludes in flake.nix).
|
# the formatter and linters (see the pre-commit/treefmt excludes in flake.nix).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ThinkPad T400 (NixOS). Shared laptop options live in ../../modules/laptop.nix;
|
# ThinkPad T400 (NixOS). Shared laptop options live in ../../modules/laptop.nix;
|
||||||
# only host-specific settings are here. Install notes (boot variants, GPU,
|
# only host-specific settings are here. Install notes (boot variants, GPU,
|
||||||
# partitions): see ./README.md.
|
# partitions): see ../../docs/hosts/t400.md.
|
||||||
{ config, ... }:
|
{ config, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,22 @@
|
|||||||
# toolchains, language-server downloads) on every NixOS host, not just WSL.
|
# toolchains, language-server downloads) on every NixOS host, not just WSL.
|
||||||
programs.nix-ld.enable = true;
|
programs.nix-ld.enable = true;
|
||||||
|
|
||||||
|
# Memory-safe sudo. The two modules assert against being enabled together;
|
||||||
|
# this one sets `security.sudo.enable = false` via mkDefault, so it is a
|
||||||
|
# straight swap and not an addition.
|
||||||
|
#
|
||||||
|
# Safe here because this fleet only ever uses the stock policy -- wheel may
|
||||||
|
# run anything, with a password -- which sudo-rs implements completely. It
|
||||||
|
# does not cover the more exotic sudoers surface (host aliases, LDAP/SSSD
|
||||||
|
# sudoers, most `Defaults` settings, `sudoreplay`); adding any of those means
|
||||||
|
# going back to `security.sudo`.
|
||||||
|
#
|
||||||
|
# Recovery if a host ever refuses to escalate: get a root shell without sudo
|
||||||
|
# (`wsl -u root -d NixOS` on the WSL box, the console or a serial/HDMI login
|
||||||
|
# elsewhere) and roll back -- `nixos-rebuild switch --rollback`, or pick the
|
||||||
|
# previous generation from the boot menu.
|
||||||
|
security.sudo-rs.enable = true;
|
||||||
|
|
||||||
# Minimal system-level CLI available before the home-manager profile loads
|
# Minimal system-level CLI available before the home-manager profile loads
|
||||||
# (e.g. early boot / rescue). User-level tooling lives in home-manager.
|
# (e.g. early boot / rescue). User-level tooling lives in home-manager.
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
|
|||||||
+67
-2
@@ -6,7 +6,72 @@
|
|||||||
# headless host (e.g. the Pi) must be able to leave it at its default without
|
# headless host (e.g. the Pi) must be able to leave it at its default without
|
||||||
# pulling in modules/sway.nix. The implementation lives in modules/sway.nix,
|
# pulling in modules/sway.nix. The implementation lives in modules/sway.nix,
|
||||||
# gated on this flag.
|
# gated on this flag.
|
||||||
{ lib, ... }:
|
#
|
||||||
|
# The file also carries the host capability facts those flags derive from
|
||||||
|
# (features.cpu.*). features.claudeCode.enable is such a derived flag: it is
|
||||||
|
# computed from the declared CPU level here and read by home/claude.nix through
|
||||||
|
# home-manager's osConfig, so a machine that cannot run the tool never installs
|
||||||
|
# it, on any host, without per-host opt-outs.
|
||||||
{
|
{
|
||||||
options.features.swayDesktop.enable = lib.mkEnableOption "the Sway desktop";
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.features;
|
||||||
|
|
||||||
|
# Claude Code runs on Node, whose V8 build requires SSE4.2 and POPCNT -- the
|
||||||
|
# x86-64-v2 feature set. On an older x86_64 CPU it does not run (illegal
|
||||||
|
# instruction), so it must not be installed there.
|
||||||
|
claudeCodeMinLevel = 2;
|
||||||
|
claudeCodeSupported =
|
||||||
|
!pkgs.stdenv.hostPlatform.isx86_64 || cfg.cpu.microarchLevel >= claudeCodeMinLevel;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.features = {
|
||||||
|
swayDesktop.enable = lib.mkEnableOption "the Sway desktop";
|
||||||
|
|
||||||
|
cpu.microarchLevel = lib.mkOption {
|
||||||
|
type = lib.types.ints.between 1 4;
|
||||||
|
default = 2;
|
||||||
|
example = 1;
|
||||||
|
description = ''
|
||||||
|
The x86-64 psABI microarchitecture level the host CPU implements:
|
||||||
|
1 = the original baseline, 2 = SSE4.2/POPCNT (Nehalem, 2008+),
|
||||||
|
3 = AVX2, 4 = AVX-512.
|
||||||
|
|
||||||
|
Nix cannot detect this (evaluation is pure and hosts are often built
|
||||||
|
elsewhere), so a machine older than the default declares its own level
|
||||||
|
and the flags below derive from it. Ignored on non-x86_64 hosts.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
claudeCode.enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = claudeCodeSupported;
|
||||||
|
defaultText = lib.literalMD ''
|
||||||
|
`true`, unless the host declares an x86-64 microarchitecture level
|
||||||
|
below ${toString claudeCodeMinLevel}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Whether to install Claude Code in this host's home-manager profiles
|
||||||
|
(implemented in home/claude.nix). Defaults off on CPUs below
|
||||||
|
x86-64-v${toString claudeCodeMinLevel}, which cannot run it; forcing it
|
||||||
|
on such a host is an evaluation error.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.claudeCode.enable -> claudeCodeSupported;
|
||||||
|
message = ''
|
||||||
|
features.claudeCode.enable is on, but this host declares
|
||||||
|
features.cpu.microarchLevel = ${toString cfg.cpu.microarchLevel}.
|
||||||
|
Claude Code needs x86-64-v${toString claudeCodeMinLevel}
|
||||||
|
(SSE4.2/POPCNT) and will not run on an older CPU.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,29 @@
|
|||||||
pkgs.tflint # Terraform linter (catches what terraformls won't)
|
pkgs.tflint # Terraform linter (catches what terraformls won't)
|
||||||
pkgs.terraform-docs # generate Terraform module docs
|
pkgs.terraform-docs # generate Terraform module docs
|
||||||
pkgs.yq-go # jq for YAML
|
pkgs.yq-go # jq for YAML
|
||||||
|
pkgs.gcx # Grafana Cloud CLI (dashboards, SLOs, synthetics, alerts)
|
||||||
];
|
];
|
||||||
services.ssh-agent.enable = true;
|
services.ssh-agent.enable = true;
|
||||||
|
|
||||||
|
# Colourised kubectl. enableAlias points `kubectl` at kubecolor, which parses
|
||||||
|
# the output of the real kubectl underneath and passes anything it does not
|
||||||
|
# recognise straight through, so every flag and subcommand still works. It
|
||||||
|
# drops colour automatically when stdout is not a terminal, leaving pipes into
|
||||||
|
# grep/jq/yq byte-identical. zsh integration reuses kubectl's own completions.
|
||||||
|
# Note the alias does apply to `KUBECONFIG=... kubectl ...`: zsh expands
|
||||||
|
# aliases after a variable-assignment prefix.
|
||||||
|
programs.kubecolor = {
|
||||||
|
enable = true;
|
||||||
|
enableAlias = true;
|
||||||
|
enableZshIntegration = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# gcx (above) keeps its OAuth tokens in the system keychain and has no
|
||||||
|
# plaintext fallback, so this WSL box needs something owning
|
||||||
|
# org.freedesktop.secrets. See home/secret-service.nix for why
|
||||||
|
# home-manager's services.gnome-keyring cannot be used on a headless host,
|
||||||
|
# and for the security trade-off of an auto-unlocked keyring.
|
||||||
|
services.headlessSecretService.enable = true;
|
||||||
home.shellAliases = {
|
home.shellAliases = {
|
||||||
docker = "/run/current-system/sw/bin/docker";
|
docker = "/run/current-system/sw/bin/docker";
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user