Compare commits

...

33 Commits

Author SHA1 Message Date
lyrathorpe 9ad8567bdf Merge pull request 'fix(wsl): remove non-existent interop options' (#43) from fix/wsl-interop-options into main
CI / flake (push) Successful in 3m31s
Reviewed-on: #43
2026-06-24 15:38:36 +01:00
Emma Thorpe bcabfd49bb fix(wsl): remove non-existent interop options
CI / flake (pull_request) Successful in 3m43s
The NixOS-WSL module's wslConf.interop submodule declares only `enabled`
and `appendWindowsPath`. `register` and `includePath` are not valid
wsl.conf interop keys, and the freeform INI type does not cover keys
nested under the already-declared `interop` group, so they were rejected
as unknown options. Remove them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:33:55 +01:00
lyrathorpe eef7203621 fix(darwin): emu2 isn't in brew, i installed it manually (#41)
CI / flake (push) Successful in 3m37s
Reviewed-on: #41
Co-authored-by: Lyra Thorpe <iam@emmathe.dev>
Co-committed-by: Lyra Thorpe <iam@emmathe.dev>
2026-06-23 16:40:10 +01:00
lyrathorpe 6064a5a1a7 feat(editor): add nvim-cmp completion keymaps (#40)
CI / flake (push) Successful in 3m39s
Closes #39.

nvim-cmp ships no default keymaps, so the completion menu (including the path source) appeared but nothing could navigate or accept it.

Bind the usual set in `plugins.cmp.settings.mapping`:

- `<C-n>` / `<C-p>` and `<Tab>` / `<S-Tab>` — select next/previous
- `<C-Space>` — open the menu
- `<C-e>` — abort
- `<CR>` — confirm with `select = false` (bare Enter stays a newline unless an entry is highlighted)

Documentation: `KEYBINDINGS.md` gains a completion-menu table under the Neovim section covering these keys, and the Neovim summary is reworded accordingly.

Verified by rendering the generated nvim config: the mappings emit as raw Lua (e.g. `["<CR>"] = cmp.mapping.confirm({ select = false })`), not quoted strings.

---------

Co-authored-by: Emma Thorpe <emma.thorpe@citrix.com>
Reviewed-on: #40
2026-06-23 16:33:12 +01:00
lyrathorpe df7747f876 Merge pull request 'feat(darwin): add apps i need' (#38) from feat/add-dosbox into main
CI / flake (push) Successful in 4m13s
Reviewed-on: #38
2026-06-23 16:26:30 +01:00
lyrathorpe 1e0485efde fix(darwin): emu2 not available via nixpkgs, use brew
CI / flake (pull_request) Successful in 3m55s
2026-06-23 16:22:20 +01:00
lyrathorpe fce75e9f4c feat(darwin): add apps i need
CI / flake (pull_request) Failing after 3m26s
dosbox and emu2, psion time
2026-06-23 16:16:07 +01:00
lyrathorpe e6e280cc73 Merge pull request 'chore(deps): update gitea actions to v7' (#34) from renovate/major-gitea-actions into main
CI / flake (push) Successful in 3m43s
Reviewed-on: #34
2026-06-22 16:28:10 +01:00
lyrathorpe 44245d16a2 Merge pull request 'fix(editor): use renamed catppuccin-mocha lualine theme' (#37) from fix/lualine-catppuccin-theme into main
CI / flake (push) Successful in 3m50s
Reviewed-on: #37
2026-06-22 15:39:09 +01:00
Emma Thorpe 123032aff9 fix(editor): use renamed catppuccin-mocha lualine theme
CI / flake (pull_request) Successful in 4m11s
catppuccin v2.0.0 renamed its lualine theme files; there is no longer a
plain "catppuccin" theme, only per-flavour files (catppuccin-mocha, etc.)
and catppuccin-nvim. The old name no longer resolved, so lualine fell back
to the auto theme and emitted a notice. Pin to catppuccin-mocha to match
the configured colorscheme flavour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:29:10 +01:00
renovate-bot 94b0b33338 Merge pull request 'chore(deps): lock file maintenance flake inputs' (#35) from renovate/lock-file-maintenance-flake-inputs into main
CI / flake (push) Successful in 3m55s
2026-06-22 00:07:10 +01:00
Renovate Bot d84b35c5ce chore(deps): lock file maintenance flake inputs
renovate/stability-days Updates have not met minimum release age requirement
CI / flake (pull_request) Successful in 4m30s
2026-06-21 23:02:33 +00:00
Renovate Bot 6f3801621f chore(deps): update gitea actions to v7
renovate/stability-days Updates have met minimum release age requirement
CI / flake (pull_request) Successful in 4m29s
2026-06-21 23:01:19 +00:00
lyrathorpe 1e49af53e7 Merge pull request 'feat: Raspberry Pi 5 host (Docker host + nginx reverse proxy)' (#32) from feat/rpi5-host into main
CI / flake (push) Successful in 3m50s
Reviewed-on: #32
2026-06-16 14:12:48 +01:00
Emma Thorpe efa9aa93da docs(rpi5): add install notes and update host table
CI / flake (pull_request) Successful in 3m47s
Add system/machine/RPi5/README.md (flash/boot, regenerate hardware-config,
Docker-socket security caveat and remote-client usage, how to add a
reverse-proxy vhost). Add lyrathorpe-rpi5 to the README host table and note
that the swayDesktop flag now lives in system/modules/features.nix so headless
hosts keep TTY login.
2026-06-16 13:32:11 +01:00
Emma Thorpe 277dfa4251 feat(flake): register lyrathorpe-rpi5 host
Add the aarch64-linux Raspberry Pi 5 host to the host table: the RPi5
machine config, the raspberry-pi-5 nixos-hardware profile, and key-only
sshd. Headless, so no swaywm.nix; base home modules only.
2026-06-16 13:31:16 +01:00
Emma Thorpe 3470751c3e refactor(modules): declare swayDesktop feature flag in a base module
lyrathorpe/user.nix reads features.swayDesktop.enable on every host, but the
option was declared inside lyrathorpe/swaywm.nix -- so a host that does not
import swaywm.nix (a headless server) would fail evaluation. Move the option
declaration to a new always-imported system/modules/features.nix and wire it
into baseModules; swaywm.nix keeps only its implementation (config) block.
Headless hosts can now omit swaywm.nix and the flag defaults to false.
2026-06-16 13:29:15 +01:00
Emma Thorpe b56641aaee feat(rpi5): add host configuration (boot, network, sshd)
Tie the RPi5 submodules together: import hardware-config, docker.nix and
reverse-proxy.nix; pin networking.hostName to the flake attr name so nh
resolves; use U-Boot/extlinux boot (raspberry-pi-5 profile supplies kernel +
firmware); enable key-only sshd and a default-deny firewall opening 22.
Headless -- swaywm.nix is not imported, so swayDesktop stays off.
2026-06-16 13:26:31 +01:00
Emma Thorpe 108f7b9528 feat(rpi5): add nginx reverse-proxy module
Enable nginx with the recommended proxy/TLS/optimisation/gzip settings and a
declarative virtualHosts table -- each proxied service is a Nix entry, so the
routing lives in-repo. Ships one HTTP-only example vhost; enableACME/forceSSL
are present but commented, to be flipped per-vhost once a DNS name and cert
exist. Opens 80 and 443.
2026-06-16 13:25:57 +01:00
Emma Thorpe 1cb8371775 feat(rpi5): add Docker host with LAN-restricted network socket
Enable Docker and expose the daemon over TCP 2375 by extending the systemd
docker.socket ListenStream (avoids the daemon.json hosts vs unit -H fd://
conflict). The port is not added to allowedTCPPorts; instead an nftables
rule accepts it only from the trusted LAN subnet. Plain 2375 is
root-equivalent, so the source restriction is the only safeguard -- mTLS on
2376 is the documented upgrade path.
2026-06-16 13:25:31 +01:00
Emma Thorpe 2fc39a5f15 feat(rpi5): add placeholder hardware-configuration
Committed so the lyrathorpe-rpi5 host evaluates in CI before the Pi is
provisioned. It is a placeholder, not a bootable config: on first install,
regenerate it on the device with nixos-generate-config and replace this file.
Excluded from formatters/linters by the existing hardware-configuration.nix
rules.
2026-06-16 13:25:02 +01:00
lyrathorpe 5f4fd8d74e Merge pull request 'Feat/extra needed apps' (#29) from feat/extra-needed-apps into main
CI / flake (push) Successful in 3m38s
Reviewed-on: #29
2026-06-16 11:56:13 +01:00
lyrathorpe d8c4f6bb0b Merge pull request 'fix(renovaterc.json): ensure lockfile updates auto merge' (#30) from fix/renovate-automerge into main
CI / flake (push) Successful in 3m36s
Reviewed-on: #30
2026-06-16 11:55:35 +01:00
Emma Thorpe 8c3b101a14 ci: always run the workflow on PRs, guard the heavy steps
CI / flake (pull_request) Successful in 3m24s
This job is a required status check on main. The workflow was path-filtered
to **.nix/flake.lock/ci.yaml, so a PR touching none of those (e.g. a
.renovaterc.json-only change) skipped the workflow entirely, leaving the
required check pending forever and making the PR unmergeable.

Run the workflow on every PR so the check is always reported, but keep a
'detect' step that diffs the PR against its base and runs nix flake check
and the per-host evals only when a .nix file, flake.lock, or this workflow
changed. When nothing Nix-relevant changed the heavy steps skip and the job
still passes, so the required check stays green-reportable without burning a
full evaluation on unrelated changes. Checkout uses fetch-depth: 0 so the
diff has the base history.
2026-06-16 11:51:18 +01:00
Emma Thorpe 2b69485107 feat(edaas): set hostName to emmathorpe-edaas
CI / flake (pull_request) Successful in 3m44s
The host inherited the stock NixOS default hostname 'nixos', which does not
match the flake's nixosConfigurations attribute 'emmathorpe-edaas'. nh
selects the configuration by the local hostname, so bare 'nh os switch'
failed to resolve. Pin the hostname to the attribute name so it resolves
without an explicit -H/--hostname flag.
2026-06-16 11:30:22 +01:00
Emma Thorpe 886ac4eb36 fix(git.nix): make personal signingkey a mkDefault
git.nix and work.nix both define user.signingkey. They used to hold the
same value, which types.str tolerates, but git.nix now sets the personal
key while work.nix sets the work key, so the two plain definitions
conflict on the EDaaS host.

Mark git.nix's signingkey as mkDefault, mirroring user.email: personal
hosts get the personal key, and work.nix's plain work-key definition wins
on the work host.
2026-06-16 11:27:17 +01:00
Emma Thorpe ffedf769a0 fix(shell.nix): let gh own its config.yml so auth login works
home-manager renders ~/.config/gh/config.yml as a read-only /nix/store
symlink whenever programs.gh is enabled (unconditionally, not gated on
settings). gh rewrites that file on 'gh auth login' and 'gh config set',
which then fail with a permission error.

Suppress the managed config.yml via xdg.configFile and drop the
settings.git_protocol declaration that created it; gh now owns the file.
The token lives in hosts.yml, which home-manager never manages. Set the
SSH protocol at runtime with 'gh config set git_protocol ssh'.
2026-06-16 11:21:51 +01:00
Emma Thorpe eec713e886 refactor(git.nix): drop redundant gh and duplicate tea packages
programs.gh.enable (in shell.nix) already installs gh, so the explicit
pkgs.gh was redundant. pkgs.tea was also declared in shell.nix; keep the
single declaration there and remove the duplicate here.
2026-06-16 11:12:48 +01:00
Emma Thorpe e995283363 feat(shell.nix): configure htop settings and meters
Flesh out programs.htop: tree view, sensible highlights, hidden kernel
threads, left CPU/Memory/Swap bar meters and right Tasks/LoadAverage/Uptime
text meters. color_scheme = 0 inherits the terminal's Catppuccin Mocha
palette, as htop has no custom-theme support of its own.

Drop the explicit pkgs.htop: programs.htop.enable already installs it.
2026-06-16 11:12:44 +01:00
Emma Thorpe a753355c0f fix(shell.nix): correct home.activation typo
The zcompdump reset was declared under home.actiVation (stray capital V),
an unknown option that fails module evaluation, so the activation script
never ran. Restore the correct home.activation attribute.
2026-06-16 11:12:38 +01:00
lyrathorpe e125296015 feat(shell.nix): add htop installation 2026-06-16 10:55:57 +01:00
lyrathorpe e0b3eb2393 feat(git.nix): add GitHub and Gitea CLI 2026-06-16 10:50:49 +01:00
lyrathorpe 35c3b08862 fix(renovaterc.json): ensure lockfile updates auto merge 2026-06-16 10:44:46 +01:00
18 changed files with 435 additions and 69 deletions
+43 -9
View File
@@ -2,27 +2,58 @@
# plus an explicit per-host evaluation pass for granular output.
name: CI
# Deliberately no `paths:` filter. This job is a required status check on main,
# and a path-filtered workflow is *skipped* (never runs) for PRs that touch no
# matching file -- which leaves the required check pending forever and blocks the
# merge (e.g. a .renovaterc.json-only change). So the workflow always runs and
# always reports. To avoid burning a full Nix evaluation on changes that can't
# affect it, the "detect" step below diffs the PR and the heavy steps run only
# when a .nix file, flake.lock, or this workflow changed; otherwise they skip and
# the job still passes. The required check is therefore always green-reportable.
on:
push:
branches: [main]
paths:
- "**.nix"
- "flake.lock"
- ".gitea/workflows/ci.yaml"
pull_request:
paths:
- "**.nix"
- "flake.lock"
- ".gitea/workflows/ci.yaml"
jobs:
flake:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
# Full history so the detect step can diff the PR against its base.
fetch-depth: 0
# Decide whether the Nix steps need to run. On a pull_request, diff the PR
# against its base and look for files that can affect the flake: any .nix,
# the lockfile, or this workflow. On any other event (push to main) always
# run. The job itself always succeeds, so the required status check is
# reported even when the heavy steps are skipped.
- name: Detect Nix-relevant changes
id: detect
run: |
set -euo pipefail
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "Event ${{ github.event_name }}: running full checks."
echo "run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
base='${{ github.event.pull_request.base.sha }}'
head='${{ github.event.pull_request.head.sha }}'
changed=$(git diff --name-only "$base...$head")
echo "Changed files:"
echo "$changed"
if echo "$changed" | grep -Eq '(\.nix$|^flake\.lock$|^\.gitea/workflows/ci\.yaml$)'; then
echo "Nix-relevant changes found: running checks."
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "No Nix-relevant changes: skipping checks (job still passes)."
echo "run=false" >> "$GITHUB_OUTPUT"
fi
- name: Install Nix
if: steps.detect.outputs.run == 'true'
uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31
with:
extra_nix_config: |
@@ -34,6 +65,7 @@ jobs:
# Runs every flake check: treefmt formatting, deadnix, statix, and the
# pre-commit hooks (so a --no-verify commit can't ship unlinted).
- name: Flake check
if: steps.detect.outputs.run == 'true'
run: nix flake check --print-build-logs
# Evaluate (not build) each host's toplevel so eval errors fail CI cheaply.
@@ -44,6 +76,7 @@ jobs:
# nixos/darwinConfigurations) rather than hard-coded, so adding or removing
# a host needs no change to this workflow.
- name: Evaluate NixOS host configurations
if: steps.detect.outputs.run == 'true'
run: |
set -euo pipefail
hosts=$(nix eval --raw '.#nixosConfigurations' \
@@ -56,6 +89,7 @@ jobs:
done
- name: Evaluate Darwin host configurations
if: steps.detect.outputs.run == 'true'
run: |
set -euo pipefail
hosts=$(nix eval --raw '.#darwinConfigurations' \
+2 -1
View File
@@ -6,7 +6,8 @@
},
"lockFileMaintenance": {
"enabled": true,
"schedule": ["before 6am on monday"]
"schedule": ["before 6am on monday"],
"automerge": true
},
"git-submodules": {
"enabled": false
+8 -5
View File
@@ -8,11 +8,12 @@ single flake.
Defined in the host table in [`flake.nix`](./flake.nix):
| Configuration | System | Machine |
| --------------------- | ---------------- | --------------------------------------------------------------------------- |
| --------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) |
| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](./system/machine/T400/README.md) |
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./system/machine/MacPro31/README.md) |
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) |
| `lyrathorpe-rpi5` | `aarch64-linux` | Raspberry Pi 5 headless server: Docker host + nginx reverse proxy — [install notes](./system/machine/RPi5/README.md) |
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) |
Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor),
@@ -41,11 +42,13 @@ darwin-rebuild switch --flake .#lyrathorpe-mac
## Login / greeter
Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running
ReGreet inside the `cage` kiosk compositor — configured centrally in
ReGreet inside the `cage` kiosk compositor — implemented in
[`lyrathorpe/swaywm.nix`](./lyrathorpe/swaywm.nix), gated on
`features.swayDesktop.enable`. The greeter is forced to Dvorak to match the
console and Sway session. Hosts with `features.swayDesktop.enable = false` (the
WSL work box) keep plain TTY login. The target account needs a password
`features.swayDesktop.enable` (the option is declared in
[`system/modules/features.nix`](./system/modules/features.nix), so headless hosts
can leave it off without importing `swaywm.nix`). The greeter is forced to Dvorak
to match the console and Sway session. Headless hosts (the WSL work box and the
Raspberry Pi server) keep plain TTY login. The target account needs a password
(`passwd <user>`) before it can log in.
## MacBook (Asahi) firmware
Generated
+27 -27
View File
@@ -25,11 +25,11 @@
},
"locked": {
"dir": "pkgs/firefox-addons",
"lastModified": 1781409739,
"narHash": "sha256-6dadOVlqPpjy0w4WuwvX+Qx0Kkaabm3tahMrCrr72Rg=",
"lastModified": 1782014564,
"narHash": "sha256-F/royQHyJAyKWKrV8AaG4Yf1yjzxa+PFk5xvTdvBrzk=",
"owner": "rycee",
"repo": "nur-expressions",
"rev": "ef56de5faccb3ac59d95aa31cce551ff72e35bed",
"rev": "d6668e34bbce788459883a1097bf0ee170f49c61",
"type": "gitlab"
},
"original": {
@@ -136,11 +136,11 @@
]
},
"locked": {
"lastModified": 1778507602,
"narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=",
"lastModified": 1781733627,
"narHash": "sha256-U3yTuGBnmXvXoQI3qkpfEDsn9RovQPAjN7ndRco+3u0=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a",
"rev": "3bbec39bc90eadfa031e6f3b77272f3f60803e39",
"type": "github"
},
"original": {
@@ -177,11 +177,11 @@
]
},
"locked": {
"lastModified": 1781319724,
"narHash": "sha256-ZGuxexEMo4Xv28KJ0dX/m/PHN4oZIOnxHZpNTyrvx4M=",
"lastModified": 1781981105,
"narHash": "sha256-/1nNBbA7PrSQpTc9Qazkhl4kIPg+TNl0CjxS3UQJKlw=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "8355f0a16b2dbb06a97959a918af5b239bbe05ae",
"rev": "7bfff44b465909f69a442701293bc0badcf476dc",
"type": "github"
},
"original": {
@@ -198,11 +198,11 @@
]
},
"locked": {
"lastModified": 1781190061,
"narHash": "sha256-QRMpLbsmlciMGv4yx75FUoIl54K02JbIX1tgdPHPw1s=",
"lastModified": 1781772065,
"narHash": "sha256-xIbRSwDB1GBAUsWsQZUjudGfAGQt3BOpsWaO/ugVa4w=",
"owner": "nix-darwin",
"repo": "nix-darwin",
"rev": "f2b3fdb347f91dfd344ad196666a0b0fe8aad05c",
"rev": "adda04f0bf4819575b1978c2f8d78401b3c2be12",
"type": "github"
},
"original": {
@@ -237,11 +237,11 @@
]
},
"locked": {
"lastModified": 1781422160,
"narHash": "sha256-W7S8O86bVrw2gaomEwkHStOzmPpnFIHQ6lS4Q2HffJ0=",
"lastModified": 1782030356,
"narHash": "sha256-h4WpMr455AfRub0FXBaon6Vcpe0waUyJ4GivIW6oyd4=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "854d04e2368fb2e9c328a36b3c0a04cd713bfae1",
"rev": "3017088b49efd404f78e3b104f553b97e4af786b",
"type": "github"
},
"original": {
@@ -258,11 +258,11 @@
]
},
"locked": {
"lastModified": 1781108254,
"narHash": "sha256-acCLKPvXbyLVd8Vui/6GuQp2JQ409riBOe00nr/qe+w=",
"lastModified": 1781520503,
"narHash": "sha256-XuqQQG1qRyc3o8ld937sDLQNx+QrGV852KJ0dNglJDg=",
"owner": "nix-community",
"repo": "nixos-apple-silicon",
"rev": "53ae7e036433331013bd7c03ae927da554482816",
"rev": "43043ad207529650f9fa68e1705f7cf9c08bfdeb",
"type": "github"
},
"original": {
@@ -278,11 +278,11 @@
]
},
"locked": {
"lastModified": 1781168557,
"narHash": "sha256-LOnLQ2tpYF9gqIDDr3+j3DbpJJr/QCH6zPRT2GzEUOE=",
"lastModified": 1781622756,
"narHash": "sha256-JrPh4M6S7aPsEE9tOENuZrxC6o2szSLlK+t4+nLke9s=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "6358ff76821101c178e3ab4919a62799bfe3652e",
"rev": "08018c72174a4df5657f8d94178ac69fb9c243e5",
"type": "github"
},
"original": {
@@ -330,11 +330,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1781074563,
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
"lastModified": 1781577229,
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
"type": "github"
},
"original": {
@@ -353,11 +353,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1781388570,
"narHash": "sha256-prn8ws642FWX4pEU3Rl6m5QlrJl5MjEMHGfII9Ho9t0=",
"lastModified": 1781971008,
"narHash": "sha256-T2u2RQZWKvD1J+TgcxjiJr8IymBr/PrUNeAGhMZFZU4=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "0e73e3cb254a806d344bd70d996ab77896d3e3a8",
"rev": "7afca458f064f166d3a9c98db3b41a984fe46492",
"type": "github"
},
"original": {
+17
View File
@@ -114,6 +114,7 @@
baseModules = [
./lyrathorpe/user.nix
./system/modules/common-nixos.nix
./system/modules/features.nix
commonModule
home-manager.nixosModules.home-manager
{
@@ -287,6 +288,22 @@
./lyrathorpe/home/work.nix
];
};
lyrathorpe-rpi5 = {
system = "aarch64-linux";
username = "lyrathorpe";
fullName = "Lyra Thorpe";
portable = false;
# Headless server: Docker host + nginx reverse proxy. No swaywm.nix
# (no desktop); the raspberry-pi-5 profile supplies kernel/firmware,
# ssh.nix adds key-only sshd.
modules = [
./system/machine/RPi5/configuration.nix
inputs.nixos-hardware.nixosModules.raspberry-pi-5
./system/modules/ssh.nix
];
homeModules = [ ./lyrathorpe/home ];
};
};
# Darwin host table — macOS machines built via mkDarwinHost. The shared
+16 -4
View File
@@ -188,10 +188,22 @@ across vim splits and tmux panes seamlessly. Everything else is stock vim, plus:
| `<leader>rn` | Rename symbol (LSP; `<leader>` is `Space`) |
| `<leader>ca` | Code action (LSP) |
LSP covers Nix, Lua, Python and Terraform (the work box adds C# and Helm);
completion (nvim-cmp) appears as you type. Files are formatted on save
(conform-nvim). `:Git` opens fugitive; gitsigns shows gutter signs. which-key
pops up after `<leader>` to show the rest.
### Completion menu (nvim-cmp)
Active only while the completion popup is open (it appears as you type, e.g.
file paths):
| Shortcut | Action |
| ----------------------- | ------------------------------------------------------------------ |
| `Tab` / `Shift`+`Tab` | Select next / previous item |
| `Ctrl`+`n` / `Ctrl`+`p` | Select next / previous item |
| `Ctrl`+`Space` | Open the completion menu |
| `Enter` | Confirm the highlighted item (no auto-select; otherwise a newline) |
| `Ctrl`+`e` | Dismiss the menu |
LSP covers Nix, Lua, Python and Terraform (the work box adds C# and Helm).
Files are formatted on save (conform-nvim). `:Git` opens fugitive; gitsigns
shows gutter signs. which-key pops up after `<leader>` to show the rest.
---
+13 -1
View File
@@ -89,6 +89,18 @@
enable = true;
autoEnableSources = true;
settings = {
# nvim-cmp ships no default keymaps; without these the menu shows but
# nothing accepts it. confirm uses select=false so a bare <CR> stays a
# newline unless an entry is explicitly highlighted.
mapping = {
"<C-n>" = "cmp.mapping.select_next_item()";
"<C-p>" = "cmp.mapping.select_prev_item()";
"<Tab>" = "cmp.mapping.select_next_item()";
"<S-Tab>" = "cmp.mapping.select_prev_item()";
"<CR>" = "cmp.mapping.confirm({ select = false })";
"<C-Space>" = "cmp.mapping.complete()";
"<C-e>" = "cmp.mapping.abort()";
};
snippet.expand = "function(args) require('luasnip').lsp_expand(args.body) end";
sources = [
{ name = "nvim_lsp"; }
@@ -109,7 +121,7 @@
trouble.enable = true; # project-wide diagnostics/quickfix list
lualine = {
enable = true;
settings.options.theme = "catppuccin";
settings.options.theme = "catppuccin-mocha";
};
comment.enable = true; # gc / gcc comment toggling
nvim-autopairs.enable = true;
+7 -5
View File
@@ -77,12 +77,14 @@ in
cc = "!cz commit";
};
# SSH commit signing on personal hosts too (the work module sets the same
# on the work host). mkDefault so a host without the key in its ssh-agent
# can override to false -- otherwise commits there would fail. Reuses the
# existing ssh key; a dedicated personal key can be swapped in later.
# SSH commit signing. This personal key is the default; the work module
# (work.nix) overrides it with the work key on the EDaaS host, the same way
# user.email is overridden -- so mkDefault here lets that plain definition
# win instead of conflicting. gpgsign is mkDefault too, so a host without
# the key in its ssh-agent can override it to false rather than fail every
# commit.
gpg.format = "ssh";
user.signingkey = "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAJMVgeRKnfX1G8coU3nAobI485aeUpGTMqH7+zbKI8o emma.thorpe@cloud.com";
user.signingkey = lib.mkDefault "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDxHvdMTOzpFWUFMtCP7C/4tIOUO3GIO2QPvaifSnWH lyrathorpe@Lyra-MBA";
commit.gpgsign = lib.mkDefault true;
tag.gpgsign = lib.mkDefault true;
};
+63 -6
View File
@@ -203,12 +203,15 @@ in
flake = "$HOME/code/nixfiles";
};
# GitHub CLI. Prefer SSH for any git operations it drives, matching the
# ssh-based remotes used elsewhere.
programs.gh = {
enable = true;
settings.git_protocol = "ssh";
};
# GitHub CLI. `programs.gh.settings` is deliberately unset: home-manager renders
# ~/.config/gh/config.yml as a read-only /nix/store symlink whenever the module
# is enabled, but gh must rewrite that file on `gh auth login` and `gh config
# set`, which then fail with a permission error. Suppress the managed config.yml
# (below) and let gh own it. The token lives in hosts.yml, which is never
# Nix-managed. Set the SSH protocol once at runtime: `gh config set git_protocol
# ssh` (it can't be declarative here without recreating the immutable file).
programs.gh.enable = true;
xdg.configFile."gh/config.yml".enable = lib.mkForce false;
programs.tmux = {
enable = true;
@@ -345,6 +348,60 @@ in
# enables this in the work module; both being true merges cleanly.
services.ssh-agent.enable = lib.mkIf pkgs.stdenv.hostPlatform.isLinux true;
# Classic process viewer (complements btop). htop has no custom-theme support
# -- only a handful of built-in color schemes -- so it can't be hex-themed like
# btop/bat/fzf. color_scheme = 0 (Default) draws from the terminal's ANSI
# palette, which is Catppuccin Mocha (foot/iTerm2), so it matches by deferring
# to the terminal rather than vendoring a theme.
programs.htop = {
enable = true;
settings = {
color_scheme = 0; # Default -> uses the terminal's Catppuccin palette
delay = 15; # refresh every 1.5s
cpu_count_from_one = 1;
show_cpu_frequency = 1;
show_cpu_usage = 1; # per-core usage shown in the CPU bars
highlight_base_name = 1; # highlight the program name within the path
highlight_megabytes = 1;
highlight_threads = 1;
hide_kernel_threads = 1;
show_program_path = 0; # show just the command, not the full path
tree_view = 1; # start in process-tree mode
tree_view_always_by_pid = 0;
account_guest_in_cpu_meter = 0;
fields = with config.lib.htop.fields; [
PID
USER
PRIORITY
NICE
M_SIZE
M_RESIDENT
M_SHARE
STATE
PERCENT_CPU
PERCENT_MEM
TIME
COMM
];
}
// (
with config.lib.htop;
leftMeters [
(bar "AllCPUs2")
(bar "Memory")
(bar "Swap")
]
)
// (
with config.lib.htop;
rightMeters [
(text "Tasks")
(text "LoadAverage")
(text "Uptime")
]
);
};
# Drop the zsh completion dump on every activation. A stale .zcompdump caches
# /nix/store paths to completion functions; once a rebuild or a manual GC
# removes them, compinit fails with "_git: function definition file not found"
+3 -3
View File
@@ -11,9 +11,9 @@ let
ctp = import ./catppuccin-mocha.nix;
in
{
options = {
features.swayDesktop.enable = lib.mkEnableOption "Enable Sway Desktop";
};
# The features.swayDesktop.enable option is declared in
# system/modules/features.nix (so headless hosts can read/set it without
# importing this module). This module only provides its implementation.
config = lib.mkIf cfg.enable {
programs.sway = {
enable = true;
+1
View File
@@ -97,6 +97,7 @@
"llvm@21"
"lld@21"
"python@3.14"
"dosbox-staging"
];
# GUI applications. macOS app bundles are managed as casks; nixpkgs darwin
# GUI support is unreliable, so these stay on brew for continuity.
+5 -2
View File
@@ -19,9 +19,7 @@
defaultUser = "emmathorpe";
wslConf.automount.root = "/mnt";
wslConf.interop.appendWindowsPath = true;
wslConf.interop.register = true;
wslConf.interop.enabled = true;
wslConf.interop.includePath = true;
wslConf.network.generateHosts = false;
startMenuLaunchers = true;
docker-desktop.enable = false;
@@ -43,6 +41,11 @@
autoPrune.enable = true;
};
# Match the flake's nixosConfigurations attribute name so `nh os switch`
# (which selects by the local hostname) resolves without an explicit
# -H/--hostname flag. The default would otherwise be the stock NixOS "nixos".
networking.hostName = "emmathorpe-edaas";
networking.resolvconf.enable = false;
# Drop the systemd-ssh-proxy Include from the generated /etc/ssh/ssh_config.
+68
View File
@@ -0,0 +1,68 @@
# Raspberry Pi 5 (`lyrathorpe-rpi5`)
Headless `aarch64-linux` server with two roles:
- **Docker host** — daemon exposed over the network (`docker.nix`).
- **nginx reverse proxy** — declarative `virtualHosts` (`reverse-proxy.nix`).
## Install
1. Flash a NixOS `aarch64` SD image (or USB) and boot the Pi. The
`raspberry-pi-5` profile from `nixos-hardware` (wired in the flake host table)
supplies the kernel, firmware and device tree; boot is U-Boot + extlinux.
2. Partition/mount the target, then **regenerate the hardware config on the
device** and replace the committed placeholder:
```sh
nixos-generate-config --root /mnt
# copy /mnt/etc/nixos/hardware-configuration.nix over
# system/machine/RPi5/hardware-configuration.nix in this repo, then commit
```
`hardware-configuration.nix` in this directory is a **placeholder** committed
only so the host evaluates in CI. The machine will not boot correctly until it
is replaced with the generated one.
3. Set the host name to match the flake attribute (already done in
`configuration.nix`: `lyrathorpe-rpi5`) and build:
```sh
sudo nixos-rebuild switch --flake .#lyrathorpe-rpi5
# or, once the hostname is live:
nh os switch
```
4. Give the login user a password (`passwd lyrathorpe`) and confirm the key in
`system/modules/ssh.nix` is the one you will connect with.
## Docker socket (security)
The daemon listens on **plain TCP `2375`, no TLS, no auth**. Access is
root-equivalent on this host. The only protection is the nftables rule in
`docker.nix`, which accepts `2375` **only** from the trusted LAN subnet
(`10.187.1.0/24` by default — change it to match your network). Do not widen
that subnet to anything untrusted.
From a LAN client:
```sh
export DOCKER_HOST=tcp://lyrathorpe-rpi5:2375
docker info
```
The secure upgrade path is mutual TLS on `2376` (`--tlsverify` with a CA and
client certs); it needs out-of-band cert provisioning and is intentionally not
wired here.
## Adding a reverse-proxy site
Each proxied service is a Nix entry in `reverse-proxy.nix`:
```nix
services.nginx.virtualHosts."app.example.lan" = {
# enableACME = true; forceSSL = true; # once a DNS name + cert exist
locations."/" = {
proxyPass = "http://127.0.0.1:8080"; # e.g. a local container
proxyWebsockets = true;
};
};
```
The example vhost is HTTP-only by design. Turn on `enableACME`/`forceSSL`
per-vhost once the host has a real DNS name and the ACME challenge can be met;
`443` is already open in the firewall.
+40
View File
@@ -0,0 +1,40 @@
# Raspberry Pi 5 (aarch64) headless server. Two roles, split into submodules:
# ./docker.nix (Docker host with a network socket) and ./reverse-proxy.nix
# (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
# flake host table. Install notes: see ./README.md.
{ ... }:
{
imports = [
./hardware-configuration.nix
./docker.nix
./reverse-proxy.nix
];
# Match the flake's nixosConfigurations attribute name so `nh os switch`
# (which selects by the local hostname) resolves without an explicit -H flag.
networking.hostName = "lyrathorpe-rpi5";
# Headless server: the Sway desktop is intentionally not set up. swaywm.nix is
# not imported and features.swayDesktop.enable defaults to false (declared in
# system/modules/features.nix), so this host keeps plain TTY/SSH login.
# Raspberry Pi boots via U-Boot + extlinux, not GRUB/systemd-boot. The
# raspberry-pi-5 nixos-hardware profile supplies the kernel, firmware and
# device tree.
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
# Remote administration. Key-only policy and the authorized key come from
# ../../modules/ssh.nix; here we just enable the daemon and open the port.
services.openssh.enable = true;
# Default-deny inbound. Open only SSH here; the Docker and nginx submodules
# open their own ports (Docker via a source-restricted nftables rule, nginx
# via 80/443). List-valued, so these merge with the submodule definitions.
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
# See `man configuration.nix` / the stateVersion docs before changing.
system.stateVersion = "26.05";
}
+34
View File
@@ -0,0 +1,34 @@
# Docker host with the daemon socket exposed over the network.
#
# SECURITY: the daemon listens on plain TCP 2375 with NO TLS and NO auth. Access
# to that port is root-equivalent on this host (the Docker API can mount the
# host filesystem and run privileged containers). The ONLY thing protecting it
# is the nftables rule below, which accepts 2375 solely from the trusted LAN
# subnet. Do not widen that subnet to anything you do not fully trust. The
# secure upgrade path is mutual TLS on 2376 (--tlsverify with client certs);
# that needs out-of-band cert provisioning and is intentionally not wired here.
{ ... }:
{
virtualisation.docker.enable = true;
# Expose the daemon over TCP by extending systemd socket activation rather than
# setting daemon.settings.hosts. The NixOS docker unit starts dockerd with
# `-H fd://` and takes its listeners from this socket; putting `hosts` in
# daemon.json as well would conflict with that and dockerd would refuse to
# start. Adding the TCP listener here keeps a single source of truth.
# The leading "" resets the unit's default (unix-socket-only) ListenStream list.
systemd.sockets.docker.socketConfig.ListenStream = [
""
"/run/docker.sock"
"0.0.0.0:2375"
];
# Source-restricted firewall rule for the Docker TCP port. 2375 is deliberately
# NOT added to networking.firewall.allowedTCPPorts (that would open it to every
# source); instead nftables accepts it only from the trusted subnet. Adjust the
# CIDR to match the LAN that should reach the Docker API.
networking.nftables.enable = true;
networking.firewall.extraInputRules = ''
ip saddr 10.187.1.0/24 tcp dport 2375 accept
'';
}
@@ -0,0 +1,31 @@
# PLACEHOLDER hardware configuration for the Raspberry Pi 5.
#
# This file is NOT the real generated config -- it exists only so the host
# 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
# nixos-generate-config --root /mnt
# and replace this placeholder with the output (commit it). See ./README.md.
#
# 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).
{ modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
nixpkgs.hostPlatform = "aarch64-linux";
# The Raspberry Pi 5 boots from an SD card / USB with a FAT firmware partition
# and an ext4 root. Labels match the conventional sd-image layout; the real
# generated config will use by-uuid device paths instead.
fileSystems."/" = {
device = "/dev/disk/by-label/NIXOS_SD";
fsType = "ext4";
};
fileSystems."/boot/firmware" = {
device = "/dev/disk/by-label/FIRMWARE";
fsType = "vfat";
};
swapDevices = [ ];
}
+39
View File
@@ -0,0 +1,39 @@
# Native nginx reverse proxy. The proxy configuration is declarative Nix:
# every proxied service is an entry under services.nginx.virtualHosts, so the
# whole routing table lives in this file and is built/version-controlled with
# the rest of the system.
#
# To add a proxied service, add another virtualHosts."<host>" entry following
# the example below. To serve it over HTTPS, uncomment enableACME + forceSSL on
# that vhost once it has a real DNS name and the ACME HTTP-01/DNS-01 challenge
# can be satisfied (see security.acme for the account/email and DNS settings).
{ ... }:
{
services.nginx = {
enable = true;
recommendedProxySettings = true; # sane proxy_set_header defaults (Host, X-Forwarded-*)
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
virtualHosts = {
# Example reverse-proxy vhost. Replace the name and upstream with a real
# service (e.g. a container published by the Docker host on this machine).
"example.lan" = {
# enableACME = true; # request a Let's Encrypt cert for this host
# forceSSL = true; # redirect HTTP -> HTTPS once the cert exists
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
proxyWebsockets = true; # forward Upgrade/Connection for WebSocket apps
};
};
};
};
# Public reverse-proxy ports. 443 is opened now so flipping a vhost to TLS
# needs no firewall change.
networking.firewall.allowedTCPPorts = [
80
443
];
}
+12
View File
@@ -0,0 +1,12 @@
# Feature-flag option declarations shared by every NixOS host (imported via
# baseModules in flake.nix). Declaring the flags here -- rather than inside the
# module that implements them -- means a host can read or set a flag without
# importing the (often large) implementation module. In particular,
# features.swayDesktop.enable is read by lyrathorpe/user.nix on every host, but a
# headless host (e.g. the Pi) must be able to leave it at its default without
# pulling in lyrathorpe/swaywm.nix. The implementation lives in swaywm.nix,
# gated on this flag.
{ lib, ... }:
{
options.features.swayDesktop.enable = lib.mkEnableOption "the Sway desktop";
}