Compare commits
38 Commits
a40558d35e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ad8567bdf | |||
| bcabfd49bb | |||
| eef7203621 | |||
| 6064a5a1a7 | |||
| df7747f876 | |||
| 1e0485efde | |||
| fce75e9f4c | |||
| e6e280cc73 | |||
| 44245d16a2 | |||
| 123032aff9 | |||
| 94b0b33338 | |||
| d84b35c5ce | |||
| 6f3801621f | |||
| 1e49af53e7 | |||
| efa9aa93da | |||
| 277dfa4251 | |||
| 3470751c3e | |||
| b56641aaee | |||
| 108f7b9528 | |||
| 1cb8371775 | |||
| 2fc39a5f15 | |||
| 5f4fd8d74e | |||
| d8c4f6bb0b | |||
| 8c3b101a14 | |||
| 2b69485107 | |||
| 886ac4eb36 | |||
| ffedf769a0 | |||
| eec713e886 | |||
| e995283363 | |||
| a753355c0f | |||
| e125296015 | |||
| e0b3eb2393 | |||
| 35c3b08862 | |||
| 6730efa3ce | |||
| fc459ddb1b | |||
| 052b95c00e | |||
| 783754bda2 | |||
| dc08522bab |
@@ -2,27 +2,58 @@
|
|||||||
# plus an explicit per-host evaluation pass for granular output.
|
# plus an explicit per-host evaluation pass for granular output.
|
||||||
name: CI
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
|
||||||
- "**.nix"
|
|
||||||
- "flake.lock"
|
|
||||||
- ".gitea/workflows/ci.yaml"
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- "**.nix"
|
|
||||||
- "flake.lock"
|
|
||||||
- ".gitea/workflows/ci.yaml"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
flake:
|
flake:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- 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
|
- name: Install Nix
|
||||||
|
if: steps.detect.outputs.run == 'true'
|
||||||
uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31
|
uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31
|
||||||
with:
|
with:
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
@@ -34,6 +65,7 @@ jobs:
|
|||||||
# Runs every flake check: treefmt formatting, deadnix, statix, and the
|
# Runs every flake check: treefmt formatting, deadnix, statix, and the
|
||||||
# pre-commit hooks (so a --no-verify commit can't ship unlinted).
|
# pre-commit hooks (so a --no-verify commit can't ship unlinted).
|
||||||
- name: Flake check
|
- name: Flake check
|
||||||
|
if: steps.detect.outputs.run == 'true'
|
||||||
run: nix flake check --print-build-logs
|
run: nix flake check --print-build-logs
|
||||||
|
|
||||||
# Evaluate (not build) each host's toplevel so eval errors fail CI cheaply.
|
# 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
|
# nixos/darwinConfigurations) rather than hard-coded, so adding or removing
|
||||||
# a host needs no change to this workflow.
|
# a host needs no change to this workflow.
|
||||||
- name: Evaluate NixOS host configurations
|
- name: Evaluate NixOS host configurations
|
||||||
|
if: steps.detect.outputs.run == 'true'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
hosts=$(nix eval --raw '.#nixosConfigurations' \
|
hosts=$(nix eval --raw '.#nixosConfigurations' \
|
||||||
@@ -56,6 +89,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Evaluate Darwin host configurations
|
- name: Evaluate Darwin host configurations
|
||||||
|
if: steps.detect.outputs.run == 'true'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
hosts=$(nix eval --raw '.#darwinConfigurations' \
|
hosts=$(nix eval --raw '.#darwinConfigurations' \
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,8 @@
|
|||||||
},
|
},
|
||||||
"lockFileMaintenance": {
|
"lockFileMaintenance": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"schedule": ["before 6am on monday"]
|
"schedule": ["before 6am on monday"],
|
||||||
|
"automerge": true
|
||||||
},
|
},
|
||||||
"git-submodules": {
|
"git-submodules": {
|
||||||
"enabled": false
|
"enabled": false
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ single flake.
|
|||||||
|
|
||||||
Defined in the host table in [`flake.nix`](./flake.nix):
|
Defined in the host table in [`flake.nix`](./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](./system/machine/T400/README.md) |
|
| `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) |
|
| `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) |
|
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) |
|
||||||
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) |
|
| `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),
|
Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor),
|
||||||
`system/modules/common-nixos.nix` (all NixOS hosts: fonts, nix-ld, caches),
|
`system/modules/common-nixos.nix` (all NixOS hosts: fonts, nix-ld, caches),
|
||||||
@@ -41,11 +42,13 @@ darwin-rebuild switch --flake .#lyrathorpe-mac
|
|||||||
## 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 — configured centrally in
|
ReGreet inside the `cage` kiosk compositor — implemented in
|
||||||
[`lyrathorpe/swaywm.nix`](./lyrathorpe/swaywm.nix), gated on
|
[`lyrathorpe/swaywm.nix`](./lyrathorpe/swaywm.nix), gated on
|
||||||
`features.swayDesktop.enable`. The greeter is forced to Dvorak to match the
|
`features.swayDesktop.enable` (the option is declared in
|
||||||
console and Sway session. Hosts with `features.swayDesktop.enable = false` (the
|
[`system/modules/features.nix`](./system/modules/features.nix), so headless hosts
|
||||||
WSL work box) keep plain TTY login. The target account needs a password
|
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.
|
(`passwd <user>`) before it can log in.
|
||||||
|
|
||||||
## MacBook (Asahi) firmware
|
## MacBook (Asahi) firmware
|
||||||
|
|||||||
Generated
+40
-40
@@ -3,16 +3,16 @@
|
|||||||
"brew-src": {
|
"brew-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1779646357,
|
"lastModified": 1781226006,
|
||||||
"narHash": "sha256-rnnAaESXxItX4D9xCMGvs3hfDBjbbTYht7OluRcvT8k=",
|
"narHash": "sha256-w4ZTuOnhYiDxjaynrMTASzp802QblBWmo3wpB8wVN4Y=",
|
||||||
"owner": "Homebrew",
|
"owner": "Homebrew",
|
||||||
"repo": "brew",
|
"repo": "brew",
|
||||||
"rev": "10a163ac127624caa80cc5cc5a705e97f3615b0e",
|
"rev": "109191be4988470b51a60a5ef1998520aa24c01b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "Homebrew",
|
"owner": "Homebrew",
|
||||||
"ref": "5.1.14",
|
"ref": "6.0.1",
|
||||||
"repo": "brew",
|
"repo": "brew",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "pkgs/firefox-addons",
|
"dir": "pkgs/firefox-addons",
|
||||||
"lastModified": 1780977789,
|
"lastModified": 1782014564,
|
||||||
"narHash": "sha256-UFJfQlvInbsVaTK5XC2lafdqWlwiNP5LuQFYfDKq6Dc=",
|
"narHash": "sha256-F/royQHyJAyKWKrV8AaG4Yf1yjzxa+PFk5xvTdvBrzk=",
|
||||||
"owner": "rycee",
|
"owner": "rycee",
|
||||||
"repo": "nur-expressions",
|
"repo": "nur-expressions",
|
||||||
"rev": "0b627f105ea3baa2fa10308a6a67a8f8cbbb3e2a",
|
"rev": "d6668e34bbce788459883a1097bf0ee170f49c61",
|
||||||
"type": "gitlab"
|
"type": "gitlab"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -136,11 +136,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1778507602,
|
"lastModified": 1781733627,
|
||||||
"narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=",
|
"narHash": "sha256-U3yTuGBnmXvXoQI3qkpfEDsn9RovQPAjN7ndRco+3u0=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "git-hooks.nix",
|
"repo": "git-hooks.nix",
|
||||||
"rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a",
|
"rev": "3bbec39bc90eadfa031e6f3b77272f3f60803e39",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -177,11 +177,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780361225,
|
"lastModified": 1781981105,
|
||||||
"narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=",
|
"narHash": "sha256-/1nNBbA7PrSQpTc9Qazkhl4kIPg+TNl0CjxS3UQJKlw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "e28654b71096e08c019d4861ca26acb646f583d8",
|
"rev": "7bfff44b465909f69a442701293bc0badcf476dc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -198,11 +198,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780789116,
|
"lastModified": 1781772065,
|
||||||
"narHash": "sha256-+/LcDMJGYQVLp3ECZ1jBhj3GcQU+Yt+OTsDsQFz8cMs=",
|
"narHash": "sha256-xIbRSwDB1GBAUsWsQZUjudGfAGQt3BOpsWaO/ugVa4w=",
|
||||||
"owner": "nix-darwin",
|
"owner": "nix-darwin",
|
||||||
"repo": "nix-darwin",
|
"repo": "nix-darwin",
|
||||||
"rev": "731951a251ca96cbd12a8e1bde63737e21947644",
|
"rev": "adda04f0bf4819575b1978c2f8d78401b3c2be12",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -217,11 +217,11 @@
|
|||||||
"brew-src": "brew-src"
|
"brew-src": "brew-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780492467,
|
"lastModified": 1781389246,
|
||||||
"narHash": "sha256-zMEJwtQPmsPPgPczFkyjWHgd1z0HagOPS2Wt2WDYLJY=",
|
"narHash": "sha256-ORqLAo/hoJdsZC7UPAuEHev6S0+XIqKEC7vjo5prz1k=",
|
||||||
"owner": "zhaofengli",
|
"owner": "zhaofengli",
|
||||||
"repo": "nix-homebrew",
|
"repo": "nix-homebrew",
|
||||||
"rev": "562332f97de9f5ba51aa647d70462e88222b2988",
|
"rev": "de7953a08ed4bb9245be043e468561c17b89130d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -237,11 +237,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780816331,
|
"lastModified": 1782030356,
|
||||||
"narHash": "sha256-0BYqs8yKWkOz2Q7+SP18N5E5gmDKSo6LSxIVIa0wWes=",
|
"narHash": "sha256-h4WpMr455AfRub0FXBaon6Vcpe0waUyJ4GivIW6oyd4=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nix-index-database",
|
"repo": "nix-index-database",
|
||||||
"rev": "1a2ea89c917781e88508d9fd2b507f2d2a0e173c",
|
"rev": "3017088b49efd404f78e3b104f553b97e4af786b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -258,11 +258,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780669925,
|
"lastModified": 1781520503,
|
||||||
"narHash": "sha256-inOQx/s7GQjh9bcCjCHXAeX0EHX+sOQUBoo8+bs48ME=",
|
"narHash": "sha256-XuqQQG1qRyc3o8ld937sDLQNx+QrGV852KJ0dNglJDg=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-apple-silicon",
|
"repo": "nixos-apple-silicon",
|
||||||
"rev": "5880026520a3fd248d59e1c81c4e4e111aefc6af",
|
"rev": "43043ad207529650f9fa68e1705f7cf9c08bfdeb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -278,11 +278,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1781020964,
|
"lastModified": 1781622756,
|
||||||
"narHash": "sha256-fS7xTi2j2iso5Hj7RNZLv/acDlCT+fgMVkVk40A7Uco=",
|
"narHash": "sha256-JrPh4M6S7aPsEE9tOENuZrxC6o2szSLlK+t4+nLke9s=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "32c2cd9e46286c4eced3dc6b613c659126bf3cca",
|
"rev": "08018c72174a4df5657f8d94178ac69fb9c243e5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -299,11 +299,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780765279,
|
"lastModified": 1781182279,
|
||||||
"narHash": "sha256-md6QHmlIx40bQkun43M2eT8aav5GURGkXEMFwof6uZs=",
|
"narHash": "sha256-V5EQQbDnmdiXGQXrEF1PEL7QYsFqfH8N1E89Z5ONwFk=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "NixOS-WSL",
|
"repo": "NixOS-WSL",
|
||||||
"rev": "3e6d8af994e2a2d31af7a91863d7c0d6e278d951",
|
"rev": "5675822ba756e6e56f8f6a5a76e90e0da2ece94d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -314,11 +314,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780734595,
|
"lastModified": 1781216227,
|
||||||
"narHash": "sha256-DmTfP92QFYRLOGXlMIE54MAgxSJjDWocl3gRNOu72Os=",
|
"narHash": "sha256-9mUW6gNwoN2SWc/l0fW4svPNOulXLl8ijqKyeSOGgJE=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9b696460ac78b5ccfc17c854d8c976f20456e943",
|
"rev": "a0374025a863d007d98e3297f6aa46cc3141c2f0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -330,11 +330,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780243769,
|
"lastModified": 1781577229,
|
||||||
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
|
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
|
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -353,11 +353,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1781038653,
|
"lastModified": 1781971008,
|
||||||
"narHash": "sha256-MxUVyE7A5QMJXOEFQhOHsqs4UPhujeDgdFhguE7Wmic=",
|
"narHash": "sha256-T2u2RQZWKvD1J+TgcxjiJr8IymBr/PrUNeAGhMZFZU4=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixvim",
|
"repo": "nixvim",
|
||||||
"rev": "e7ce6708cc5b6275406a8feee06ca50c5cafd6bf",
|
"rev": "7afca458f064f166d3a9c98db3b41a984fe46492",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -114,6 +114,7 @@
|
|||||||
baseModules = [
|
baseModules = [
|
||||||
./lyrathorpe/user.nix
|
./lyrathorpe/user.nix
|
||||||
./system/modules/common-nixos.nix
|
./system/modules/common-nixos.nix
|
||||||
|
./system/modules/features.nix
|
||||||
commonModule
|
commonModule
|
||||||
home-manager.nixosModules.home-manager
|
home-manager.nixosModules.home-manager
|
||||||
{
|
{
|
||||||
@@ -287,6 +288,22 @@
|
|||||||
./lyrathorpe/home/work.nix
|
./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
|
# Darwin host table — macOS machines built via mkDarwinHost. The shared
|
||||||
|
|||||||
@@ -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>rn` | Rename symbol (LSP; `<leader>` is `Space`) |
|
||||||
| `<leader>ca` | Code action (LSP) |
|
| `<leader>ca` | Code action (LSP) |
|
||||||
|
|
||||||
LSP covers Nix, Lua, Python and Terraform (the work box adds C# and Helm);
|
### Completion menu (nvim-cmp)
|
||||||
completion (nvim-cmp) appears as you type. Files are formatted on save
|
|
||||||
(conform-nvim). `:Git` opens fugitive; gitsigns shows gutter signs. which-key
|
Active only while the completion popup is open (it appears as you type, e.g.
|
||||||
pops up after `<leader>` to show the rest.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,18 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
autoEnableSources = true;
|
autoEnableSources = true;
|
||||||
settings = {
|
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";
|
snippet.expand = "function(args) require('luasnip').lsp_expand(args.body) end";
|
||||||
sources = [
|
sources = [
|
||||||
{ name = "nvim_lsp"; }
|
{ name = "nvim_lsp"; }
|
||||||
@@ -109,7 +121,7 @@
|
|||||||
trouble.enable = true; # project-wide diagnostics/quickfix list
|
trouble.enable = true; # project-wide diagnostics/quickfix list
|
||||||
lualine = {
|
lualine = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.options.theme = "catppuccin";
|
settings.options.theme = "catppuccin-mocha";
|
||||||
};
|
};
|
||||||
comment.enable = true; # gc / gcc comment toggling
|
comment.enable = true; # gc / gcc comment toggling
|
||||||
nvim-autopairs.enable = true;
|
nvim-autopairs.enable = true;
|
||||||
|
|||||||
@@ -77,12 +77,14 @@ in
|
|||||||
cc = "!cz commit";
|
cc = "!cz commit";
|
||||||
};
|
};
|
||||||
|
|
||||||
# SSH commit signing on personal hosts too (the work module sets the same
|
# SSH commit signing. This personal key is the default; the work module
|
||||||
# on the work host). mkDefault so a host without the key in its ssh-agent
|
# (work.nix) overrides it with the work key on the EDaaS host, the same way
|
||||||
# can override to false -- otherwise commits there would fail. Reuses the
|
# user.email is overridden -- so mkDefault here lets that plain definition
|
||||||
# existing ssh key; a dedicated personal key can be swapped in later.
|
# 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";
|
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;
|
commit.gpgsign = lib.mkDefault true;
|
||||||
tag.gpgsign = lib.mkDefault true;
|
tag.gpgsign = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# Daily automated review and triage of Renovate dependency PRs awaiting Emma's
|
||||||
|
# review.
|
||||||
|
#
|
||||||
|
# Host-scoped: imported only from work.nix (the EDaaS/WSL host), so the timer
|
||||||
|
# exists on this machine alone. A systemd *user* timer runs Claude Code headless
|
||||||
|
# once a day; it queries GitHub via the project-scoped github MCP server and
|
||||||
|
# writes a risk-graded summary to the journal (read with
|
||||||
|
# `journalctl --user -u renovate-review`).
|
||||||
|
#
|
||||||
|
# Triage policy:
|
||||||
|
# * PRs that are clearly low risk (patch/minor bumps to tooling, infra, test
|
||||||
|
# or framework libs; symmetric diff; CI green; no application logic) AND not
|
||||||
|
# already approved are AUTO-APPROVED headlessly. These repos enable Renovate
|
||||||
|
# automerge, so an approval lets the PR merge itself with no human in the
|
||||||
|
# loop. This is intentional and was explicitly requested.
|
||||||
|
# * Everything else (medium/high risk, failing/pending CI, stale branches,
|
||||||
|
# anything touching application logic or needing judgement) is left
|
||||||
|
# untouched and surfaced to Emma.
|
||||||
|
#
|
||||||
|
# The run records two state files under $XDG_STATE_HOME/renovate-review for the
|
||||||
|
# once-a-day interactive-shell reminder defined below (programs.zsh.initContent):
|
||||||
|
# `last-run` (date of the last successful run) and `needs-review.txt` (the PRs
|
||||||
|
# that need Emma's eyes).
|
||||||
|
#
|
||||||
|
# Caveats (the foundation this stands on, none of it owned by this flake):
|
||||||
|
# * Auth is Vertex AI via gcloud Application Default Credentials
|
||||||
|
# (~/.config/gcloud/application_default_credentials.json). When that token
|
||||||
|
# can no longer refresh the run fails; re-auth with `gcloud auth login`.
|
||||||
|
# * The Vertex project, region and model are hardcoded below, copied from the
|
||||||
|
# interactive environment (the corporate launcher injects them; they live in
|
||||||
|
# no config file). If IT changes them, update them here. Claude Code handles
|
||||||
|
# its own network egress, so no proxy is set.
|
||||||
|
# * The github MCP server is defined in ~/code/.mcp.json, so the job runs with
|
||||||
|
# that directory as its working directory.
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# The review instructions handed to headless Claude: queue -> drop archived
|
||||||
|
# repos -> grade risk -> auto-approve the clearly-safe ones, surface the rest.
|
||||||
|
reviewPrompt = ''
|
||||||
|
Daily Renovate PR review and triage for Emma-Thorpe_citrix.
|
||||||
|
|
||||||
|
1. github MCP search_pull_requests, query: `is:open is:pr review-requested:Emma-Thorpe_citrix author:app/jenkins-stf-jm` (jenkins-stf-jm[bot] is this org's Renovate bot), perPage 50.
|
||||||
|
2. Build the archived-repo exclusion set: github MCP search_repositories with query `org:csg-citrix-storefront archived:true`, perPage 100, paginate all pages (~128). Collect each archived repo full_name. Do NOT use the `archived:false` qualifier on the PR search itself; it is mis-indexed and returns zero. Filter by the repo set instead.
|
||||||
|
3. Drop any PR whose repository is in the archived set (e.g. csg-citrix-storefront/traefik-fips is archived; a PR to an archived repo cannot merge and is noise).
|
||||||
|
4. For each remaining PR: pull_request_read method=get (diff size, mergeable_state, labels, age), method=get_status (CI), and method=get_reviews (existing approvals). Read the body's dependency table for what is bumped.
|
||||||
|
5. Grade risk Low / Medium / High. LOW means ALL of: only patch or minor version bumps; the packages are tooling, observability, infrastructure, test, or framework/runtime libraries (not business logic); the diff is small and symmetric (version strings / lockfiles only); CI is passing; nothing security-policy-loosening. Anything that is a major bump, touches application logic, has failing or pending CI, is a stale branch needing rebase, or that you are not confident about is NOT Low.
|
||||||
|
6. AUTO-APPROVE the safe ones: for every PR that is Low risk AND has passing CI AND is not already approved by Emma-Thorpe_citrix, submit an approving review with pull_request_review_write (method=create, event=APPROVE, body: a one-line note that this is an automated approval of a low-risk dependency update). Approve only these. NEVER call merge. NEVER approve a Medium/High PR or one you are unsure about. (Note: these repos automerge on approval, so approval effectively merges it.)
|
||||||
|
7. Leave for Emma, without approving: every Medium/High risk PR, anything with failing or pending CI, stale branches, and anything needing human judgement.
|
||||||
|
8. Print a markdown table (PR linked, repo, change summary, size, CI, risk, action: Auto-approved / Needs review / Held) and terse notes. State how many PRs were excluded as archived.
|
||||||
|
9. As the FINAL lines of your output, emit machine-readable triage lines, one per PR, with these EXACT prefixes and nothing else on the line:
|
||||||
|
- For each PR you auto-approved: APPROVED> owner/repo#NUMBER short title
|
||||||
|
- For each PR that needs Emma's review: NEEDS> owner/repo#NUMBER (Risk) one-line reason — https://github.com/owner/repo/pull/NUMBER
|
||||||
|
If no PR needs Emma's review, emit no NEEDS> lines at all.
|
||||||
|
If the post-filter search returns zero PRs, say so in one line and emit no NEEDS> lines.
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Hold the prompt in its own store file rather than inline, so its literal
|
||||||
|
# backticks and `$` don't trip shellcheck (SC2016) in the wrapper below.
|
||||||
|
promptFile = pkgs.writeText "renovate-review-prompt.md" reviewPrompt;
|
||||||
|
|
||||||
|
# Tools the headless run is permitted to use without interactive prompts.
|
||||||
|
# Read-only github MCP calls, plus review_write so it can submit APPROVE
|
||||||
|
# reviews on low-risk PRs. Deliberately NOT included: any merge tool.
|
||||||
|
allowedTools = lib.concatStringsSep "," [
|
||||||
|
"mcp__github-mcp__search_pull_requests"
|
||||||
|
"mcp__github-mcp__search_repositories"
|
||||||
|
"mcp__github-mcp__pull_request_read"
|
||||||
|
"mcp__github-mcp__pull_request_review_write"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Where the run records state for the interactive-shell reminder.
|
||||||
|
stateDir = "$HOME/.local/state/renovate-review";
|
||||||
|
|
||||||
|
renovate-review = pkgs.writeShellApplication {
|
||||||
|
name = "renovate-review";
|
||||||
|
runtimeInputs = [ config.programs.claude-code.package ];
|
||||||
|
text = ''
|
||||||
|
# The github MCP server is project-scoped to ~/code; run from there.
|
||||||
|
cd "$HOME/code"
|
||||||
|
|
||||||
|
# Claude Code auth + endpoint: Vertex AI. These are injected into the
|
||||||
|
# interactive shell by the corporate launcher (not present in any config
|
||||||
|
# file), so a systemd-spawned process must set them explicitly. Do NOT set
|
||||||
|
# HTTP(S)_PROXY: Claude Code self-provisions its own network egress to
|
||||||
|
# Vertex; forcing a proxy here points it at a per-session socket that does
|
||||||
|
# not exist outside an interactive launch and breaks connectivity.
|
||||||
|
export CLAUDE_CODE_USE_VERTEX=1
|
||||||
|
export ANTHROPIC_VERTEX_PROJECT_ID=claude-code-citrix
|
||||||
|
export CLOUD_ML_REGION=global
|
||||||
|
export ANTHROPIC_MODEL='claude-opus-4-8[1m]'
|
||||||
|
|
||||||
|
# Capture the run so we can both log it (journal) and persist the triage
|
||||||
|
# for the shell reminder. If claude exits non-zero, errexit aborts here and
|
||||||
|
# the state files are left stale, so the reminder will flag a missed run.
|
||||||
|
out="$(claude -p "$(cat ${promptFile})" \
|
||||||
|
--allowedTools ${lib.escapeShellArg allowedTools} \
|
||||||
|
--output-format text)"
|
||||||
|
|
||||||
|
printf '%s\n' "$out"
|
||||||
|
|
||||||
|
# Persist state for programs.zsh.initContent's daily reminder. needs-review
|
||||||
|
# gets the PRs Claude flagged for Emma (the NEEDS> lines, prefix stripped);
|
||||||
|
# it is empty when nothing needs her attention. grep || true: no matches is
|
||||||
|
# the all-clear case, not an error.
|
||||||
|
mkdir -p "${stateDir}"
|
||||||
|
printf '%s\n' "$out" | grep '^NEEDS> ' | sed 's/^NEEDS> //' > "${stateDir}/needs-review.txt" || true
|
||||||
|
date +%F > "${stateDir}/last-run"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.user.services.renovate-review = {
|
||||||
|
Unit.Description = "Daily Renovate PR review (headless Claude Code)";
|
||||||
|
Service = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = lib.getExe renovate-review;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.user.timers.renovate-review = {
|
||||||
|
Unit.Description = "Schedule the daily Renovate PR review";
|
||||||
|
Timer = {
|
||||||
|
OnCalendar = "*-*-* 08:47:00";
|
||||||
|
# Run on next boot if the machine was off at the scheduled time.
|
||||||
|
Persistent = true;
|
||||||
|
# Avoid firing exactly on the minute boundary.
|
||||||
|
RandomizedDelaySec = "5m";
|
||||||
|
};
|
||||||
|
Install.WantedBy = [ "timers.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Interactive-shell reminder: nudge once per calendar day about the daily
|
||||||
|
# Renovate timer -- whether it actually ran, and any PRs that need Emma's eyes
|
||||||
|
# (the auto-approved ones need no nudge). Throttled via a `reminded-on` marker
|
||||||
|
# so it prints in the first shell/tmux pane of the day, not every pane. mkOrder
|
||||||
|
# 1600 runs after shell.nix's tmux re-exec (order 200), so it fires inside the
|
||||||
|
# tmux pane where Emma actually reads it.
|
||||||
|
programs.zsh.initContent = lib.mkOrder 1600 ''
|
||||||
|
if [[ $- == *i* ]]; then
|
||||||
|
__rr_dir="$HOME/.local/state/renovate-review"
|
||||||
|
__rr_today=$(date +%F)
|
||||||
|
if [[ "$(cat "$__rr_dir/reminded-on" 2>/dev/null)" != "$__rr_today" ]]; then
|
||||||
|
__rr_last=$(cat "$__rr_dir/last-run" 2>/dev/null)
|
||||||
|
if [[ "$__rr_last" != "$__rr_today" ]]; then
|
||||||
|
print -P "%F{yellow}renovate:%f last review ''${__rr_last:-never} (not today) -- check: systemctl --user status renovate-review"
|
||||||
|
fi
|
||||||
|
if [[ -s "$__rr_dir/needs-review.txt" ]]; then
|
||||||
|
print -P "%F{red}renovate:%f $(grep -c . "$__rr_dir/needs-review.txt") PR(s) need your review:"
|
||||||
|
sed 's/^/ - /' "$__rr_dir/needs-review.txt"
|
||||||
|
print -P " -> journalctl --user -u renovate-review -e"
|
||||||
|
elif [[ "$__rr_last" == "$__rr_today" ]]; then
|
||||||
|
print -P "%F{green}renovate:%f reviewed today -- low-risk auto-approved, nothing for you."
|
||||||
|
fi
|
||||||
|
mkdir -p "$__rr_dir" && print -r -- "$__rr_today" > "$__rr_dir/reminded-on"
|
||||||
|
fi
|
||||||
|
unset __rr_dir __rr_today __rr_last
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -203,12 +203,15 @@ in
|
|||||||
flake = "$HOME/code/nixfiles";
|
flake = "$HOME/code/nixfiles";
|
||||||
};
|
};
|
||||||
|
|
||||||
# GitHub CLI. Prefer SSH for any git operations it drives, matching the
|
# GitHub CLI. `programs.gh.settings` is deliberately unset: home-manager renders
|
||||||
# ssh-based remotes used elsewhere.
|
# ~/.config/gh/config.yml as a read-only /nix/store symlink whenever the module
|
||||||
programs.gh = {
|
# is enabled, but gh must rewrite that file on `gh auth login` and `gh config
|
||||||
enable = true;
|
# set`, which then fail with a permission error. Suppress the managed config.yml
|
||||||
settings.git_protocol = "ssh";
|
# (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 = {
|
programs.tmux = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -345,6 +348,60 @@ in
|
|||||||
# enables this in the work module; both being true merges cleanly.
|
# enables this in the work module; both being true merges cleanly.
|
||||||
services.ssh-agent.enable = lib.mkIf pkgs.stdenv.hostPlatform.isLinux true;
|
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
|
# 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
|
# /nix/store paths to completion functions; once a rebuild or a manual GC
|
||||||
# removes them, compinit fails with "_git: function definition file not found"
|
# removes them, compinit fails with "_git: function definition file not found"
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
# Host-scoped extras for this machine only (the EDaaS/WSL host).
|
||||||
|
imports = [
|
||||||
|
./renovate-review.nix # daily headless Renovate PR review (systemd user timer)
|
||||||
|
];
|
||||||
|
|
||||||
# The work box keeps its own (corporate) ~/.ssh/config; don't let the personal
|
# The work box keeps its own (corporate) ~/.ssh/config; don't let the personal
|
||||||
# programs.ssh (shell.nix) take it over. The ssh-agent below still runs.
|
# programs.ssh (shell.nix) take it over. The ssh-agent below still runs.
|
||||||
programs.ssh.enable = lib.mkForce false;
|
programs.ssh.enable = lib.mkForce false;
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ let
|
|||||||
ctp = import ./catppuccin-mocha.nix;
|
ctp = import ./catppuccin-mocha.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
# The features.swayDesktop.enable option is declared in
|
||||||
features.swayDesktop.enable = lib.mkEnableOption "Enable Sway Desktop";
|
# 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 {
|
config = lib.mkIf cfg.enable {
|
||||||
programs.sway = {
|
programs.sway = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -97,6 +97,7 @@
|
|||||||
"llvm@21"
|
"llvm@21"
|
||||||
"lld@21"
|
"lld@21"
|
||||||
"python@3.14"
|
"python@3.14"
|
||||||
|
"dosbox-staging"
|
||||||
];
|
];
|
||||||
# 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.
|
||||||
|
|||||||
@@ -19,9 +19,7 @@
|
|||||||
defaultUser = "emmathorpe";
|
defaultUser = "emmathorpe";
|
||||||
wslConf.automount.root = "/mnt";
|
wslConf.automount.root = "/mnt";
|
||||||
wslConf.interop.appendWindowsPath = true;
|
wslConf.interop.appendWindowsPath = true;
|
||||||
wslConf.interop.register = true;
|
|
||||||
wslConf.interop.enabled = true;
|
wslConf.interop.enabled = true;
|
||||||
wslConf.interop.includePath = true;
|
|
||||||
wslConf.network.generateHosts = false;
|
wslConf.network.generateHosts = false;
|
||||||
startMenuLaunchers = true;
|
startMenuLaunchers = true;
|
||||||
docker-desktop.enable = false;
|
docker-desktop.enable = false;
|
||||||
@@ -43,6 +41,11 @@
|
|||||||
autoPrune.enable = true;
|
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;
|
networking.resolvconf.enable = false;
|
||||||
|
|
||||||
# Drop the systemd-ssh-proxy Include from the generated /etc/ssh/ssh_config.
|
# Drop the systemd-ssh-proxy Include from the generated /etc/ssh/ssh_config.
|
||||||
@@ -58,6 +61,13 @@
|
|||||||
systemd.services.docker-desktop-proxy.script = lib.mkForce ''${config.wsl.wslConf.automount.root}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.wslConf.automount.root}/wsl/docker-desktop "C:\Program Files\Docker\Docker\resources"'';
|
systemd.services.docker-desktop-proxy.script = lib.mkForce ''${config.wsl.wslConf.automount.root}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.wslConf.automount.root}/wsl/docker-desktop "C:\Program Files\Docker\Docker\resources"'';
|
||||||
|
|
||||||
features.swayDesktop.enable = false;
|
features.swayDesktop.enable = false;
|
||||||
|
|
||||||
|
# Keep this user's systemd --user instance running without an open login
|
||||||
|
# session, so the home-manager user timer (renovate-review.nix) fires on
|
||||||
|
# schedule even when no terminal is attached. On WSL the timer still only runs
|
||||||
|
# while the distro itself is up; Persistent=true catches up a missed run at
|
||||||
|
# next start.
|
||||||
|
users.users.emmathorpe.linger = true;
|
||||||
# programs.nix-ld is enabled for all NixOS hosts in common-nixos.nix.
|
# programs.nix-ld is enabled for all NixOS hosts in common-nixos.nix.
|
||||||
# This value determines the NixOS release from which the default
|
# This value determines the NixOS release from which the default
|
||||||
# settings for stateful data, like file locations and database versions
|
# settings for stateful data, like file locations and database versions
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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 = [ ];
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user