diff --git a/AGENTS.md b/AGENTS.md index 4352eb6e..149d5405 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,8 +97,10 @@ introduces one, and re-evaluate entries when their `Revisit` trigger comes up. commit the lock file. Use `nix flake update ` when the update should remain uncommitted. - `just niri` and `just local` activate the machine you are on through `sudo` and block on a password prompt; `just darwin-rollback` switches the macOS generation on the machine you are on, - and a bare `sudo systemctl restart ` activates a running service the same way. The user runs - these, not an agent. + and a bare `sudo systemctl restart ` activates a running service the same way. + `just mihomo-geo` (mihomo's state directory) and `just shoukei-home-wifi` (a persistent + NetworkManager profile) change the local machine through `sudo` too. The user runs these, not an + agent. - `just shoryu`, `just shushou`, `just youko`, `just aquamarine`, `just ruby`, `just kana`, `just lab`, `just k3s-test`, and `just col ` activate systems through Colmena. Use the narrower recipe that matches the intended host scope. @@ -115,6 +117,9 @@ introduces one, and re-evaluate entries when their `Revisit` trigger comes up. only for the intended cleanup or history operation. - `just penvof` reads a process environment and can expose secrets. Use normal process inspection commands when environment values are not required. +- `just del-failed` deletes failed pods in every namespace of the current kube context. +- `just pkg-review ` and `just pkg-test ` dispatch a workflow in + `ryan4yin/nixpkgs-review-gha` that posts its result on the nixpkgs PR, a public GitHub write. ## Task Skills diff --git a/Justfile b/Justfile index 9d662cc0..df866040 100644 --- a/Justfile +++ b/Justfile @@ -60,7 +60,7 @@ upp input: history: nix profile history --profile /nix/var/nix/profiles/system -# Open a nix shell with the flake +# Open a nix repl with nixpkgs from the flake registry [group('nix')] repl: nix repl -f flake:nixpkgs diff --git a/SECURITY.md b/SECURITY.md index dff818dd..6983bbc8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -43,7 +43,7 @@ small, reversible controls and narrow exceptions; benchmark before adopting expe | MicroVM storage | Host store shared read-only through virtiofs; guest `/etc`, `/var` and `/home` persisted in separate images | Read-only store does not isolate the host-side virtiofs implementation or protect writable guest state | | Desktop apps | Selected apps use nixpak/bubblewrap; Podman is rootless | Rules and shared directories vary per app; user namespaces are needed for these sandboxes | | Secrets | agenix declarations here, encrypted data in the private nix-secrets repository | Recipients and plaintext file permissions must be reviewed; root on a recipient can read its decrypted secrets | -| Supply chain | Locked flake inputs; explicit binary-cache trust keys; normal user is not a Nix trusted-user | A trusted cache or imported Nix module can supply code executed as root; a lock file is not a security audit | +| Supply chain | Locked flake inputs; explicit binary-cache trust keys on NixOS; normal user is not a Nix trusted-user | A trusted cache or imported Nix module can supply code executed as root; a lock file is not a security audit | | Recovery | NixOS generations, btrbk/restic, hourly read-only snapshots of the REST store, and documented backup procedures | A rollback does not undo compromised data or secrets; backup configuration alone does not prove restore works; the offsite immutable copy is still planned | Sources: [host layout](hosts/README.md), [k3s deployment](hosts/k8s/README.md), @@ -51,6 +51,11 @@ Sources: [host layout](hosts/README.md), [k3s deployment](hosts/k8s/README.md), [Nix trust](modules/base/nix.nix), [secrets](secrets/README.md), and [backup](BACKUP.md). Cluster resources are maintained in the separate k8s-gitops repository. +The Macs set `nix.enable = false` ([nix-core.nix](modules/darwin/nix-core.nix)), so nix-darwin +writes no `/etc/nix/nix.conf`: the substituters, trusted keys and access-token include declared here +do not reach them. Their Nix settings come from the Determinate installation, which this repository +does not manage; those settings were not verified on the Macs. + ## Kernel and process baseline The following explicit, overridable baseline is implemented in @@ -96,9 +101,9 @@ an advisory and affected kernel versions before adding CVE claims; search snippe enrollment and actual Secure Boot state with `sbctl status`; do not assume all hosts have it. - Secure Boot, signed boot images, kernel lockdown and module signature enforcement are distinct controls. Test custom kernels and NVIDIA/out-of-tree modules before extending enforcement. -- AppArmor is enabled, but the explicit sudo policy is in complain mode and the default-deny/nix - scaffolds are disabled. Complain mode logs rather than blocks. Existing FHS-oriented abstractions - need Nix store path review; this is not broad enforced application confinement. +- AppArmor is enabled, but the only explicit policy, sudo's, is in complain mode. Complain mode logs + rather than blocks. Existing FHS-oriented abstractions need Nix store path review; this is not + broad enforced application confinement. - Systemd service sandboxing is per-service, not a global policy. Review capabilities, writable paths, network access and syscall requirements before tightening an individual service. @@ -242,10 +247,12 @@ export, credential, DNS record or firmware configuration was changed during the - Darwin's activation now re-applies each secret's declared ownership explicitly instead of a blanket `chown` over `/etc/agenix/*`, so the root-only `rclone.conf` stays root-owned. Runtime ownership (`ls -l /etc/agenix`) on each Darwin host still needs a check after the next activation. -- PostgreSQL's loopback TCP listener still trusts application roles by password (Immich and the - exporter use it), but `postgres` and replication now require `scram-sha-256`, closing the - local-superuser path. Provisioning per-role passwords for full loopback `scram-sha-256` is the - follow-up (the Grafana datasource file already tracks this as its item 1.5). +- PostgreSQL's loopback TCP listener still trusts every role except `postgres` without a password, + so any local process can log in as `immich` or `playground`. No service configured here uses that + path: Immich and the exporter connect over the Unix socket with peer authentication. `postgres` + and replication now require `scram-sha-256`, closing the local-superuser path. Provisioning + per-role passwords for full loopback `scram-sha-256` is the follow-up; the disabled Grafana + `postgres-playground` datasource also waits on a `playground` password. ### Sandbox and agent boundaries diff --git a/WORKAROUNDS.md b/WORKAROUNDS.md index afcfd2d8..e9d0d6da 100644 --- a/WORKAROUNDS.md +++ b/WORKAROUNDS.md @@ -25,7 +25,7 @@ A version or revision pinned for a reason that can go away. | WA-005 | `tuios` input — `flake.nix`; `home/base/tui/tuios/` | nixpkgs lags behind upstream | Use nixpkgs' `tuios` once it is recent enough | 2026-10-03 | on nixpkgs bump | active | | WA-006 | `nixpkgs-blender` — `flake.nix` | Blender 5.2 LTS + CUDA/OptiX pin; keeps `just up` from triggering a full source rebuild | Bump deliberately when needed; drop the pin if the current nixpkgs build is acceptable | 2026-07-24 | deliberate | active | | WA-007 | WeChat from `nixpkgs-master` — `hardening/bwraps/default.nix` | newest builds; the pinned channel lags | Fall back to the stable channel once it has a good build | 2026-10-03 | on nixpkgs bump | active | -| WA-020 | `fcitx5-vinput` pinned to release tag `v2.4.0` — `flake.nix`; consumer `home/linux/gui/base/vinput/default.nix` | Tracks a release tag instead of a branch, so `just up` stays on that release | Bump to a newer release tag when its changes are wanted | 2026-10-04 | on vinput release | active | +| WA-020 | `fcitx5-vinput` pinned to release tag `v2.4.2` — `flake.nix`; consumer `home/linux/gui/base/vinput/default.nix` | Tracks a release tag instead of a branch, so `just up` stays on that release | Bump to a newer release tag when its changes are wanted | 2026-10-04 | on vinput release | active | | WA-021 | `nix-x86-on-aarch64` input — our fork of rowanG077's patched FEX + muvm runtime — `flake.nix`; `hosts/12kingdoms-shoukei/game/` | FEX does not support 16K pages, so x86/x86_64 apps and games on shoukei need this pinned flake's 4K-page microVM runtime; our fork lets upstream changes be reviewed before use | Drop once FEX supports 16K pages directly, or nixpkgs/nixos-apple-silicon ships an equivalent runtime | 2026-10-07 | on FEX/nixpkgs/muvm update | active | | WA-023 | `nix.package = pkgs.nixVersions.latest` — `modules/nixos/base/nix.nix` | NixOS is opted into Nix 2.35 (2.35.2), which nixpkgs ships as `nixVersions.latest`, while `nixVersions.stable` is still 2.34 | Drop the override and fall back to the default `pkgs.nix` once `nixVersions.stable` is 2.35 or newer | 2026-10-07 | next nixpkgs bump | active | | WA-037 | `nixpkgs-npu` input pinned to `2ddccb854d89` — the `openvino-npu-compiler` branch of `ryan4yin/nixpkgs`, carrying [NixOS/nixpkgs#570230](https://github.com/NixOS/nixpkgs/pull/570230) — `flake.nix`; consumer `pkgs-npu` in `outputs/default.nix`, `hosts/idols-aquamarine/asr.nix` | The Intel NPU compiler, and the driver change that loads it, are unmerged, so the NPU driver the Whisper service needs comes from that branch | Drop the input and load `intel-npu-driver` from `nixos-unstable` once #570230 merges | 2026-10-17 | when #570230 merges | active | @@ -52,7 +52,7 @@ A setting that works around a bug or a hardware quirk. | WA-032 | `amdgpu.runpm=0` kernel parameter — `hosts/idols-aquamarine/hardware-amd-gpu.nix` | this board's DSDT advertises ATPX power control (`\_SB.PC00.GFX0.ATPX`) but never defines the `PEG0.PEGP._OFF`/`_ON` symbols its body calls, so the first idle resume of the headless R9700 leaves it in `runtime_status = error` (`resume of IP block failed -62`) until reboot, and amdgpu discards the ACPI error. Root cause, kernel-source trail and the measured field comparison: `hosts/idols-aquamarine/ROCM.md`. | Drop once a BIOS update defines those ACPI methods (upstream releases 1023/1066 exist for this board; a flash changes PCR7, so re-enroll TPM2), or a kernel release stops trusting ATPX when its power methods are missing | 2026-10-14 | on a BIOS update or kernel bump | active | | WA-036 | catppuccin eval-time ports kept in the home closure (`home.extraDependencies`) — `home/base/core/theme.nix` | catppuccin/nix reads some ports at eval time (`lib.importTOML "${sources.starship}/.toml"`) and inlines the result, so the built port, catppuccin's build/install hooks and the fetched port source stay unreachable: every gc deletes them, and the deploy after it rebuilds them (build logs: 2026-10-16). `home.extraDependencies` records the port in the generation, which is a gc root. Also read at eval time upstream: `bottom`, `element` (option `element-desktop`), `firefox`, `rio`, `palette` (options `skim`, `tty`), `spotify-player`. | Drop once catppuccin/nix stops reading port files at eval time, or those ports come from a binary cache | 2026-10-16 | on a catppuccin bump | active | | WA-039 | niri's NVIDIA free-buffer pool bounded (`GLVidHeapReuseRatio` 0) — `hosts/idols-ai/hardware-nvidia.nix` | the driver does not return freed vidmem buffers to its free pool, so niri's VRAM grows to ~1.1 GiB instead of the ~100 MiB its NVIDIA notes expect (measured 1137 MiB after 24 h uptime; [NVIDIA/egl-wayland#126](https://github.com/NVIDIA/egl-wayland/issues/126)) | Drop once a driver stops growing niri's VRAM without the profile, or niri ships its own default | 2026-10-19 | on an nvidia driver bump | active | -| WA-040 | NixOS root CA blacklist — `modules/base/security.nix` | eMudhra intermediate keys were compromised; [NixOS/nixpkgs#576390](https://github.com/NixOS/nixpkgs/pull/576390) and [#576401](https://github.com/NixOS/nixpkgs/pull/576401) merged before reaching release channels | Drop once every consumed NixOS nixpkgs revision includes the upstream root blacklist | 2026-10-20 | on nixpkgs bump | active | +| WA-040 | Root CA blacklist on NixOS and Darwin (Nix CA bundle only, not the macOS Keychain) — `modules/base/security.nix` | eMudhra intermediate keys were compromised; [NixOS/nixpkgs#576390](https://github.com/NixOS/nixpkgs/pull/576390) and [#576401](https://github.com/NixOS/nixpkgs/pull/576401) merged before reaching release channels | Drop once every consumed nixpkgs revision (`nixpkgs` and `nixpkgs-darwin`) includes the upstream root blacklist | 2026-10-20 | on nixpkgs bump | active | | WA-041 | Monitor OSD "no-signal auto power-off" disabled — no code in this repo (the P32A2V OSD setting) | during a DPMS-off idle the panel drops and re-adds HPD every ~13 s, and after wake niri keeps scanning out while the panel stays dark until the monitor is power cycled; the panel's own auto power-off drives that state, so it is now disabled. Pinning the mode to 120 Hz did not change the symptom | Drop once a driver retrains the DP link after a DPMS wake, the monitor firmware stops cycling HPD, or this monitor is replaced | 2026-10-21 | confirm on the next full idle cycle, then on an nvidia or monitor firmware update | active | If WA-008 freezes recur, the fallback is to restore the parameters commented out in diff --git a/docs/nixos-modernization-options.md b/docs/nixos-modernization-options.md index 253a5c4b..4b3e802d 100644 --- a/docs/nixos-modernization-options.md +++ b/docs/nixos-modernization-options.md @@ -2,10 +2,14 @@ > **Purpose:** let the maintainer decide, per candidate, whether it is worth adopting — by comparing > it against what this repository does **today**. A plan derived from this file is only approved -> work once its card here says **adopt**. +> work once its card here says **adopt**. The plans are not on `main`: they exist only on the local, +> unpushed git tag `modernization-plans-wip`, under `docs/plans/`: +> `2026-10-07-boot-chain-limine-uki-repart.md`, `2026-10-07-host-level-modernization.md` and +> `2026-10-07-lab-experimental-etc-init.md` (`git show modernization-plans-wip:docs/plans/`). > > Researched against nixpkgs `nixos-unstable` on 2026-10-07. Option names and upstream wording are -> quoted from the module sources cited in each card. +> quoted from the module sources cited in each card. The **Current:** sections were re-checked +> against this repository on 2026-10-21; the upstream research was not redone. ## How to use this file @@ -56,16 +60,20 @@ LAN-only client can run Tailscale. ## A2. systemd-resolved -**Current:** `services.resolved.enable` is set **nowhere**; hosts get DNS from systemd-networkd or -NetworkManager (or static config) and use the default `/etc/resolv.conf`. Note -`modules/nixos/base/networking/mdns.nix` already sets -`services.resolved.settings.Resolve.MulticastDNS = false` — currently inert, and it will activate -the moment resolved is enabled. mDNS itself is provided by avahi. +**Current:** `services.resolved.enable` evaluates `true` on 14 of the 15 NixOS hosts (all but +`akane`): nixpkgs enables it by default with systemd-networkd +(`nixos/modules/system/boot/networkd.nix`), and `shoukei`, which runs NetworkManager, enables it +through `modules.networking.mihomo.resolvedIntegration` (asserted by the eval test +`outputs/aarch64-linux/tests/mihomo-dns`). On `ai`, a mihomo lifecycle hook points the link's +resolved DNS at mihomo (`hosts/idols-ai/default.nix`). `DNSOverTLS` and `DNSSEC` evaluate `false`. +The `services.resolved.settings.Resolve.MulticastDNS = false` in +`modules/nixos/base/networking/mdns.nix` is therefore live; mDNS itself is provided by avahi. -**Proposed:** `services.resolved.enable = true`, optionally with `DNSOverTLS`/DNSSEC and caching. +**Proposed:** `DNSOverTLS`/DNSSEC and caching settings on the resolved instance the hosts already +run (`services.resolved.enable = true` would still be new on `akane`). -**Pros:** caching resolver, DoT/DNSSEC available, consistent stub across hosts, and the mDNS caveat -is already handled by the existing setting. +**Pros:** DoT/DNSSEC available; the caching resolver and a consistent stub across hosts already come +with the resolved instance in use, and the mDNS caveat is already handled by the existing setting. **Cons:** the LAN gateway `suzi` is a mihomo transparent proxy that **hijacks DNS to fake IPs that are only routable through the proxy** — DoT/DoH on a host would bypass that hijack and break proxied @@ -84,12 +92,16 @@ changes. ## A3. systemd-networkd instead of dhcpcd -**Current:** dhcpcd is not used anywhere in this repo. `ai`, `kana` and `ruby` already set -`networking.useNetworkd = true`; `shoukei` and `akane` run NetworkManager; VM hosts and MicroVM -guests use static config with `useDHCP = false`. The `br0` bridge on VM hosts is declared through -`systemd.network.netdevs/networks` already. +**Current:** dhcpcd is not used anywhere in this repo. `networking.useNetworkd = true` is set on 13 +of the 15 hosts: every VM host (`lib/genVmHostModule.nix`) and MicroVM guest +(`lib/genMicrovmGuestModule.nix`), plus `ai`, `aquamarine`, `kana` and `ruby`; only `shoukei` and +`akane` run NetworkManager. VM hosts and MicroVM guests use static config with `useDHCP = false`. +The `br0` bridge on VM hosts is declared through `systemd.network.netdevs/networks` already. -**Proposed:** `networking.networkd.enable = true` with `networking.networks`/`networkdOptions`. +**Proposed:** systemd-networkd on the remaining NetworkManager hosts, through the options the other +hosts already use: `networking.useNetworkd` and `systemd.network.{networks,netdevs}`. The +`networking.networkd.enable` / `networking.networks` / `networkdOptions` names first recorded here +do not exist in the locked nixpkgs. **Pros:** declarative per-interface config, one implementation for stage 1 and stage 2, no dhcpcd anywhere in the path (nothing in this repo uses it), and VM hosts already speak `systemd.network` @@ -103,7 +115,10 @@ lease/renew and DNS handling. **Trigger to adopt:** guest-only first, then a non-VM host with console access. -**Recommendation:** **watch**, guest-only experiment (plan 3, task 5). +**Recommendation:** **watch**, guest-only experiment (`lab-experimental-etc-init.md`, Task 5). The +corrected facts make that experiment moot — every MicroVM guest already runs networkd, and the plan +task uses the non-existent option names above — so the open call is whether `shoukei` and `akane` +move off NetworkManager. **Decision:** _pending_ @@ -134,12 +149,16 @@ missing-log incident shows up. ## A5. zram-generator instead of zramSwap **Current:** a local module `modules/nixos/base/zram.nix` wraps `zramSwap` and adds sysctl tunings -(swappiness, readahead), disabled on idols-ai (disk swapfile) and memoryPercent-tuned on shoukei. +(swappiness, readahead), disabled on idols-ai (disk swapfile) and aquamarine, memoryPercent-tuned on +shoukei, and not imported on akane. In the locked nixpkgs `zramSwap` is itself implemented on +zram-generator (`nixos/modules/config/zram.nix` sets `services.zram-generator.enable`, which youko +evaluates `true`). **Proposed:** `services.zram-generator.enable` with a `zram0` definition. **Pros:** systemd-native unit generator, config expressed as data, upstream-maintained, no -NixOS-specific script. +NixOS-specific script — all but writing the `zram0` settings directly already come with `zramSwap` +(see Current). **Cons:** your module couples zram presence to sysctl tunings — the migration must keep that logic working when zram is off; two zram implementations are easy to have half-enabled during the change; @@ -155,8 +174,8 @@ the gain over a working `zramSwap` is mostly cosmetic. ## A6. nixos-facter instead of hand-written hardware-configuration.nix -**Current:** 3 hosts carry hand-written `hardware-configuration.nix` (`shoryu`, `shoukei`, `ai`; -`shushou` and `youko` import `shoryu`'s, and the qemu guests use the shared +**Current:** 4 hosts carry hand-written `hardware-configuration.nix` (`shoryu`, `shoukei`, `ai`, +`aquamarine`; `shushou` and `youko` import `shoryu`'s, and the qemu guests use the shared `modules/nixos/server/qemu-guest-hardware-configuration.nix`), plus deliberate per-host quirks such as the NVMe APST workaround WA-008 and USB-SATA mitigations WA-009. @@ -174,7 +193,7 @@ defaults become less transparent than a file you can read in the diff; your deli **Trigger to adopt:** on a new host build, or when a hardware change forces a regeneration anyway. -**Recommendation:** **watch** — adopt on the next new host rather than retrofitting eight. +**Recommendation:** **watch** — adopt on the next new host rather than retrofitting four. **Decision:** _pending_ @@ -204,10 +223,12 @@ cannot go to a public cache. **Current:** static `nix.buildMachines` (`modules/nixos/base/remote-builders.nix`); the plain user is deliberately not a trusted user on the clients, though the builder host trusts its builder login; -Nix 2.35 via `nixVersions.latest` (WA-023). +Nix 2.35 via `nixVersions.latest` (WA-023). `auto-allocate-uids` (with `cgroups`) is already enabled +in `modules/nixos/base/nix.nix` for nixpkgs' nspawn-based tests, and the locked nixpkgs has no +`nix-servd` module or package. -**Proposed:** `nix-servd` for scheduling, plus `auto-allocate-uids` and `local-overlay-store` -experimental features. +**Proposed:** `nix-servd` for scheduling, plus the `local-overlay-store` experimental feature +(`auto-allocate-uids` is already on, see Current). **Pros:** dynamic builder selection and health checks instead of a hand-maintained list; the experimental features move toward running the daemon without root, consistent with the @@ -253,9 +274,10 @@ are hard to attribute; no measured problem to solve yet. ## B1. limine instead of systemd-boot + lanzaboote -**Current:** systemd-boot everywhere, except qemu guests (GRUB on `/dev/vda`) and idols-ai, where -lanzaboote provides Secure Boot with a `/etc/secureboot` (lzpt) bundle; the lanzaboote flake input -must track a rust-overlay follow (WA-003). +**Current:** systemd-boot on akane, shoukei and the VM hosts (shoryu, shushou, youko); GRUB on +`/dev/vda` on the libvirt guests kana and ruby; no bootloader on the MicroVM guests; on idols-ai and +aquamarine, lanzaboote provides Secure Boot with a `/etc/secureboot` (lzpt) bundle. The lanzaboote +flake input must track a rust-overlay follow (WA-003). **Proposed:** `boot.loader.limine` — in nixpkgs, with `secureBoot`, `validateChecksums`, `panicOnChecksumMismatch`, `biosSupport`, `maxGenerations`, graphical menu. @@ -282,8 +304,8 @@ but it is the highest-risk item on this list. ## B2. boot.uki / system.build.uki -**Current:** lanzaboote already produces and signs a UKI on idols-ai; other hosts boot -kernel+initrd. +**Current:** lanzaboote already produces and signs a UKI on idols-ai and aquamarine; other hosts +boot kernel+initrd. **Proposed:** `boot.uki`. @@ -355,8 +377,13 @@ and its own state; unclear what problem it solves here. ## B6. Measured boot phases / FIDO2 credentials -**Current:** Secure Boot via lanzaboote; TPM2/FIDO2 initrd options are off, so boot is signed but -not measured into a policy. +**Current:** Secure Boot via lanzaboote on ai and aquamarine, whose LUKS volumes are enrolled for +TPM2 unlock bound to PCRs 0+7, with the passphrase slot as fallback (`hosts/idols-ai/README.md`, +`hosts/idols-aquamarine/README.md`). `boot.initrd.systemd.tpm2.enable` evaluates `true` on every +host except the MicroVM guests, and on ai and aquamarine `systemd.tpm2.enable` and +`boot.initrd.systemd.fido2.enable` are `true` as well (nixpkgs defaults from the systemd package). +Not adopted: `systemd.tpm2.pcrphases.enable` is `false`, and the READMEs document no FIDO2 +credential, only TPM2 and passphrase slots. **Proposed:** `systemd.tpm2.enable`, `systemd.tpm2.pcrphases.enable`, `boot.initrd.systemd.tpm2.enable`, `boot.initrd.systemd.fido2.enable`. @@ -413,16 +440,22 @@ are deleted early. **Recommendation:** **adopt additively**. **Decision:** _pend ## C3. nix-fast-build -**Current:** serial evaluation in CI. **Proposed:** `nix-fast-build` over the eval tests and the -three host systems, reusing remote builders. **Pros:** parallel eval/build, no new CI infra. -**Cons:** one more tool in the Justfile; CI already gates merges. **Recommendation:** **adopt**. -**Decision:** _pending_ +**Current:** CI (`.github/workflows/flake_evaltests.yml`) runs one `nix eval .#evalTests` and one VM +test (`checks.x86_64-linux.security-exporters`) on GitHub-hosted `ubuntu-latest` runners. +**Proposed:** `nix-fast-build` over the eval tests and the three host systems; the remote builder +(aquamarine, over SSH from repo machines) serves only a local Justfile run, not the hosted CI +runners. **Pros:** parallel eval/build, no new CI infra. **Cons:** one more tool in the Justfile; CI +already gates merges. **Recommendation:** **adopt**. **Decision:** _pending_ ## C4. Cleanups (no decision needed) WA-001 (smartctl pin) should be dropped once nixpkgs carries the upstream fix; the "nixfmt with width 100" sentence in `AGENTS.md` should be checked against the formatter `just fmt` actually -resolves to. **Recommendation:** **adopt**. **Decision:** _pending_ +resolves to. Checked 2026-10-21: the locked nixpkgs still ships smartctl_exporter 0.14.0, which +lacks the #329 fix (`overlays/smartctl-exporter/`), so WA-001 stays; `just fmt` runs the dev shell's +nixfmt 1.5.0, whose default width is 100, and `pre-commit-check` sets width 100 +(`outputs/default.nix`), so the `AGENTS.md` sentence is correct. **Recommendation:** **adopt**. +**Decision:** _pending_ ## C5. jj @@ -434,18 +467,18 @@ _pending_ # D. Lab-only experimental group The whole group shares one characteristic: upstream labels it experimental, and a mistake can -prevent boot or login. **Recommendation for the group: evaluate in a disposable guest (plan 3), -adopt individually only afterwards.** +prevent boot or login. **Recommendation for the group: evaluate in a disposable guest +(`lab-experimental-etc-init.md` on the plans tag), adopt individually only afterwards.** -| # | Candidate | Current | Main pro | Main con | Recommendation | -| --- | ------------------------------------------------------ | -------------------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| D1 | `system.etc.overlay.enable` | /etc generated by the perl activation script | /etc becomes an EROFS + overlay mount; foundation for an immutable /etc | upstream: _"This is currently experimental. Only enable this option if you are confident that you can recover your system if it breaks."_; every /etc writer must be audited | lab only | -| D2 | `systemd.sysusers.enable` / `services.userborn.enable` | `update-users-groups.pl` | removes the perl script; prerequisite for an immutable /etc | experimental; uid/gid migration risk | lab only | -| D3 | `system.etc.overlay.mutable = false` | writable /etc | truly immutable /etc | **blocked**: agenix writes `/etc/agenix` (see the `environment.etc` copies in `secrets/nixos.nix`), so secrets must move to /run or to sops-nix first | lab only, after secrets move | -| D4 | `system.nixos-init.enable` | `activationScripts` | bashless init | makes `activationScripts` a silent no-op — btrbk/restic hooks would stop running without an error | lab only | -| D5 | `security.pam.settings` | per-service booleans | freeform PAM config | upstream marks it subject to breaking changes; a mistake locks you out | lab only | -| D6 | `security.shadow.enable = false` + greetd | shadow suite provides login/su | fewer setuid programs | assertion couples the two; changes VT login semantics | lab only | -| D7 | `security.apparmor.policies` | deprecated `profiles` option | policy set is now data | writing profiles is real work and breakage-prone; `SECURITY.md` explicitly warns against untested profiles | lab only | +| # | Candidate | Current | Main pro | Main con | Recommendation | +| --- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| D1 | `system.etc.overlay.enable` | /etc generated by the perl activation script | /etc becomes an EROFS + overlay mount; foundation for an immutable /etc | upstream: _"This is currently experimental. Only enable this option if you are confident that you can recover your system if it breaks."_; every /etc writer must be audited | lab only | +| D2 | `systemd.sysusers.enable` / `services.userborn.enable` | `update-users-groups.pl` | removes the perl script; prerequisite for an immutable /etc | experimental; uid/gid migration risk | lab only | +| D3 | `system.etc.overlay.mutable = false` | writable /etc | truly immutable /etc | **blocked**: agenix writes `/etc/agenix` (see the `environment.etc` copies in `secrets/nixos.nix`), so secrets must move to /run or to sops-nix first | lab only, after secrets move | +| D4 | `system.nixos-init.enable` | `activationScripts` | bashless init | makes `activationScripts` a silent no-op — youko's `data-ro-backing`, `installCaddyWallpapers` and `installHomepageDashboardConfig` would stop running without an error | lab only | +| D5 | `security.pam.services..rules` | per-service booleans | freeform PAM config | upstream marks it subject to breaking changes; a mistake locks you out | lab only | +| D6 | `security.shadow.enable = false` + greetd | shadow suite provides login/su | fewer setuid programs | assertion couples the two; changes VT login semantics | lab only | +| D7 | `security.apparmor.policies` | `policies.sudo` already in use (`hardening/apparmor/default.nix`); the old `profiles` option is removed upstream | policy set is now data | writing profiles is real work and breakage-prone; `SECURITY.md` explicitly warns against untested profiles | lab only | --- @@ -465,8 +498,8 @@ adopt individually only afterwards.** # F. Decision summary -Fill this in as cards are decided; the plan documents should then be trimmed to **adopt** items -only. +Fill this in as cards are decided; the plan documents (on the `modernization-plans-wip` tag, see the +header) should then be trimmed to **adopt** items only. | Card | Candidate | Decision | Date | Note | | ----- | --------------------------- | -------- | ---- | ---- | diff --git a/flake.nix b/flake.nix index 37f89272..982426c1 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ # https://hydra.nixos.org/jobset/nixpkgs/unstable # update via nix flake update nixpkgs --override-input nixpkgs github:NixOS/nixpkgs/ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - # Pinned to release tag v2.4.0 (not a branch); bump deliberately when needed. + # Pinned to release tag v2.4.2 (not a branch); bump deliberately when needed. fcitx5-vinput = { url = "github:xifan2333/fcitx5-vinput/v2.4.2"; inputs.nixpkgs.follows = "nixpkgs"; diff --git a/home/base/README.md b/home/base/README.md index 6e34ce58..b2165221 100644 --- a/home/base/README.md +++ b/home/base/README.md @@ -31,12 +31,13 @@ systems. - **terminal/**: Terminal emulator configurations - **ghostty.nix**: Ghostty terminal (default) - **kitty.nix**: Kitty terminal + - **work-gitconfig.nix**: Work git identity for repositories under `~/work`, linked from agenix ### Terminal Interface - **tui/**: Terminal-based interface configurations - - **agents/**: AI coding agents — global rules deployment, CLIs, and telemetry/auto-update - opt-outs + - **agents/**: AI coding agents — global rules and skills deployment, CLIs, and + telemetry/auto-update opt-outs - **cloud/**: Cloud development tools (Terraform, etc.) - **container.nix**: Container tools (Docker, Podman) - **dev-tools.nix**: Terminal-based development tools diff --git a/home/base/tui/agents/README.md b/home/base/tui/agents/README.md index 9165b2b1..ccfa097f 100644 --- a/home/base/tui/agents/README.md +++ b/home/base/tui/agents/README.md @@ -13,13 +13,13 @@ Markdown with no Nix dependency; this module only wires it into place. - `skills.nix`: links the global skills into `~/.agents/skills/`, from pinned flake inputs and from `agents/skills/`; the inventory is [agents/skills/README.md](../../../../agents/skills/README.md). -- `packages.nix`: agent CLIs (`codex`, `opencode2`, `pi`) from the `llm-agents` flake input, plus - `pkgs.mcp-grafana` for the Grafana MCP server. -- `env.nix`: telemetry and auto-update opt-outs for the agents. dsh is installed by this module and - configured at runtime: profile patches live in `~/.dsh/profiles//cordis.patch.yml`. The - Nushell-only shell is selected when the package is built, via `dsh.withPlugins` from - `nur-ryan4yin`, so it is a derivation choice rather than runtime state. See [dsh.md](./dsh.md) for - the layer order and the settings that bite. +- `packages.nix`: agent CLIs (`codex`, `opencode2`, `pi`) from the `llm-agents` flake input, dsh + from `nur-ryan4yin`, `pkgs.ctx7` for the `find-docs` skill, and `pkgs.mcp-grafana` for the Grafana + MCP server. dsh's Nushell-only shell is selected when the package is built, via `dsh.withPlugins` + with the plugin from the `dsh-nu` flake input, so it is a derivation choice rather than runtime + state; profile patches live in `~/.dsh/profiles//cordis.patch.yml`. See [dsh.md](./dsh.md) + for the layer order and the settings that bite. +- `env.nix`: telemetry and auto-update opt-outs for the agents. ## Deployed rule targets @@ -65,7 +65,7 @@ compatibility links are needed: includes `~/.agents/skills` and follows symlink directories. - **dsh**: [Filesystem skill package](https://www.npmjs.com/package/@deepseek-ai/dsh-skill-filesystem) - documents `~/.agents/skills` and directory symlinks; confirmed in the shipped 0.2.0-rc.2 loader. + documents `~/.agents/skills` and directory symlinks; confirmed in the shipped 0.2.1-rc.2 loader. Adding or removing links requires a user-run Home Manager switch. Changes to an already-linked skill are available without a rebuild; start a new session or use the harness's reload mechanism to read diff --git a/home/base/tui/agents/dsh.md b/home/base/tui/agents/dsh.md index de2f4f05..a9bc970a 100644 --- a/home/base/tui/agents/dsh.md +++ b/home/base/tui/agents/dsh.md @@ -81,8 +81,10 @@ not been boot-tested. Verify after a switch: ```sh -dsh --profile web --dump-config | grep -n "nu-executor\|nu-teaching" +dsh --profile web --dump-config | grep -nE 'nu-executor|nu-teaching' # expect the first-party shell rows disabled, nu-executor and nu-teaching inserted -dsh --profile web o+e>| grep "disabling profile plugin" -# a dsh bump can disable a row whose manifest no longer matches; nothing prints when healthy ``` + +A dsh bump can disable a row whose manifest no longer matches. That compatibility check runs only +when a profile boots, not under the boot-free `--dump-config`, so read the stderr of the next boot +(for example `just dsh-web`) for `disabling profile plugin`; nothing prints when healthy. diff --git a/home/darwin/README.md b/home/darwin/README.md index eb7250ca..5899d4cd 100644 --- a/home/darwin/README.md +++ b/home/darwin/README.md @@ -19,7 +19,8 @@ This directory contains macOS-specific Home Manager configurations for Darwin sy ## Features - macOS-specific package installations and configurations -- Native macOS applications and utilities -- Touch ID and system integration -- Homebrew integration for additional packages -- macOS-specific shell configurations and aliases +- macOS-specific shell configurations (Homebrew's `bin` directories on `PATH` for bash and zsh) + +System-level macOS settings live in the nix-darwin modules: Touch ID for `sudo` in +[`modules/darwin/system.nix`](../../modules/darwin/system.nix), and native applications and Homebrew +packages in [`modules/darwin/apps.nix`](../../modules/darwin/apps.nix). diff --git a/home/hosts/README.md b/home/hosts/README.md index 427c065b..a526ef72 100644 --- a/home/hosts/README.md +++ b/home/hosts/README.md @@ -11,7 +11,7 @@ This directory contains host-specific Home Manager entry modules. 1. Each host output should reference only one file under `home/hosts/...`. 2. Shared home module imports should be handled in the host file itself. - - Linux hosts usually import `../linux/core.nix` or `../linux/gui.nix`. - - Darwin hosts import `../darwin`. + - Linux hosts usually import `../../linux/core.nix` or `../../linux/gui.nix`. + - Darwin hosts import `../../darwin`. 3. Host-specific overrides (SSH keys, desktop toggles, host-local config links) live in the same host file. diff --git a/home/linux/gui/README.md b/home/linux/gui/README.md index d871b6c5..d8cfe054 100644 --- a/home/linux/gui/README.md +++ b/home/linux/gui/README.md @@ -22,9 +22,8 @@ Manager. - Fcitx5 input method framework - Games and gaming utilities - GTK theme configurations - - Immutable file handling - Note-taking applications - - Wayland applications + - Wayland session tools and environment variables - XDG desktop configurations ## Why install Desktop Environments in Home Manager instead of NixOS Module? diff --git a/home/linux/gui/base/README.md b/home/linux/gui/base/README.md index c689e1a1..e3a552f2 100644 --- a/home/linux/gui/base/README.md +++ b/home/linux/gui/base/README.md @@ -84,14 +84,15 @@ Noctalia consolidates functionality that previously required multiple tools: screen recording, auto-mount, virt-manager/virt-viewer, `wlogout` emergency fallback) - **[`browsers.nix`](./browsers.nix)**: Web browsers - **[`vscode.nix`](./vscode.nix)**: VS Code -- **[`media.nix`](./media.nix)**: Media players +- **[`media.nix`](./media.nix)**: Audio control, mpv, go-musicfox, an image viewer, GPU video + diagnostics, Zoom (x86_64 only), and EasyEffects loudness normalization - **[`gaming.nix`](./gaming.nix)**: Gaming applications - **[`creative.nix`](./creative.nix)**: Creative software - **[`note-taking.nix`](./note-taking.nix)**: Note-taking apps ### Development -- **[`dev-tools.nix`](./dev-tools.nix)**: Development tools and IDEs +- **[`dev-tools.nix`](./dev-tools.nix)**: Android platform tools (`android-tools`) ### System Utilities diff --git a/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix b/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix index 626dc7c0..3db60a1c 100644 --- a/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix +++ b/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix @@ -90,8 +90,8 @@ } # Disabled: the `playground` role has no password and remote logins # require scram-sha-256, so this datasource could never authenticate. - # Re-enable once a `playground` DB password is provisioned - # (docs/system-audit-2026-09-19.md, item 1.5). + # Re-enable once a `playground` DB password is provisioned (the PostgreSQL + # per-role password follow-up in SECURITY.md). /* { # https://grafana.com/docs/grafana/latest/datasources/postgres/configure/ diff --git a/hosts/12kingdoms-youko/homelab-services/postgresql.nix b/hosts/12kingdoms-youko/homelab-services/postgresql.nix index 3b9ecd33..3dafa42a 100644 --- a/hosts/12kingdoms-youko/homelab-services/postgresql.nix +++ b/hosts/12kingdoms-youko/homelab-services/postgresql.nix @@ -96,10 +96,11 @@ in # "local" is for Unix domain socket connections only local all all peer map=superuser_map - # TCP loopback: application roles may still connect without a password - # (Immich, the exporter), but the superuser and replication may not. That - # is the path a compromised local service would take. No password is set - # for postgres, so these lines deny it over TCP. + # TCP loopback: application roles may still connect without a password, + # but the superuser and replication may not. That is the path a + # compromised local service would take. No password is set for postgres, + # so these lines deny it over TCP. Immich and the exporter use the Unix + # socket above, so no configured client needs the loopback trust lines. host all postgres 127.0.0.1/32 scram-sha-256 host all postgres ::1/128 scram-sha-256 host all all 127.0.0.1/32 trust diff --git a/modules/nixos/desktop/gaming.nix b/modules/nixos/desktop/gaming.nix index d6027f09..4af537fc 100644 --- a/modules/nixos/desktop/gaming.nix +++ b/modules/nixos/desktop/gaming.nix @@ -17,7 +17,7 @@ in options.modules.desktop = { gaming = { - enable = mkEnableOption "Enable desktop gaming support"; + enable = mkEnableOption "desktop gaming support"; }; }; diff --git a/nixos-installer/Justfile b/nixos-installer/Justfile index b968cfc9..73248bf9 100644 --- a/nixos-installer/Justfile +++ b/nixos-installer/Justfile @@ -2,7 +2,7 @@ default: @just --list -# Enter a shell session which has all the necessary tools for this flake +# Build the ai host's system closure to check this flake [linux] test: nix build ".#nixosConfigurations.ai.config.system.build.toplevel" --show-trace --verbose diff --git a/nixos-installer/README.md b/nixos-installer/README.md index a471ed2b..1e118725 100644 --- a/nixos-installer/README.md +++ b/nixos-installer/README.md @@ -84,6 +84,10 @@ nixos-enter # mv /etc/ssh /persistent/etc/ # Then exit and: exit +# keep this checkout (still in nix-config/nixos-installer); / is tmpfs, so put it where +# preservation mounts ~/nix-config from +mkdir -p /mnt/persistent/home/ryan +cp -r .. /mnt/persistent/home/ryan/nix-config umount -R /mnt reboot ``` @@ -115,7 +119,6 @@ After the first boot: 3. Deploy the main config: ```bash - sudo mv /etc/nixos ~/nix-config sudo chown -R ryan:ryan ~/nix-config cd ~/nix-config just niri diff --git a/nixos-installer/README.shoukei.md b/nixos-installer/README.shoukei.md index 3d8766de..bdc03fdf 100755 --- a/nixos-installer/README.shoukei.md +++ b/nixos-installer/README.shoukei.md @@ -236,7 +236,7 @@ Then, generate the NixOS configuration: nixos-generate-config --root /mnt # we need to update our filesystem configs in old hardware-configuration.nix according to the generated one. -cp /etc/nixos/hardware-configuration.nix ./nix-config/hosts/12kingdoms-shoukei/hardware-configuration-new.nix +cp /mnt/etc/nixos/hardware-configuration.nix ./nix-config/hosts/12kingdoms-shoukei/hardware-configuration-new.nix vim ./nix-config ``` @@ -278,7 +278,7 @@ mv /etc/ssh /persistent/etc/ exit # delete the generated configuration after editing -rm -f /mnt/etc/nixos +rm -rf /mnt/etc/nixos rm ~/nix-config/hosts/12kingdoms-shoukei/hardware-configuration-new.nix # NOTE: `cat README.shoukei.md | grep git-1 > git-1.sh` to generate this script @@ -287,8 +287,10 @@ git config --global user.email "ryan4yin@linux.com" # git-1 git config --global user.name "Ryan Yin" # git-1 git commit -am "feat: update hardware-configuration" -# copy our configuration to the installed file system -cp -r ~/nix-config /mnt/etc/nixos +# copy our configuration to the installed file system; / is tmpfs there, so put it where +# preservation mounts ~/nix-config from +mkdir -p /mnt/persistent/home/ryan +cp -r ~/nix-config /mnt/persistent/home/ryan/nix-config # sync the disk, unmount the partitions, and close the encrypted device sync @@ -319,7 +321,6 @@ decrypt them automatically on the new host when I deploy my NixOS configuration. After all these steps, we can finally deploy the main flake's NixOS configuration by: ```bash -sudo mv /etc/nixos ~/nix-config sudo chown -R ryan:ryan ~/nix-config cd ~/nix-config diff --git a/outputs/README.md b/outputs/README.md index df12aaf0..9b722b00 100644 --- a/outputs/README.md +++ b/outputs/README.md @@ -47,10 +47,10 @@ Some tests iterate over every configuration, so a new host is covered automatica them. Others name specific hosts — or a name prefix, like `k3s-api-endpoint`, which covers every `k3s-test-1-*` configuration — and only cover a new host if you add it. -| Kind | Tests | -| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Every configuration | `hostname`, `kernel`, `nix-system-features`, `security-apparmor`, `security-container-groups`, `security-exporters`, `security-firewall`, `security-k3s-kubeconfig`, `security-k3s-pod-firewall`, `security-kernel`, `security-ssh-x11` | -| Named hosts only | `btrbk`, `cpu-power`, `home-manager`, `home-manager-xdg`, `computer-use-headless`, `k3s-api-endpoint`, `k3s-node-home-manager`, `idols-ai-gpu`, `idols-ai-net`, `idols-ai-voice-input`, `idols-aquamarine`, `security-postgres-auth`, `youko-metrics`, `ups-metrics`, `shoukei-logind` | +| Kind | Tests | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Every configuration | `hostname`, `kernel`, `nix-system-features`, `security-apparmor`, `security-container-groups`, `security-exporters`, `security-firewall`, `security-k3s-kubeconfig`, `security-k3s-pod-firewall`, `security-kernel`, `security-ssh-x11` | +| Named hosts only | `btrbk`, `cpu-power`, `home-manager`, `home-manager-xdg`, `computer-use-headless`, `k3s-api-endpoint`, `k3s-node-home-manager`, `idols-ai-gpu`, `idols-ai-net`, `idols-ai-voice-input`, `idols-aquamarine`, `security-postgres-auth`, `youko-metrics`, `ups-metrics`, `shoukei-logind`, `mihomo-dns`, `vm-host-power`, `colmena-parity` | `hostname` expects each configuration's `networking.hostName` to equal its name. Niri desktop configurations are the exception (`ai-niri` → `ai`, `shoukei-niri` → `shoukei`) and are listed in @@ -107,6 +107,7 @@ All the outputs of this flake are defined here. │ ├── 12kingdoms-shushou.nix │ ├── 12kingdoms-youko.nix │ ├── idols-ai.nix + │ ├── idols-aquamarine.nix │ ├── idols-kana.nix │ ├── idols-ruby.nix │ ├── k3s-test-1-master-1.nix diff --git a/scripts/shoukei-home-wifi-static.nu b/scripts/shoukei-home-wifi-static.nu index 6d4d8694..bcf5a63d 100644 --- a/scripts/shoukei-home-wifi-static.nu +++ b/scripts/shoukei-home-wifi-static.nu @@ -6,7 +6,7 @@ # HOME_SSID: the script refuses to run unless the current Wi-Fi network is that # exact SSID, so it can never pin the wrong network by accident. Every other # network keeps using DHCP. The change is stored in the profile under -# /etc/NetworkManager/system-connections (preserved by impermanence), so it +# /etc/NetworkManager/system-connections (preserved by preservation), so it # survives reboots and rebuilds -- no need to run it again. # # just shoukei-home-wifi diff --git a/secrets/nixos.nix b/secrets/nixos.nix index 38415332..c45743c7 100644 --- a/secrets/nixos.nix +++ b/secrets/nixos.nix @@ -268,8 +268,8 @@ in mode = "0400"; owner = "root"; }; - # restic repository password (used by modules.restic-backup). Gated - # separately below so desktops get it without enabling a server group. + # restic repository password (used by modules.restic-backup): declared in + # the blocks above, so desktops get their own without a server group. }; # place secrets in /etc/ diff --git a/vars/README.md b/vars/README.md index bf7b1377..157b9c3f 100644 --- a/vars/README.md +++ b/vars/README.md @@ -21,15 +21,20 @@ Contains user information, SSH keys, and password configuration: - Initial hashed password for new installations - SSH authorized keys (main and backup sets) - Public key references for system access +- Remote-builder client host keys (`remoteBuilderClientKeys`) that may build through aquamarine ### 2. `networking.nix` Defines: - **Gateway settings**: Main router and proxy gateway configurations +- **Home LAN** (`lanCidr`): the trusted LAN prefix the shared host firewall trusts by source +- **Cluster addressing**: `clusterULA6`, the IPv6 ULA prefix for MicroVM guest addresses, and + `k8sVip`, each k3s cluster's kube-vip API and LoadBalancer VIP block - **DNS servers**: IPv4 and IPv6 name servers - **Host inventory**: static LAN hosts and their interfaces; DHCP/mobile hosts (such as the macOS - hosts) are omitted by design + hosts) are omitted by design. `homeOnly = true` marks an address that exists only at home; SSH + aliases and monitoring skip it - **SSH configuration**: Host aliases and known hosts configuration - **Network topology**: Physical machines, VMs, Kubernetes clusters, and SBCs