headscale: add OIDC login, stop overriding clients' local DNS
Two changes to the control server, plus the fallout on the hosts. OIDC via Authentik, so `tailscale up --login-server ...` opens a browser instead of needing a pre-auth key. This is a second Authentik application, separate from headplane's, with headscale's own /oidc/callback redirect. Headless hosts keep using pre-auth keys. Note that headscale runs OIDC discovery at startup and a failure is FATAL -- pointing `issuer` at an application that does not exist yet means the control server will not boot, so verify the discovery document before deploying. override_local_dns = false, because the upstream default of true replaces resolv.conf with 100.100.100.100 on every node. That silently broke the LAN's `.sol` names -- pihole serves those and the global nameservers return NXDOMAIN for them -- and took ad blocking down with them. It also made each node's entire DNS depend on tailscaled, which is what had forced --accept-dns=false onto neptun and mercury individually; both of those workarounds are now removed, and with MagicDNS resolving properly again neptun no longer needs its hardcoded /etc/hosts pin for jupiter. Also serves jellyfin and seerr from jupiter, matching the ports they already use on its LAN vhosts, and rotates the tailnet pre-auth keys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,19 +28,9 @@
|
||||
networking.interfaces.eth0.ipv6.addresses = [
|
||||
{ address = "fd18:df17:9078:0::10"; prefixLength = 64; }
|
||||
];
|
||||
# VERIFY with `ip route` on the Pi — assuming the router is .1.
|
||||
networking.defaultGateway = { address = "10.0.0.1"; interface = "eth0"; };
|
||||
# Resolvers for the Pi's OWN lookups (not the LAN service). Kept public so the
|
||||
# host resolves during boot without depending on its own pihole/unbound.
|
||||
networking.nameservers = [ "1.1.1.1" "9.9.9.9" ];
|
||||
|
||||
# Never take MagicDNS on THIS host. headscale pushes override_local_dns, so
|
||||
# accepting it would repoint mercury's resolv.conf at 100.100.100.100 and
|
||||
# make the LAN's DNS server depend on tailscaled to resolve anything — the
|
||||
# same boot-time DNS deadlock this host already hit once (see CLAUDE.md),
|
||||
# except a failure here takes the whole network's DNS down with it.
|
||||
services.tailscale.extraUpFlags = [ "--accept-dns=false" ];
|
||||
|
||||
# ---- pihole web admin password (from sops) ----
|
||||
# The pihole container reads FTLCONF_* env vars. Render an env file from the
|
||||
# sops secret and feed it to the container — password stays out of repo/store.
|
||||
|
||||
@@ -43,20 +43,6 @@
|
||||
networking.defaultGateway6 = { address = "fe80::1"; interface = "eth0"; };
|
||||
networking.nameservers = [ "9.9.9.9" "1.1.1.1" "2620:fe::fe" ];
|
||||
|
||||
# ---- Don't take MagicDNS from our own control server ----
|
||||
# headscale pushes override_local_dns, so joining the tailnet would point
|
||||
# neptun's resolv.conf at a MagicDNS served by the tailscaled neptun itself
|
||||
# hosts. A tailscaled failure would then also kill DNS, and with it ACME
|
||||
# renewal — expiring the certs for the very control server every other node
|
||||
# needs to recover. Keep the public resolvers above and resolve the one
|
||||
# tailnet name we proxy to from /etc/hosts instead.
|
||||
services.tailscale.extraUpFlags = [ "--accept-dns=false" ];
|
||||
|
||||
# ⚠️ PLACEHOLDER IP — replace once jupiter first enrols, with its address
|
||||
# from `headscale nodes list` on this box. Until then the audiobookshelf
|
||||
# vhost and the gitea SSH forward below both fail to connect.
|
||||
networking.hosts."100.64.0.2" = [ "jupiter.hosts.mgaction.town" ];
|
||||
|
||||
# firewall (enable + 22), caddy (80/443), tailscale (trust tailscale0 + join
|
||||
# headscale) come from ../../common.nix and ../../services/{caddy,tailscale}.nix.
|
||||
|
||||
@@ -71,21 +57,49 @@
|
||||
# ---- Public reverse proxy vhosts ----
|
||||
# Caddy gets automatic public HTTPS (Let's Encrypt) for real domains; the
|
||||
# *.mgaction.town wildcard already points every name here (A + AAAA), and
|
||||
# the module opens 80/443. Both of these live on jupiter and are reached
|
||||
# over the tailnet — see the /etc/hosts pin above.
|
||||
#
|
||||
# `abs`, not `audiobookshelf`: that's the name this has always been served
|
||||
# under, and the mobile app is configured with it.
|
||||
# the module opens 80/443. All of these live on jupiter and are reached over
|
||||
# the tailnet by their MagicDNS name, which resolves because headscale no
|
||||
# longer overrides local DNS (see services/vpn/headscale.nix).
|
||||
|
||||
# ---- Audiobookshelf ----
|
||||
services.caddy.virtualHosts."abs.mgaction.town".extraConfig = ''
|
||||
reverse_proxy http://jupiter.hosts.mgaction.town:8000
|
||||
'';
|
||||
|
||||
# ---- Seerr ----
|
||||
services.caddy.virtualHosts."seerr.mgaction.town".extraConfig = ''
|
||||
reverse_proxy http://jupiter.hosts.mgaction.town:5055
|
||||
'';
|
||||
|
||||
# ---- Jellyfin ----
|
||||
services.caddy.virtualHosts."jellyfin.mgaction.town".extraConfig = ''
|
||||
reverse_proxy http://jupiter.hosts.mgaction.town:8096
|
||||
'';
|
||||
|
||||
# ---- Gitea WebUI ----
|
||||
# Gitea's web UI and HTTPS clones (services/dev/gitea.nix, HTTP_PORT 3000).
|
||||
# Its SSH side is the separate :2222 forward further down.
|
||||
services.caddy.virtualHosts."git.mgaction.town".extraConfig = ''
|
||||
reverse_proxy http://jupiter.hosts.mgaction.town:3000
|
||||
'';
|
||||
# The apex mgaction.town is deliberately not served — it returns Caddy's
|
||||
# default 404.
|
||||
|
||||
# ---- Gitea SSH forward ----
|
||||
# Caddy only proxies HTTP; forward :2222 over the tailnet to gitea's own
|
||||
# SSH server on jupiter (services/dev/gitea.nix), so
|
||||
# `ssh://git@git.mgaction.town:2222/...` works. Also needs a matching
|
||||
# inbound-2222 rule in netcup's edge firewall panel (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 ];
|
||||
|
||||
# ---- Authentik (identity/OIDC provider) ----
|
||||
# Runs locally on neptun (see services/identity/authentik.nix); Caddy just
|
||||
@@ -113,23 +127,5 @@
|
||||
}
|
||||
'';
|
||||
|
||||
# ---- Gitea SSH forward ----
|
||||
# Caddy only proxies HTTP; forward :2222 over the tailnet to gitea's own
|
||||
# SSH server on jupiter (services/dev/gitea.nix), so
|
||||
# `ssh://git@git.mgaction.town:2222/...` works. Also needs a matching
|
||||
# inbound-2222 rule in netcup's edge firewall panel (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
|
||||
}
|
||||
|
||||
@@ -43,9 +43,14 @@
|
||||
# placeholders (see services/vpn/headplane.nix) until Authentik/headscale are
|
||||
# actually deployed and those get created for real.
|
||||
#
|
||||
# owner: unlike authentik's EnvironmentFile above, headplane opens these
|
||||
# paths itself, already running as headscale's user — so root:root 0400
|
||||
# would fail and each needs an explicit owner.
|
||||
# owner: unlike authentik's EnvironmentFile above, headscale and headplane
|
||||
# open these paths themselves, already running as the headscale user — so
|
||||
# the root:root 0400 default would fail and each needs an explicit owner.
|
||||
#
|
||||
# headscale's OIDC client is a SEPARATE Authentik application from
|
||||
# headplane's (services/vpn/headscale.nix), hence the second client secret.
|
||||
sops.secrets.headscale_oidc_client_secret.owner = "headscale";
|
||||
|
||||
sops.secrets.headplane_cookie_secret.owner = "headscale";
|
||||
sops.secrets.headplane_oidc_client_secret.owner = "headscale";
|
||||
sops.secrets.headplane_headscale_api_key.owner = "headscale";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
samba_password: ENC[AES256_GCM,data:K3FtKC0CrOLMyfQokmxxlyUnDPo=,iv:9bTE/S/i05LJYldOHuBuz3+g8JdTuq0ysFFgXOmfti8=,tag:77NIxy7qpHRqCmbOR83AfA==,type:str]
|
||||
darman_password: ENC[AES256_GCM,data:DOHHlM4Qdw4WgkN+/M51n2LMjJqq5MS0FeGEH8Pz3297yUZ8jBDOKRS/Tek9stC05JKEQRtn/vVVbAY0nXs20MwrsDMo+IEFXx7Ms90vVyYIYe5O5/0aQkPq84vcwGN0RW1Rj5Y3s38vWA==,iv:DAqHbvOBq7FT7ALbmBXJ0HadEGEsL8V2e7R99H5ZH0s=,tag:9XpiRFIbaZjMn59uHCOA1g==,type:str]
|
||||
tailscale_authkey: ENC[AES256_GCM,data:LjRXyFW1SbhUiXbCQnRKinJluGfQWsjS7TTP/TMvx2noP6aFNrcYMvitG+OqLAlNEQAy0bcxZejZgJN+yNS+5iuy2F7OXhrpO7Df0JHkyMvQpnaH2XBEsw==,iv:QxVkB6GtZOYWFAOFVdBQMU2Hb/2OwBRCLHdfxoDF/ME=,tag:PBL0TV6yZJSu4+TGpycf/A==,type:str]
|
||||
tailscale_authkey: ENC[AES256_GCM,data:dwBoHIJNcRSLxk1rbMsrEHVoYdxdnvYpPq44utfQ8t4XkXEzto96FEctv/QuPYVGgUxdyPczmc0/97m4AAKjaBVGFXpt/XlnFQI2XgzgSJ0qhNWyfJ1F/g==,iv:1b9nMeeZwr4O8cKjxQ0f+j/yxMUBZpicmdXw7bb9hZk=,tag:FWgrKtKgqailQk+BcZNXhw==,type:str]
|
||||
mediamanager_token_secret: ENC[AES256_GCM,data:g75vj1E6B029O076yV3DS/1z99Tq6wMhEVx+ULYDjHsplyA+vqVRvBlviC64V47IMqKd9k2eTGBJ98Ptv3UIjbr446xZinz0c7PZgHU0XOX4EUcB1ORloMFZIv1zUv8VRjUISUQnn/vRk60e7u8eXrOrgXjxfnoBKrdKZmqxhAM=,iv:2t0XBExC9RvbTomezka+99/LtJl64zNwneA7WWc4ju0=,tag:SVCYuw0n6JevnvoQOIkjPw==,type:str]
|
||||
sabnzbd_api_key: ENC[AES256_GCM,data:6UW1u2Ikmnq34t4H4k/4C44SJeFHRlaPjWwUjEfH1GQ=,iv:sGsd8Sd2pfUhTUDg6PlRzfVYejRbF69jmDTIa2fvY4M=,tag:3fOLgU1K1gKHxQ3J1+3oRQ==,type:str]
|
||||
prowlarr_api_key: ENC[AES256_GCM,data:ab1QACaagI8ACJXFUy8r9X9lgYwRo1byUmhBPSRWwKk=,iv:EcuF6EN/4mWxlXi6R1qDzv4rOw6AT+OGSNQaaBwjJHg=,tag:hixHrbQWU6QQZNMM5rNDsg==,type:str]
|
||||
@@ -25,7 +25,7 @@ sops:
|
||||
CzjSDQZTcseEXZNwuzZcfB5Mvq0BQvjOj7lGuxzuE4qwWkdJWGfVLQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1zak7glavmg4026p2389fyqe769vqm4jrryknuqckgqq4merz5f7q44rkkt
|
||||
lastmodified: "2026-07-20T07:51:55Z"
|
||||
mac: ENC[AES256_GCM,data:CBQrHpdIOn35CIrZaqZqZd/V4pp+bMNygXJoYwlOvwAtb49ElqmQgSZxWVgTKpTNZpMkqYie6HQB0c4vsmU/BYeLQKJNYBQvBxIrTQr4wMURj2jBMhtVT2osX5cUpNakoHZUdz6ARXyYGEVOBzMY77vSxk6VkVi5CFmeOETkcik=,iv:a6iTeIlR2S65pTrH+juS6kEhpbiaAmhcPLssasUPZ58=,tag:QP+LMclWU9SO5/uPI/Nwwg==,type:str]
|
||||
lastmodified: "2026-07-20T18:42:14Z"
|
||||
mac: ENC[AES256_GCM,data:l9O/KAd/5KfC+LXwE2y5+ybflyy9Xl0PikoRfcOzIFu+fVWESyi0D22emWen6J4mpvsXgjAp4GCu4RKNTGyy47mfkaHh/KdxozEOmP8VlKOJXtqzU9lTgZwhHqkF9RKsRLKHvmREugwzWnpplJ8J7bg5Z3llBIM36FlElzQ9hUw=,iv:x0lM9Ctcn1XCsXftB9Ze8WV8Rp4YA26MQ/AdGC78MyY=,tag:gtfaSDQAcAkngRw2Ph2N0A==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
darman_password: ENC[AES256_GCM,data:iZQERcXtyH+91yUc3r7U6jnFYrGQPFeCPk/9ZDfxOhPLlGMX3/iEZ+SzZ7a7rDKUeUAaQUsrqANLDLclRYm4Ngo09EkbDxBx5x2GpQwlqSAS45LHnTen9LTzisWghdy79Xnilq322eaB3g==,iv:ozx/BPLR8nZTKHZroKrrh2z6ZlVCuLydQ3aNY4XvcIg=,tag:CSrq5ZipYxtXTT8RintuHQ==,type:str]
|
||||
pihole_webpassword: ENC[AES256_GCM,data:5iOTqD0CcbOCnM1b4+RbajMTyAU=,iv:2ZRW7dshnPzWkuudrn6n92y4Z2n/6fdnBB7BO5/ypS4=,tag:8UZSqJM6dVECQgrF6v5Fuw==,type:str]
|
||||
tailscale_authkey: ENC[AES256_GCM,data:CZe1wkh2DNF2g4wy30Wph0c4/3f1M2K9Up+3oAcLEVdZjJzLRJotAU/0Nw2PSGoDZTtqOSfDaEXsaa45DBIJj2EUvWKSYrkV4XkLCio6ZWoGhmxH9PYfkw==,iv:UqNZz7grRPT/d1a2Tv1PW4H2k5FeZCuDy/fK+jVou9E=,tag:cYbLk+7JqU/1by8j2ri0Xg==,type:str]
|
||||
tailscale_authkey: ENC[AES256_GCM,data:Ogm5RTcbJl5lsL19qgTcqXLWK7ypxXrAHNYgriduQQSSRoRWvsnJ3A2Lbynb+r756WxJyFSFqY3WGr9pHaCYo8LAf5oD1MJE26I7JtsAVcHY0QS29b2MnQ==,iv:3H0YMk6llOBapqtMkxsdlY8wQhzIXa+6W4ZrWD0iq2M=,tag:3rPQS7FX8ODLAQK5gOL7lw==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
@@ -21,7 +21,7 @@ sops:
|
||||
x6FfYadcRfqvSX60l6+TGdzq6xDpxLIZOJ8q19qZsAvB0in50HW5gg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1cpty7zrgnn6l97upq00w5wa8zcvnkxkdt2jvhlj97jh83exure4slha43t
|
||||
lastmodified: "2026-07-20T08:31:19Z"
|
||||
mac: ENC[AES256_GCM,data:kdWGhCOENvnUAnwbLW58JH8Ur+g+f4Z1pr3seZGGOFa04xeXb5HOzegpIH+x7OT5OvsKZsMKWf7sBGgB/nx+m2ZKy4NLC39dLbV8V5hs18NhRKl5vKZBZHE1D2xAK4+REwRYT6LiXArQssPv9e2q5fcW6q3rtnlHMF0tUsWip9o=,iv:BkKv2ZBxZMX4agHTzF2Mcmasxpp+hqkiu9mH0mUeLS4=,tag:t0MeKRPikYR5MAmU8r77sg==,type:str]
|
||||
lastmodified: "2026-07-20T18:41:59Z"
|
||||
mac: ENC[AES256_GCM,data:Ay3JLVmJu9S5wlxyL5mrACgLv+jbAsKB5/5r8/z5m4fPjfaYnBQbn1b2s8G764yI0+L3JVWyAdvdy2Gqplrds87lWqlf+gpJKGOoargsiIZAfJmJEzZOYZLPT8YanvGMHniAJszeZ9CnL7Uf9fn0RpT4ObeT7didSqIds6xVeRs=,iv:whZ9L1+tMTkTZaVUdshghIsS9Gmde4RozMDe3XN1ek8=,tag:015mccmr9A/YW+7E+1CsGg==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.1
|
||||
|
||||
+4
-3
@@ -2,10 +2,11 @@ darman_password: ENC[AES256_GCM,data:7G2Hgh13TxI6ugw2ebp9UtLTQ7HRC/hrCga0FmZo8h8
|
||||
authentik_secret_key: ENC[AES256_GCM,data:qLrAWBywlMqT6D3FYDqE9I/5Ep+zDVu9ns9TA4UVOBSQ1uFGEj01TU9norqPf12pi9/Qs32mVzm5BqDG259DxdGT9DZVQc03QVwiIQYtmWo=,iv:OVCIxIP1Xv+nHmYsrxaPgYWQiwzVPUe8pnbyMBVAuoI=,tag:+ywkwPqkY1Dkx1R5cUJ7PA==,type:str]
|
||||
authentik_bootstrap_email: ENC[AES256_GCM,data:OmqpKAiiFyS/rytnHYRZ,iv:VXiPV5VfduC/IW+E3gDlNAeE+hr+IZ9W7Ty6Npuu59Y=,tag:rjvTHnBxN/pMvpQC5W9S6Q==,type:str]
|
||||
authentik_bootstrap_password: ENC[AES256_GCM,data:QPCY0ni3jBQY5HyK+vRlyT4YTEo=,iv:41u1Jf+WYksPUY4pvKdHQA1RHW21GAfdVDRuQ7XtdYc=,tag:SYmzv26GbE1kwleXyKLG4Q==,type:str]
|
||||
tailscale_authkey: ENC[AES256_GCM,data:t8oKG+oSZrGF1Xb2YX7qWxo/z/dfsq/56kah/cvIdfQihuOSWNuMEWDcph1wlRCgRmNhQE8oqbO61m8xNQuOn+bNHsAEmh9GO9dt+uFuRKdwXJ2vM0nA+Q==,iv:6aq6buNvJ+Y0AUWBLNCdsl+svGGlXzpn5J3Oy7dPDys=,tag:T/lwzHUtyvcbjbKbu6jWeA==,type:str]
|
||||
tailscale_authkey: ENC[AES256_GCM,data:0Rkz9igafuEwHFDNqCaT6bVgL4pF7uyW4PKqimONMfZy4zrrHBO9SFedyrOjmByZxQjmHsb9VmBiHsrStPVnPmMRqZJG5lyuiWjorHcyoG+NxpAo6Az87Q==,iv:UnSWVQOFc/nEbJs5s4rF+F9XYRY0VVpIxoMp+VqKh/o=,tag:Gv6f+s+zwY2jU7NZUx0rrA==,type:str]
|
||||
headplane_cookie_secret: ENC[AES256_GCM,data:oXYRG4z16u6HS7zXoWrV2q/HL2o24n4UwVXnQvqBmbY=,iv:itAiy/w6ue4VzqO5xYnvSYN3uCLLmu52dvAAxZ2pCGc=,tag:MEFP171GpSQpAqUFsWY+VA==,type:str]
|
||||
headplane_oidc_client_secret: ENC[AES256_GCM,data:RiEESz1WHYH/smlJKmKFpPoF5HRYp8gHJoKqc49xRRuU9WnQCnmETie/68u37sSVniQmQZSOmXxdmvuuJgP5LMcPdkrQmQE+QX80RQhtTVvg2AhqRaK4V+qfQleD/aJjoIHojeM3UBUi4kYOhDJF0FFTP+qR4oitH6W8/2jxRsc=,iv:hXquOglPhRv+QTVRw5fkY7BWuwLtJxK5tNERGBOOmt8=,tag:2KczveRWoq7iTOdwjO+AUw==,type:str]
|
||||
headplane_headscale_api_key: ENC[AES256_GCM,data:KcwcprV100wfAkn+YM4+1oTfXkmyeAMAbXNpQKf2iIq0VxwqVep+fORrKmHiwDViciDoyxdMiT4scGlmd4vQRsWEHdDpkuH7MRHYu0hQepE3rSSQAMbK,iv:NEhmi2hiOE+uSIRZ4uXOK1UGpN+FQx/NpooWzTgwik8=,tag:KcPsD4gp51ERaFwgfA0BrQ==,type:str]
|
||||
headscale_oidc_client_secret: ENC[AES256_GCM,data:262lZGgtmuXTuk3He1WyqyjcXrdnIiwNyWlXEYCGKYbKkWe0JPbUdWcwGVlZ+wXt71pzKuRDpZjgdibVTWYd71mohD/Ni6CwJm7dhf517DqznnYrxojZaSxRYXg/sCVSkwEvVseJ5z2yf/Led1VLNd3I1PaUIlwkdm2P91ps7mM=,iv:w/hu7siWwNAIw7g14bake5BP8ouA/b1SZdeukZXa/dw=,tag:vAXqWfSHwIdGAjR78gSpRg==,type:str]
|
||||
caddy_acme_email: ENC[AES256_GCM,data:j+iPz4lt0Gqs2SKHTS53,iv:DQNfcpYy2v93sKeCvXzLOoBhAo0PjZrxHgFD1oVqYu8=,tag:1yPH/2cW/IiRi9lT8dELEg==,type:str]
|
||||
sops:
|
||||
age:
|
||||
@@ -27,7 +28,7 @@ sops:
|
||||
Wptkf76aP9UpjhgNkxzedRebQPB7ti+UiVqCvLVimtuHcsm/NJPcRg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1hp72xyx2cnd05937e4eww95g5kdtn0wsf9j2nypw330pa69gfdxqn0lpkp
|
||||
lastmodified: "2026-07-20T08:15:48Z"
|
||||
mac: ENC[AES256_GCM,data:yDiCTDaIwMCXZfzE4khI7AHNoYi3AHFX+nSXtioNRqLEOu1fBCA3OqHpzTi4Cskv2o7r9nlxx5AGPM+2d3bSHkXW7SJk680IMA/XzfDIKGU6A7+3NDt14/PgxOvPeVTfNWwlWIW9tXPtTVJmnoItTzPsxcLO3OyfbbO+vZhj/j4=,iv:xeqYTLjG0B6yCWoke0lulHetVNlShmw7Nm+8mmwOZ5I=,tag:JUEqRfEprzGMGoGsXp9rKQ==,type:str]
|
||||
lastmodified: "2026-07-20T18:41:45Z"
|
||||
mac: ENC[AES256_GCM,data:Ie+xvOMKI4ARsYEvKc9iKuCahNQWWmjHQ56NpGsPbYF7XMv2HoOor/Nj7VJNhUD8DnoSq5A67qglfmTSk+KB65yMVSeDYJam5hwUzuo+IB9fHC1sWPNiWCVuG1iKh7ruiTaW/r3uA59+vQ2Ww5fFM/tTIuR1wtYtzVJ+5GKB/fA=,iv:5oYnhxlRfUDaxgR0FXPwE1eBrpqh/1eRWKTG8B+BgmQ=,tag:28Kxp4lp6F/mxTNXCCW8ZQ==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ ... }:
|
||||
{ config, ... }:
|
||||
|
||||
# Headscale — self-hosted control server for the tailnet. Every host's
|
||||
# services/vpn/tailscale.nix points --login-server at https://vpn.mgaction.town
|
||||
@@ -19,6 +19,39 @@
|
||||
dns = {
|
||||
base_domain = "hosts.mgaction.town";
|
||||
nameservers.global = [ "1.1.1.1" "9.9.9.9" ];
|
||||
|
||||
# Leave each client's own resolvers alone; only route base_domain to
|
||||
# MagicDNS. Upstream defaults this to true, which replaces resolv.conf
|
||||
# with 100.100.100.100 on every node — that silently breaks the LAN's
|
||||
# `.sol` names (pihole on mercury serves those, and the global
|
||||
# nameservers above return NXDOMAIN for them) and takes ad blocking
|
||||
# with it. It also makes a node's entire DNS depend on tailscaled
|
||||
# being up, which is what forced --accept-dns=false onto neptun and
|
||||
# mercury individually.
|
||||
override_local_dns = false;
|
||||
};
|
||||
|
||||
# Authentik as the login provider, so `tailscale up --login-server ...`
|
||||
# sends you to a browser instead of needing a pre-auth key. This is a
|
||||
# SEPARATE Authentik application from headplane's — its own provider,
|
||||
# slug `headscale`, redirect https://vpn.mgaction.town/oidc/callback
|
||||
# (headscale's own callback; headplane's is under /admin).
|
||||
#
|
||||
# ⚠️ headscale performs OIDC discovery at STARTUP and a failure is
|
||||
# FATAL ("creating OIDC provider from issuer config: 404 Not Found") —
|
||||
# it will not boot, taking the whole tailnet's control plane with it.
|
||||
# Never point `issuer` at an application that doesn't exist yet; verify
|
||||
# with:
|
||||
# curl -s <issuer>.well-known/openid-configuration
|
||||
#
|
||||
# Headless hosts still enrol with pre-auth keys. Note also that users
|
||||
# created here are distinct from `headscale users create` ones: matching
|
||||
# is by the OIDC `sub` claim against the user's providerId, and 0.28
|
||||
# dropped map_legacy_users, so CLI-made users never gain one.
|
||||
oidc = {
|
||||
issuer = "https://auth.mgaction.town/application/o/headscale/";
|
||||
client_id = "14vhRYaLiONHmI2YFIxbQEveJDLu5cCvzSkTb9oq";
|
||||
client_secret_path = config.sops.secrets.headscale_oidc_client_secret.path;
|
||||
};
|
||||
|
||||
# Run our own DERP relay instead of pulling Tailscale's map.
|
||||
|
||||
Reference in New Issue
Block a user