From e62e9c9a6a0128e330f02b64e14adf1e10ac6d93 Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Tue, 28 Jul 2026 15:18:06 +1000 Subject: [PATCH] =?UTF-8?q?chore:=20full=20sweep=20=E2=80=94=20docs=20sync?= =?UTF-8?q?,=20SSH=20key=20module=20extraction,=20NFS=20dedup,=20dead=20co?= =?UTF-8?q?de=20removal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documentation fixes: - README/AGENTS: rename tailscale-exit-node → tailscale-router, add ha-server build type and proxmox-ha-server-{1,2} host table rows, add baremetal to platform list, remove references to non-existent flake-target-refactor-spec.md and remove-sensetive-info-refactor.md - docs/auto-installer.md: fix lxc-tailscale-exit-node → lxc-tailscale-router, add pxe-minimal to the flake outputs list - variables.nix: fix domainControllerIp comment — IPA is the authoritative DNS at .253 (Pi-hole is gone), not a forwarding intermediary Code deduplication: - Extract duplicate SSH host-key preservation activation scripts from modules/platforms/lxc.nix and modules/platforms/proxmox.nix into a shared modules/common/preserve-ssh-host-key.nix; both platforms now import it - Replace 8-line hand-enumerated NFS export lists in server.nix and ha-server.nix with a mkNfsExports helper that generates exports from vars.nfsShares — adding a share to variables.nix now propagates to both exporters automatically Dead code removal: - modules/common/configuration.nix: remove leftover NixOS skeleton comments (hardware-configuration import, grub lines) that were never used - modules/docker/enable-service.nix: remove commented-out listenOptions and daemon.settings blocks - hosts/server/host.nix, hosts/nix-cache/host.nix: remove #DOCKER_HOST comments Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 14 ++++---- README.md | 21 ++++++----- docs/auto-installer.md | 8 +++-- hosts/nix-cache/host.nix | 1 - hosts/server/host.nix | 1 - modules/build-types/ha-server.nix | 21 +++++------ modules/build-types/server.nix | 20 +++++------ modules/common/configuration.nix | 17 +++------ modules/common/preserve-ssh-host-key.nix | 35 +++++++++++++++++++ modules/docker/enable-service.nix | 10 ------ modules/platforms/lxc.nix | 44 +----------------------- modules/platforms/proxmox.nix | 33 +----------------- variables.nix | 2 +- 13 files changed, 87 insertions(+), 140 deletions(-) create mode 100644 modules/common/preserve-ssh-host-key.nix diff --git a/AGENTS.md b/AGENTS.md index a84fb12..b8c3e31 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,12 +6,14 @@ This repository contains flake-based NixOS configurations for Wayne's LAN servers and workstation. The flake exposes NixOS configurations named `-` -(platforms: `linode`, `proxmox`, `lxc`; build types: `minimal`, `nix-cache`, -`server`, `docker`, `gui`, `pxe-boot`, `tailscale-exit-node`, `tor-relay`), generated from `modules/platforms/*` -and `modules/build-types/*` by the `mkTarget` function in `flake.nix`. Not -every combination is built — `pxe-boot` has no `linode` variant. See -`README.md` for the full current target list; treat `flake.nix` as the -source of truth since this list can drift. +(platforms: `linode`, `proxmox`, `lxc`, `baremetal`; build types: `minimal`, +`nix-cache`, `server`, `docker`, `gui`, `pxe-boot`, `tailscale-router`, +`tor-relay`, `ha-server`), generated from `modules/platforms/*` and +`modules/build-types/*` by the `mkTarget` function in `flake.nix`. Not every +combination is built — `pxe-boot` has no `linode` variant, `ha-server` only +exists on `proxmox`, and `tor-relay` only exists on `lxc`. See `README.md` +for the full current target list; treat `flake.nix` as the source of truth +since this list can drift. Do not deploy, switch, reboot, repartition, format disks, or run destructive install commands from this repository unless explicitly asked. diff --git a/README.md b/README.md index 40a62b5..2a23a7b 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ pieces composed in `flake.nix`: - **Platforms** (what it runs on): `linode`, `proxmox`, `lxc`, `baremetal` - **Build types** (what it's for): `minimal`, `nix-cache`, `server`, `docker`, - `gui`, `pxe-boot`, `tailscale-exit-node`, `tor-relay` + `gui`, `pxe-boot`, `tailscale-router`, `tor-relay`, `ha-server` Not every combination exists — `pxe-boot` has no `linode` variant, since PXE/DHCP/TFTP need LAN L2 adjacency that a Linode VPS doesn't have, -`tor-relay` currently only exists as `lxc-tor-relay`, and `baremetal` -currently only exists as `baremetal-gui` (the real gui-host hardware). The -full list: +`tor-relay` and `ha-server` currently only exist on `lxc`/`proxmox`, and +`baremetal` currently only exists as `baremetal-gui` (the real gui-host +hardware). The full list: | Target | Purpose | | --- | --- | @@ -29,8 +29,9 @@ full list: | `linode-gui` / `proxmox-gui` / `lxc-gui` | Cinnamon desktop workstation — previously the flat `nixos` target | | `baremetal-gui` | Same Cinnamon desktop workstation, on the real gui-host hardware — ZFS RAID0 root, systemd-boot | | `proxmox-pxe-boot` / `lxc-pxe-boot` | HTTP/iPXE boot asset host — previously the flat `pxe-boot` target | -| `linode-tailscale-exit-node` / `proxmox-tailscale-exit-node` / `lxc-tailscale-exit-node` | Tailscale exit node | +| `linode-tailscale-router` / `proxmox-tailscale-router` / `lxc-tailscale-router` | Tailscale subnet router + MagicDNS forwarder for the LAN | | `lxc-tor-relay` | Tor middle relay | +| `proxmox-ha-server-1` / `proxmox-ha-server-2` | HA file-server cluster nodes — DRBD + XFS + iSCSI + NFS, managed by Corosync + Pacemaker | Which variant of a given buildtype is actually deployed isn't tracked anywhere in this repo — that's live infrastructure state, not something a @@ -47,8 +48,7 @@ section for which is which. Each buildtype's `hosts//host.nix` carries the per-machine identity (hostname, hostId, per-machine secrets, `system.stateVersion`) that must stay -fixed regardless of which platform it's built for — see -`flake-target-refactor-spec.md` for the full rationale. Every deployed host +fixed regardless of which platform it's built for. Every deployed host stamps its own active target name into `/etc/flake-target` at build time, so `nixos-rebuild switch --flake .#$(cat /etc/flake-target)` always picks up the right one even after a platform migration changes the flake attribute name. @@ -167,7 +167,6 @@ per-boot host key for sops-nix to derive from on ephemeral media) — see (`vars/per-machine//openssh/`, committed and sops-encrypted) for their SSH host keys. -This repository's git *history* still contains secrets committed before this -migration (see `remove-sensetive-info-refactor.md`) — those are being -scrubbed and rotated separately; don't treat the repo as safe to make public -until that's finished. +This repository's git *history* still contains secrets committed before the +sops-nix migration — those are being scrubbed and rotated separately; don't +treat the repo as safe to make public until that's finished. diff --git a/docs/auto-installer.md b/docs/auto-installer.md index db13478..a4ff1b8 100644 --- a/docs/auto-installer.md +++ b/docs/auto-installer.md @@ -22,7 +22,7 @@ see "LXC hosts" immediately below for why those are different.** ## LXC hosts `lxc-*` targets (`lxc-minimal`, `lxc-nix-cache`, `lxc-server`, `lxc-docker`, -`lxc-gui`, `lxc-pxe-boot`, `lxc-tailscale-exit-node`, `lxc-tor-relay`) are **not** installed via `auto-install.sh` — the +`lxc-gui`, `lxc-pxe-boot`, `lxc-tailscale-router`, `lxc-tor-relay`) are **not** installed via `auto-install.sh` — the interactive menu deliberately excludes them. Don't try to select one there; `nixos-install` would bind-mount `/` onto `/mnt` (LXC containers have no raw disk to partition) and then refuse to touch the filesystem it's currently @@ -133,13 +133,15 @@ Flake outputs: ```nix nixosConfigurations.installer # ISO/netboot installer image -packages.x86_64-linux.iso # installer ISO/netboot image -packages.x86_64-linux.pxe # netboot-ipxe + netboot-initrd + netboot-kernel, bundled +packages.x86_64-linux.iso # installer ISO/netboot image +packages.x86_64-linux.pxe # auto-installer netboot bundle (kernel + initrd + ipxe script) +packages.x86_64-linux.pxe-minimal # vanilla NixOS minimal netboot bundle (no installer wiring) ``` ```sh nix build .#iso nix build .#pxe +nix build .#pxe-minimal ``` There's no `nixosConfigurations.proxmox-lxc` (installer-boots-as-an-LXC- diff --git a/hosts/nix-cache/host.nix b/hosts/nix-cache/host.nix index 89f6c55..d314f59 100644 --- a/hosts/nix-cache/host.nix +++ b/hosts/nix-cache/host.nix @@ -20,7 +20,6 @@ }; services.beszel.agent.environment = { - #DOCKER_HOST = "tcp://docker-socket-proxy:2375"; KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG"; }; diff --git a/hosts/server/host.nix b/hosts/server/host.nix index 38544d5..344c2d8 100644 --- a/hosts/server/host.nix +++ b/hosts/server/host.nix @@ -21,7 +21,6 @@ }; services.beszel.agent.environment = { - #DOCKER_HOST = "tcp://docker-socket-proxy:2375"; KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG"; EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}"; LOG_LEVEL = "debug"; diff --git a/modules/build-types/ha-server.nix b/modules/build-types/ha-server.nix index 26026ac..c4490a2 100644 --- a/modules/build-types/ha-server.nix +++ b/modules/build-types/ha-server.nix @@ -15,6 +15,16 @@ # under services.beszel.agent.environment.KEY once the hub accepts the # new agents, following the pattern in hosts/server/host.nix. { lib, vars, ... }: + +let + # Generates /etc/exports lines for all nfsShares data entries. Shared + # pattern with modules/build-types/server.nix — both export the same + # set of shares, differing only in the storage root they serve from. + mkNfsExports = storageRoot: + lib.concatMapStrings + (share: " ${storageRoot}/${share.subpath} ${vars.lanCidr}${vars.nfsShares.options}\n") + (lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares)); +in { imports = [ ../ha/pacemaker-stack.nix @@ -25,16 +35,7 @@ services.nfs.server = { enable = true; - exports = '' - ${vars.haStorageRoot}/${vars.nfsShares.dockerConfig.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.haStorageRoot}/${vars.nfsShares.dockerVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.haStorageRoot}/${vars.nfsShares.dockerDatabases.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.haStorageRoot}/${vars.nfsShares.nextcloudData.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.haStorageRoot}/${vars.nfsShares.raspiVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.haStorageRoot}/${vars.nfsShares.proxmoxIsos.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.haStorageRoot}/${vars.nfsShares.proxmoxLxcImages.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.haStorageRoot}/${vars.nfsShares.pxebootImages.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ''; + exports = mkNfsExports vars.haStorageRoot; }; # Pacemaker controls nfs-server — prevent systemd from starting it at boot diff --git a/modules/build-types/server.nix b/modules/build-types/server.nix index 5455065..2bdd522 100644 --- a/modules/build-types/server.nix +++ b/modules/build-types/server.nix @@ -13,6 +13,15 @@ let lib.concatMap (share: ancestors share.subpath) (lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares)) ); + + # Generates /etc/exports lines for all nfsShares data entries (every + # attrset value — excludes the bare `options` string). Both server and + # ha-server export the same share set from different storage roots, so + # this helper is the single source of truth for the export line format. + mkNfsExports = storageRoot: + lib.concatMapStrings + (share: " ${storageRoot}/${share.subpath} ${vars.lanCidr}${vars.nfsShares.options}\n") + (lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares)); in { imports = [ @@ -95,16 +104,7 @@ in services.nfs.server = { enable = true; - exports = '' - ${vars.storageRoot}/${vars.nfsShares.dockerConfig.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.storageRoot}/${vars.nfsShares.dockerDatabases.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.storageRoot}/${vars.nfsShares.nextcloudData.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.storageRoot}/${vars.nfsShares.raspiVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.storageRoot}/${vars.nfsShares.proxmoxIsos.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.storageRoot}/${vars.nfsShares.proxmoxLxcImages.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ${vars.storageRoot}/${vars.nfsShares.pxebootImages.subpath} ${vars.lanCidr}${vars.nfsShares.options} - ''; + exports = mkNfsExports vars.storageRoot; }; # mountd (20048) is needed for showmount/NFSv3 mount protocol — without it diff --git a/modules/common/configuration.nix b/modules/common/configuration.nix index 0144331..4d99bb8 100644 --- a/modules/common/configuration.nix +++ b/modules/common/configuration.nix @@ -25,13 +25,10 @@ let ''; in { - imports = - [ - # Include the results of the hardware scan. - # ./hardware-configuration.nix - ./set-locale.nix - ../ipa/client.nix - ]; + imports = [ + ./set-locale.nix + ../ipa/client.nix + ]; # System-wide shell config so all users (including IPA accounts) get the # same management aliases as the local nixos user's Home Manager provides. @@ -42,11 +39,7 @@ in }; interactiveShellInit = buildImageFn; }; - # Use the GRUB 2 boot loader. - # boot.loader.grub.enable = true; - #boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only - - networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. + networking.networkmanager.enable = true; # Recommended over the true default (bypasses ZFS's own import safeguards) # per the option's own docs; matches hosts/docker/host.nix and diff --git a/modules/common/preserve-ssh-host-key.nix b/modules/common/preserve-ssh-host-key.nix new file mode 100644 index 0000000..ce09490 --- /dev/null +++ b/modules/common/preserve-ssh-host-key.nix @@ -0,0 +1,35 @@ +# Shared activation-script logic to preserve the SSH host key across +# nixos-rebuild on platforms that embed the key via environment.etc (lxc and +# proxmox). When NIXOS_HOST_KEYS_DIR is not set the key is absent from +# environment.etc, and NixOS's etc activation removes any /etc file not in +# the new generation — which would destroy the live key and break sops-nix +# decryption permanently. These scripts save the key to /run before etc +# removes it, then restore it afterward. +# +# Explicit deps enforce the correct ordering: without them the topological +# sort places preserveSshHostKey after etc (confirmed live on lxc-tor-relay: +# position 7 vs etc's position 5), so the key is gone before it can be saved. +_: { + system.activationScripts = { + preserveSshHostKey = '' + if [ -f /etc/ssh/ssh_host_ed25519_key ]; then + cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp + cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp + fi + ''; + + restoreSshHostKey = { + deps = [ "etc" ]; + text = '' + if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then + install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key + install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub + fi + rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp + ''; + }; + + etc = { deps = [ "preserveSshHostKey" ]; }; + setupSecrets = { deps = [ "restoreSshHostKey" ]; }; + }; +} diff --git a/modules/docker/enable-service.nix b/modules/docker/enable-service.nix index 99e1382..2642f93 100644 --- a/modules/docker/enable-service.nix +++ b/modules/docker/enable-service.nix @@ -1,19 +1,9 @@ { pkgs, vars, ... }: { - # virtualisation.docker.enable = true; virtualisation.docker = { enable = true; package = pkgs.docker; - # listenOptions = [ - # "unix:///var/run/docker.sock" - # "tcp://0.0.0.0:2375" - #]; - - # daemon.settings = { - # metrics-addr = "0.0.0.0:9323"; - # experimental = true; - # }; }; users.users.${vars.primaryUser}.extraGroups = [ "docker" ]; environment.systemPackages = with pkgs; [ diff --git a/modules/platforms/lxc.nix b/modules/platforms/lxc.nix index 0eb0f42..c642108 100644 --- a/modules/platforms/lxc.nix +++ b/modules/platforms/lxc.nix @@ -52,6 +52,7 @@ in # LXC container does). imports = [ (modulesPath + "/virtualisation/proxmox-lxc.nix") + ../common/preserve-ssh-host-key.nix ]; proxmoxLXC = { @@ -105,49 +106,6 @@ in }; }; - # NixOS's etc activation removes any /etc file that was in the previous - # generation's environment.etc but is absent from the current one — even - # real (non-symlink) copies. On every routine nixos-rebuild switch/test that - # lacks NIXOS_HOST_KEYS_DIR the key is absent from environment.etc, so it - # gets removed as "obsolete". sops-nix derives its age decryption key from - # /etc/ssh/ssh_host_ed25519_key; deletion cascades into every sops secret - # failing with "Error getting data key: 0 successful groups required, got 0". - # - # Fix: activation scripts that bracket the etc step, with explicit deps - # to enforce the correct ordering. Without deps the topological sort places - # preserveSshHostKey AFTER etc (confirmed live on a deployed lxc-tor-relay: - # position 7 vs etc's position 5) -- the key is already gone by the time it - # tries to save it. The etc/setupSecrets entries ADD to existing deps - # (types.listOf concatenates across module definitions). - system.activationScripts = { - # Saves the live key to /run before etc can delete it. - preserveSshHostKey = '' - if [ -f /etc/ssh/ssh_host_ed25519_key ]; then - cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp - cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp - fi - ''; - - # Reinstalls the key after etc runs if it was removed as "obsolete". - # The resulting file is not registered in environment.etc for either - # generation, so subsequent rebuilds leave it alone permanently. - restoreSshHostKey = { - deps = [ "etc" ]; - text = '' - if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then - install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key - install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub - fi - rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp - ''; - }; - - # Force etc to wait until the key is saved, and sops to wait until the - # key is restored. Without these the topological sort breaks the chain. - etc = { deps = [ "preserveSshHostKey" ]; }; - setupSecrets = { deps = [ "restoreSshHostKey" ]; }; - }; - # virtualisation/proxmox-lxc.nix (imported above) registers the Nix # store DB via a systemd service (register-nix-paths) -- it never runs # an activation script at all. Confirmed live this means neither diff --git a/modules/platforms/proxmox.nix b/modules/platforms/proxmox.nix index 53df069..c427095 100644 --- a/modules/platforms/proxmox.nix +++ b/modules/platforms/proxmox.nix @@ -34,6 +34,7 @@ in ../hardware-configuration/vm/proxmox.nix ../boot/efi.nix ../disko/proxmox.nix + ../common/preserve-ssh-host-key.nix ]; environment.etc = lib.mkIf hasKeyForThisTarget { @@ -46,36 +47,4 @@ in mode = "0644"; }; }; - - # NixOS's etc activation removes any /etc file that was in the previous - # generation's environment.etc but is absent from the current one. Since - # the SSH key is only in environment.etc during the --impure build (when - # NIXOS_HOST_KEYS_DIR is set), normal rebuilds would remove it as - # "obsolete". These scripts mirror lxc.nix's approach: save the live key - # before etc runs, restore it after. Without the explicit deps, the - # topological sort places preserveSshHostKey after etc (confirmed live on - # lxc-tor-relay: position 7 vs etc's position 5), so the key is gone - # before it can be saved. - system.activationScripts = { - preserveSshHostKey = '' - if [ -f /etc/ssh/ssh_host_ed25519_key ]; then - cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp - cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp - fi - ''; - - restoreSshHostKey = { - deps = [ "etc" ]; - text = '' - if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then - install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key - install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub - fi - rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp - ''; - }; - - etc = { deps = [ "preserveSshHostKey" ]; }; - setupSecrets = { deps = [ "restoreSshHostKey" ]; }; - }; } diff --git a/variables.nix b/variables.nix index 45e64b6..2e639a9 100644 --- a/variables.nix +++ b/variables.nix @@ -16,7 +16,7 @@ serverIp = "192.168.2.226"; # server (NFS/ZFS) Proxmox VM LAN IP dockerIp = "192.168.2.225"; # docker Proxmox VM LAN IP pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP (not NixOS-managed) - domainControllerIp = "192.168.2.253"; # FreeIPA domain controller / primary DNS (not NixOS-managed) + domainControllerIp = "192.168.2.253"; # FreeIPA domain controller — authoritative DNS for sweet.home (not NixOS-managed) ipaServer = "domain-controller.sweet.home"; # FreeIPA server hostname (used by security.ipa and Kerberos; must be a resolvable FQDN, not an IP) # Cross-host references (LAN hostnames/users other hosts reach over the network)