My nix-darwin and NixOS config
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: wait for Tailscale interface before Caddy/CoreDNS start

Race condition: tailscaled.service starting doesn't mean the interface
has its IP yet. Caddy and CoreDNS bind to the Tailscale IP, so they fail
if started before the interface is ready.

Add tailscale-ready.service that blocks until tailscale0 has the expected
IP, then make Caddy and CoreDNS depend on it.

๐Ÿ‘พ Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>

+43 -8
+36 -4
modules/server/infra/network/caddy.nix
··· 35 35 ''; 36 36 }; 37 37 38 + # โ”€โ”€ Wait for Tailscale interface to be ready โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 39 + # tailscaled.service starting doesn't mean the interface has its IP yet. 40 + # This service blocks until the Tailscale interface is up and has the expected IP. 41 + systemd.services.tailscale-ready = lib.mkIf hasTailnet { 42 + description = "Wait for Tailscale interface to be ready"; 43 + after = [ "tailscaled.service" ]; 44 + wants = [ "tailscaled.service" ]; 45 + wantedBy = [ "multi-user.target" ]; 46 + before = [ "caddy.service" ]; 47 + serviceConfig = { 48 + Type = "oneshot"; 49 + RemainAfterExit = true; 50 + }; 51 + script = '' 52 + echo "Waiting for Tailscale interface to be ready..." 53 + for i in $(seq 1 30); do 54 + if ip addr show tailscale0 2>/dev/null | grep -q "${cfg.server.tailscaleIP}"; then 55 + echo "Tailscale interface ready with IP ${cfg.server.tailscaleIP}" 56 + exit 0 57 + fi 58 + sleep 1 59 + done 60 + echo "Timed out waiting for Tailscale interface" 61 + exit 1 62 + ''; 63 + }; 64 + 38 65 # โ”€โ”€ Caddy systemd service tweaks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 39 66 systemd.services.caddy = { 40 67 serviceConfig = { ··· 42 69 RestartSec = lib.mkDefault "5s"; 43 70 }; 44 71 # Ensure the ACME wildcard cert exists before Caddy starts, that 45 - # Tailscaled is up, and that network-online.target has been reached so 46 - # the Tailscale interface (and its bind address) actually exists. 72 + # Tailscaled is up, and that the Tailscale interface is ready with its IP. 47 73 after = [ 48 74 "tailscaled.service" 49 75 "network-online.target" 50 76 ] 51 - ++ lib.optional hasTailnet "acme-ewancroft.uk.service"; 77 + ++ lib.optionals hasTailnet [ 78 + "tailscale-ready.service" 79 + "acme-ewancroft.uk.service" 80 + ]; 52 81 wants = [ 53 82 "tailscaled.service" 54 83 "network-online.target" 55 84 ] 56 - ++ lib.optional hasTailnet "acme-ewancroft.uk.service"; 85 + ++ lib.optionals hasTailnet [ 86 + "tailscale-ready.service" 87 + "acme-ewancroft.uk.service" 88 + ]; 57 89 }; 58 90 59 91 # โ”€โ”€ ACME wildcard cert for tailnet vhosts โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+7 -4
modules/server/infra/network/split-dns.nix
··· 71 71 }; 72 72 73 73 # CoreDNS systemd ordering and restart config. 74 - # Bind address is the Tailscale IP, so we must wait for both tailscaled 75 - # and network-online.target before starting. RestartSec gives Tailscale 76 - # enough time to bring up the interface before CoreDNS retries. 74 + # Bind address is the Tailscale IP, so we must wait for the Tailscale 75 + # interface to be ready before starting. 77 76 systemd.services.coredns = { 78 77 after = [ 79 78 "tailscaled.service" 80 79 "network-online.target" 80 + "tailscale-ready.service" 81 81 ]; 82 - wants = [ "network-online.target" ]; 82 + wants = [ 83 + "network-online.target" 84 + "tailscale-ready.service" 85 + ]; 83 86 serviceConfig = { 84 87 Restart = lib.mkForce "on-failure"; 85 88 RestartSec = lib.mkDefault "10s";