forked from lukas/nixos-config
Restarting the headscale broker (and blunix itself) left tailscaled, headscale, and traefik stuck waiting on each other: traefik's web-internal/ssh entrypoints can't bind until tailscale0 has an IP, tailscaled can't authenticate until headscale is reachable through traefik's public entrypoint, and headscale can't start until dex's OIDC discovery is reachable through traefik. net.ipv4.ip_nonlocal_bind lets traefik bind those entrypoints before tailscale0 exists, so the public entrypoint always comes up and the cycle can't deadlock again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
123 lines
3.5 KiB
Nix
123 lines
3.5 KiB
Nix
{config, ...}:
|
|
{
|
|
networking.firewall.allowedTCPPorts = [22 80 443 2222 5222];
|
|
|
|
# web-internal/ssh binden an tailnetIPs.blunix, das erst existiert, sobald
|
|
# tailscaled eingeloggt ist; tailscaled wiederum braucht headscale/dex über
|
|
# den websecure-Entrypoint. Ohne diesen Sysctl blockiert ein gleichzeitiger
|
|
# Neustart von traefik/tailscaled/headscale sich gegenseitig dauerhaft
|
|
# (verifiziert am 29.07.2026). ip_nonlocal_bind erlaubt den Bind schon
|
|
# bevor die Adresse auf einem Interface liegt; der Listener wird aktiv,
|
|
# sobald tailscale0 die IP später bekommt.
|
|
boot.kernel.sysctl."net.ipv4.ip_nonlocal_bind" = 1;
|
|
|
|
services.traefik = {
|
|
enable = true;
|
|
staticConfigOptions = {
|
|
log = {
|
|
level = "TRACE";
|
|
};
|
|
api = {}; # enable API handler
|
|
entryPoints = {
|
|
web = {
|
|
address = ":80";
|
|
http.redirections.entryPoint = {
|
|
to = "websecure";
|
|
scheme = "https";
|
|
};
|
|
};
|
|
web-internal = {
|
|
address = "${config.tailnetIPs.blunix}:443";
|
|
};
|
|
websecure = {
|
|
address = "${config.publicIPv4}:443";
|
|
# forwardedHeaders = {
|
|
# insecure = true;
|
|
# };
|
|
};
|
|
xmpp = {
|
|
address= ":5222";
|
|
};
|
|
ssh = { address = "${config.tailnetIPs.blunix}:2222"; }; # TCP EntryPoint für SSH
|
|
};
|
|
certificatesResolvers = {
|
|
cloudflare = {
|
|
acme = {
|
|
email = config.adminEmail;
|
|
storage = "/var/lib/traefik/acme.json";
|
|
caserver = "https://acme-v02.api.letsencrypt.org/directory";
|
|
dnsChallenge = {
|
|
provider = "cloudflare";
|
|
resolvers = [ "1.1.1.1:53" "8.8.8.8:53" ];
|
|
propagation.delayBeforeChecks = 60; # Important: Increase delay for slow DNS propagation
|
|
};
|
|
};
|
|
};
|
|
};
|
|
tls.certificates = [
|
|
{
|
|
certResolver = "cloudflare";
|
|
domains = [
|
|
{ main = "${config.domain}"; sans = [ "*.${config.domain}" ]; }
|
|
];
|
|
}
|
|
];
|
|
};
|
|
dynamicConfigOptions = {
|
|
tcp = {
|
|
routers = {
|
|
|
|
forgejo-ssh = {
|
|
entryPoints = [ "ssh" ]; # TCP-EntryPoint
|
|
rule = "HostSNI(`*`)"; # SSH hat kein SNI, also Wildcard
|
|
service = "forgejo-ssh";
|
|
};
|
|
xmpp-service = {
|
|
service = "xmpp-service";
|
|
rule = "HostSNI(`*`)";
|
|
entryPoints = ["xmpp"];
|
|
};
|
|
};
|
|
services = {
|
|
forgejo-ssh = {
|
|
loadBalancer = {
|
|
servers = [
|
|
{ address = "${config.tailnetIPs.pinix}:2222"; } # Pi mit SSH-Port 2222
|
|
];
|
|
};
|
|
};
|
|
xmpp-service = {
|
|
loadbalancer = {
|
|
servers = [ { address = "${config.tailnetIPs.pinix}:5222";}];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
http = {
|
|
middlewares = {
|
|
auth = {
|
|
basicAuth = {
|
|
usersFile = config.age.secrets.traefikBasicAuth.path;
|
|
};
|
|
};
|
|
};
|
|
routers = {
|
|
api = {
|
|
rule = "Host(`traefik.${config.domain}`)";
|
|
service = "api@internal";
|
|
entrypoints = ["websecure"];
|
|
middlewares = ["auth"];
|
|
tls = {
|
|
certResolver = "cloudflare";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
systemd.services.traefik.serviceConfig = {
|
|
EnvironmentFile = ["${config.age.secrets.traefik.path}"];
|
|
};
|
|
|
|
}
|