Files
homelab/hosts/neptun/configuration.nix
T
darmanandClaude Sonnet 5 5507dfac0a neptun: add Headscale + Headplane
Headscale is the tailnet control server every host's services/tailscale.nix
already points at (--login-server=https://vpn.mgaction.town). MagicDNS
base_domain "hosts.mgaction.town" matches the "jupiter.hosts.mgaction.town"
names already used in this repo's Caddy vhosts.

Headplane is its web UI, running as headscale's own user (native process
integration, no container). No OIDC wired up - log in with a headscale API
key generated on the box. Both proxied through Caddy; headscale's vhost
needs flush_interval -1 since its node-update endpoint is a long-poll.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 22:36:40 +02:00

97 lines
4.4 KiB
Nix

{ config, pkgs, lib, ... }:
# netcup VPS (UEFI, /dev/vda). Public reverse proxy + tailnet node.
{
imports = [
./hardware-configuration.nix
./disk-config.nix # disko: vda partitions + filesystems
./secrets.nix # sops-nix: tailscale authkey
../../common.nix # shared base: user / ssh / nix / firewall
../../services/caddy.nix
../../services/tailscale.nix
../../services/zitadel.nix
../../services/headscale.nix
../../services/headplane.nix
];
# ---- Boot (UEFI) ----
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Root is on the virtio disk — pin these so stage-1 mounts it regardless of
# what nixos-generate-config detects in the installer.
boot.initrd.availableKernelModules = [ "virtio_pci" "virtio_blk" "virtio_scsi" ];
networking.hostName = "neptun";
# ---- Static networking (netcup) ----
# No LAN fallback: get this right or the box is unreachable (use netcup's
# VNC console / rescue system to fix). Values captured from the running VPS.
networking.useDHCP = false;
networking.usePredictableInterfaceNames = false; # keep the NIC named eth0
networking.interfaces.eth0 = {
ipv4.addresses = [ { address = "159.195.64.117"; prefixLength = 22; } ];
ipv6.addresses = [ { address = "2a0a:4cc0:c2:19e1:44b4:8dff:fe4d:c7d7"; prefixLength = 64; } ];
};
networking.defaultGateway = { address = "159.195.64.1"; interface = "eth0"; };
# netcup IPv6 gateway is conventionally fe80::1 — VERIFY with `ip -6 route`
# on the running VPS; wrong v6 gw won't break v4 reachability.
networking.defaultGateway6 = { address = "fe80::1"; interface = "eth0"; };
# Public resolvers for early boot; tailscale MagicDNS overrides once up.
networking.nameservers = [ "9.9.9.9" "1.1.1.1" "2620:fe::fe" ];
# firewall (enable + 22), caddy (80/443), tailscale (trust tailscale0 + join
# headscale) come from ../../common.nix and ../../services/{caddy,tailscale}.nix.
# ---- Public reverse proxy vhosts ----
# Caddy gets automatic public HTTPS (Let's Encrypt) for real domains.
# Proxies to jupiter's audiobookshelf over the tailnet (MagicDNS name).
# Needs a public A record -> this VPS IP (ports 80/443 opened by the module).
services.caddy.virtualHosts."audiobookshelf.mgaction.town".extraConfig = ''
reverse_proxy http://jupiter.hosts.mgaction.town:8000
'';
# TODO: port your other VPS services' vhosts here before deploying.
# ---- Zitadel (identity/OIDC provider) ----
# Runs locally on neptun (see services/zitadel.nix); Caddy just terminates
# TLS and proxies to it.
services.zitadel.settings.ExternalDomain = "auth.mgaction.town";
services.caddy.virtualHosts."auth.mgaction.town".extraConfig = ''
reverse_proxy http://localhost:8080
'';
# ---- Headscale + Headplane (tailnet control server + its web UI) ----
# Both run locally on neptun (see services/{headscale,headplane}.nix).
# headscale's node-update endpoint is a long-poll, hence `flush_interval
# -1` — without it Caddy buffers the response and clients see stale state.
services.caddy.virtualHosts."vpn.mgaction.town".extraConfig = ''
reverse_proxy http://localhost:8082 {
flush_interval -1
}
'';
services.caddy.virtualHosts."headplane.mgaction.town".extraConfig = ''
reverse_proxy http://localhost:3000
'';
# ---- Gitea SSH forward ----
# Caddy only proxies HTTP; git-over-ssh needs a raw TCP forward. Gitea's
# own built-in SSH server runs on jupiter:2222 (see services/gitea.nix —
# not :222, the unpriv gitea user can't bind <1024). Forward this VPS's
# public :2222 to it over the tailnet, so `ssh://git@git.mgaction.town:2222/...`
# reaches gitea. Needs a matching inbound-2222 rule in netcup's *edge*
# firewall panel too (separate from this box's own, and not managed by Nix).
systemd.services.gitea-ssh-forward = {
description = "Forward :2222 to jupiter's gitea SSH server over tailscale";
after = [ "network-online.target" "tailscaled.service" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
DynamicUser = true;
ExecStart = "${pkgs.socat}/bin/socat TCP-LISTEN:2222,fork,reuseaddr TCP:jupiter.hosts.mgaction.town:2222";
Restart = "always";
};
};
networking.firewall.allowedTCPPorts = [ 2222 ];
system.stateVersion = "26.05"; # set at install time; do NOT bump on upgrades
}