diff --git a/CLAUDE.md b/CLAUDE.md index 73ccc15..ff28eab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -133,9 +133,10 @@ nixosSystem { ``` Platforms: `linode`, `proxmox`, `lxc`. Build types: `minimal`, `nix-cache`, -`server`, `docker`, `gui`, `pxe-boot`. Not every combination is built — e.g. -`pxe-boot` has no `linode` variant (PXE/DHCP/TFTP need LAN L2 adjacency a -Linode VPS doesn't have). Treat `flake.nix`'s `generatedTargets` as the source +`server`, `docker`, `gui`, `pxe-boot`, `tailscale-exit-node`. Not every +combination is built — e.g. `pxe-boot` has no `linode` variant (PXE/DHCP/TFTP +need LAN L2 adjacency a Linode VPS doesn't have). Treat `flake.nix`'s +`generatedTargets` as the source of truth for which hosts exist — `README.md`, `AGENTS.md`, `docs/flake-lock-automation.md`, and the CI eval workflows (`.github/workflows/check-nixos.yml`, `.gitea/workflows/check-nixos.yml`) list diff --git a/README.md b/README.md index 643798a..2287756 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ pieces composed in `flake.nix`: - **Platforms** (what it runs on): `linode`, `proxmox`, `lxc` - **Build types** (what it's for): `minimal`, `nix-cache`, `server`, `docker`, - `gui`, `pxe-boot` + `gui`, `pxe-boot`, `tailscale-exit-node` 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. The full @@ -26,6 +26,7 @@ list: | `linode-docker` / `proxmox-docker` / `lxc-docker` | Docker host for the main container stack (`proxmox-docker` is the real, deployed one — previously the flat `docker` target) | | `linode-gui` / `proxmox-gui` / `lxc-gui` | Cinnamon desktop workstation (`proxmox-gui` is the real, deployed one — previously the flat `nixos` target) | | `proxmox-pxe-boot` / `lxc-pxe-boot` | HTTP/iPXE boot asset host (`proxmox-pxe-boot` is the real, deployed one — previously the flat `pxe-boot` target) | +| `linode-tailscale-exit-node` / `proxmox-tailscale-exit-node` / `lxc-tailscale-exit-node` | Tailscale exit node (no deployed target yet; `lxc-tailscale-exit-node` is the one planned for actual use) | The "(real, deployed)" targets above are also tracked machine-readably in `variables.nix`'s `deployedTargets` — keep both in sync when a deployment diff --git a/flake.nix b/flake.nix index f4beec8..e2f36d7 100644 --- a/flake.nix +++ b/flake.nix @@ -94,6 +94,10 @@ proxmox-pxe-boot = mkTarget { platform = "proxmox"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; }; lxc-pxe-boot = mkTarget { platform = "lxc"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; }; + + linode-tailscale-exit-node = mkTarget { platform = "linode"; buildType = "tailscale-exit-node"; hostPath = ./hosts/tailscale-exit-node/host.nix; }; + proxmox-tailscale-exit-node = mkTarget { platform = "proxmox"; buildType = "tailscale-exit-node"; hostPath = ./hosts/tailscale-exit-node/host.nix; }; + lxc-tailscale-exit-node = mkTarget { platform = "lxc"; buildType = "tailscale-exit-node"; hostPath = ./hosts/tailscale-exit-node/host.nix; }; }; # Auto-install environments (migrated from the former nix-auto-installer diff --git a/hosts/tailscale-exit-node/host.nix b/hosts/tailscale-exit-node/host.nix new file mode 100644 index 0000000..26deb46 --- /dev/null +++ b/hosts/tailscale-exit-node/host.nix @@ -0,0 +1,12 @@ +_: + +{ + networking.hostName = "exit-node"; + + # No networking.hostId: only ZFS-touching hosts (server, docker) need one + # for pool-import safety, and this host does neither. + + # A genuinely new host (not a pre-refactor carry-over), so it tracks the + # flake's current nixpkgs release rather than being pinned to an older one. + system.stateVersion = "26.05"; +} diff --git a/modules/build-types/tailscale-exit-node.nix b/modules/build-types/tailscale-exit-node.nix new file mode 100644 index 0000000..c96ddc0 --- /dev/null +++ b/modules/build-types/tailscale-exit-node.nix @@ -0,0 +1,21 @@ +{ ... }: + +{ + imports = [ + ../tailscale/exit-node.nix + ]; + + # "server", not "both": this build type only ever advertises itself as an + # exit node (see ../tailscale/exit-node.nix) -- it doesn't advertise LAN + # subnet routes, so it doesn't need the "client"-side loose reverse-path + # filtering that "both" would also turn on. Deliberately left unbundled + # from LAN-subnet-route advertisement so this build type stays valid on + # every platform, including linode (a remote VPS with no network path to + # the home LAN at all). + services.tailscale.useRoutingFeatures = "server"; + + # Forwarded exit-node traffic arrives on tailscale0 already + # tailscale-authenticated -- the firewall's normal per-port allow-list + # would otherwise drop it. Standard NixOS/Tailscale exit-node guidance. + networking.firewall.trustedInterfaces = [ "tailscale0" ]; +} diff --git a/modules/tailscale/exit-node.nix b/modules/tailscale/exit-node.nix index 73d9f8a..f23fda8 100644 --- a/modules/tailscale/exit-node.nix +++ b/modules/tailscale/exit-node.nix @@ -1,12 +1,19 @@ -{ vars, ... }: +_: { services.tailscale = { enable = true; - extraUpFlags = [ + # extraSetFlags (tailscale set, via the always-on tailscaled-set + # service), not extraUpFlags -- extraUpFlags is only ever applied by + # tailscaled-autoconnect, which itself only runs when + # services.tailscale.authKeyFile is set (nothing in this repo sets one, + # so tailscale up is a manual, one-time operator step on every host that + # uses this service). extraSetFlags has no such gate, so + # --advertise-exit-node self-reapplies on every boot once the operator + # has authenticated the node once. + extraSetFlags = [ "--advertise-exit-node" - "--advertise-routes=${vars.lanCidr}" ]; }; }