From 1e996d4c05d8b6ac9885111cd07fcff0b11615aa Mon Sep 17 00:00:00 2001 From: NeilClack Date: Sun, 4 Oct 2026 18:12:47 -0500 Subject: [PATCH] Add tailscale --- configuration.nix | 80 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/configuration.nix b/configuration.nix index 58678a8..d429dd8 100644 --- a/configuration.nix +++ b/configuration.nix @@ -11,6 +11,9 @@ let then "/var/lib/radiantwave/current/bin/radiantwave" else "/run/current-system/sw/bin/radiantwave"; myKey = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4qWNZnYU5JaqR9c2qh6oFZktzVNBOx42PfDaGofCL6 cnclack@pm.me macbook" ]; + + # Headscale preauth key. Paste/replace here. + headscaleAuthKey = "hskey-auth-zZdWiKTwDfTT-tgRKiAHyXabykoARDDyNfYihgVrJQ6y6zK7lwV0FW6xwHTdyWoqa_ZpCf80vgAJC"; in { ############################################################ @@ -85,6 +88,81 @@ in networking.networkmanager.enable = true; + ############################################################ + # Tailscale (self-hosted Headscale control plane) + ############################################################ + # The node joins the RadiantWave Headscale server at + # hs.radiantwavetech.com using the *hardware serial* as its Tailscale + # node name (not networking.hostName), so a kiosk is always reachable at + # even though every variant shares one hostName per branch. + # + # The preauth key is set in `headscaleAuthKey` in the let block at the + # top of this file. NOTE: it ends up in the world-readable /nix/store, so + # keep this file out of public repos while a live key is in it. + services.tailscale = { + enable = true; + useRoutingFeatures = "client"; + }; + + # Reach the kiosk's services (SSH, etc.) over the tailnet without extra + # per-port rules. Membership is gated by the preauth key. + networking.firewall.trustedInterfaces = [ "tailscale0" ]; + + systemd.services.radiantwave-tailscale-auth = { + description = "Join the RadiantWave tailnet using the hardware serial as node name"; + after = [ "tailscaled.service" "network-online.target" ]; + wants = [ "tailscaled.service" "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + path = [ pkgs.tailscale pkgs.coreutils pkgs.gnused pkgs.gnugrep ]; + script = '' + set -eu + + # Hardware serial. x86 exposes it via DMI; Raspberry Pi exposes it in + # the device tree (null-terminated), mirrored under /proc/device-tree. + serial="" + for f in \ + /sys/class/dmi/id/product_serial \ + /sys/class/dmi/id/board_serial \ + /sys/firmware/devicetree/base/serial-number \ + /proc/device-tree/serial-number + do + if [ -r "$f" ]; then + serial="$(tr -d '\000' < "$f" | tr -d '\n\r')" + [ -n "$serial" ] && break + fi + done + + # Tailscale node names are RFC 1035 labels: lowercase, [a-z0-9-], <=63. + serial="$(printf '%s' "$serial" \ + | tr '[:upper:]' '[:lower:]' \ + | tr -c 'a-z0-9-' '-' \ + | sed -e 's/-\{1,\}/-/g' -e 's/^-//' -e 's/-$//' \ + | cut -c1-63)" + [ -n "$serial" ] || serial="radiantwave-unknown" + + echo "radiantwave-tailscale-auth: joining as '$serial'" + for _ in $(seq 1 30); do + if tailscale up \ + --auth-key=${pkgs.lib.escapeShellArg headscaleAuthKey} \ + --login-server=https://hs.radiantwavetech.com \ + --hostname="$serial" \ + --accept-dns=false + then + echo "radiantwave-tailscale-auth: connected" + exit 0 + fi + sleep 2 + done + + echo "radiantwave-tailscale-auth: could not join tailnet after retries" >&2 + exit 1 + ''; + }; + system.autoUpgrade = { enable = true; flake = upgradeFlakeRef; @@ -263,4 +341,4 @@ in echo "Backup completed at $(date)" > "$BACKUP_DIR/.backup-timestamp" ''; }; -} +} \ No newline at end of file