{ config, lib, pkgs, ... }: let serviceName = "netbird"; portUtils = import ../../../../lib/port-utils.nix {inherit lib;}; servicePort = portUtils.getPort serviceName "AZ-CLD-1"; domain = "v.az-gruppe.com"; proxyDomain = "p.az-gruppe.com"; ipBase = "10.89.0"; ipOffset = 50; # Derived IPs gatewayIp = "${ipBase}.1"; dashboardIp = "${ipBase}.${toString ipOffset}"; serverIp = "${ipBase}.${toString (ipOffset + 1)}"; proxyIp = "${ipBase}.${toString (ipOffset + 2)}"; # Database configuration dbName = "netbird"; dbUser = "netbird"; dbHost = gatewayIp; # NetBird config as Nix attribute set netbirdConfig = { server = { listenAddress = ":80"; exposedAddress = "https://${domain}:443"; stunPorts = [3478]; metricsPort = 9090; healthcheckAddress = ":9000"; logLevel = "info"; logFile = "console"; dataDir = "/var/lib/netbird"; auth = { issuer = "https://${domain}/oauth2"; localAuthDisabled = true; signKeyRefreshEnabled = true; dashboardRedirectURIs = [ "https://${domain}/nb-auth" "https://${domain}/nb-silent-auth" ]; cliRedirectURIs = ["http://localhost:53000/"]; }; reverseProxy = { trustedHTTPProxies = ["${gatewayIp}/32"]; }; # Proxy Feature proxy = { enabled = true; domain = proxyDomain; }; store = { engine = "postgres"; postgres = { host = dbHost; port = 5432; database = dbName; username = dbUser; }; }; }; }; # Generate YAML config yamlFormat = pkgs.formats.yaml {}; configYamlBase = yamlFormat.generate "netbird-config-base.yaml" netbirdConfig; # Script to inject secrets at runtime configGenScript = pkgs.writeShellScript "netbird-gen-config" '' set -euo pipefail AUTH_SECRET=$(cat "$1") DB_PASSWORD=$(cat "$2") ENCRYPTION_KEY=$(cat "$3") ${pkgs.yq-go}/bin/yq eval " .server.authSecret = \"$AUTH_SECRET\" | .server.store.encryptionKey = \"$ENCRYPTION_KEY\" | .server.store.postgres.password = \"$DB_PASSWORD\" " ${configYamlBase} ''; in { age.secrets."${serviceName}-auth-secret".file = ../../../../secrets/${serviceName}-auth-secret.age; age.secrets."${serviceName}-db-password".file = ../../../../secrets/${serviceName}-db-password.age; age.secrets."${serviceName}-encryption-key".file = ../../../../secrets/${serviceName}-encryption-key.age; age.secrets."${serviceName}-dashboard-env".file = ../../../../secrets/${serviceName}-dashboard-env.age; age.secrets."${serviceName}-server-env".file = ../../../../secrets/${serviceName}-server-env.age; age.secrets."${serviceName}-proxy-env".file = ../../../../secrets/${serviceName}-proxy-env.age; # Systemd oneshot service to generate config with secrets systemd.services."${serviceName}-config" = { description = "Generate NetBird config with secrets"; wantedBy = ["multi-user.target"]; before = ["podman-${serviceName}-server.service"]; requiredBy = ["podman-${serviceName}-server.service"]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = pkgs.writeShellScript "netbird-write-config" '' mkdir -p /var/lib/${serviceName} ${configGenScript} \ ${config.age.secrets."${serviceName}-auth-secret".path} \ ${config.age.secrets."${serviceName}-db-password".path} \ ${config.age.secrets."${serviceName}-encryption-key".path} \ > /var/lib/${serviceName}/config.yaml chmod 600 /var/lib/${serviceName}/config.yaml ''; }; }; virtualisation.oci-containers.containers = { "${serviceName}-dashboard" = { image = "netbirdio/dashboard:latest"; autoStart = true; ports = ["127.0.0.1:${toString servicePort}:80"]; environmentFiles = [config.age.secrets."${serviceName}-dashboard-env".path]; extraOptions = [ "--ip=${dashboardIp}" "--network=web" ]; }; "${serviceName}-server" = { image = "netbirdio/netbird-server:latest"; autoStart = true; ports = ["3478:3478/udp"]; environmentFiles = [config.age.secrets."${serviceName}-server-env".path]; volumes = [ "${serviceName}_data:/var/lib/netbird" "/var/lib/${serviceName}/config.yaml:/etc/netbird/config.yaml:ro" ]; cmd = ["--config" "/etc/netbird/config.yaml"]; extraOptions = [ "--ip=${serverIp}" "--network=web" ]; }; "${serviceName}-proxy" = { image = "netbirdio/reverse-proxy:latest"; autoStart = true; ports = ["51820:51820/udp"]; volumes = [ "${serviceName}_proxy_certs:/certs" ]; environmentFiles = [config.age.secrets."${serviceName}-proxy-env".path]; cmd = [ "--domain=${proxyDomain}" "--mgmt=https://${domain}:443" "--addr=:8443" "--cert-dir=/certs" "--acme-certs" "--trusted-proxies=${gatewayIp}/32" ]; dependsOn = ["${serviceName}-server"]; extraOptions = [ "--ip=${proxyIp}" "--network=web" ]; }; }; services.traefik.dynamicConfigOptions = { # HTTP services and routers http = { services = { "${serviceName}-dashboard".loadBalancer.servers = [ {url = "http://localhost:${toString servicePort}/";} ]; "${serviceName}-server".loadBalancer.servers = [ {url = "http://${serverIp}:80/";} ]; "${serviceName}-server-h2c".loadBalancer.servers = [ {url = "h2c://${serverIp}:80";} ]; }; routers = { # gRPC (Signal + Management) "${serviceName}-grpc" = { rule = "Host(`${domain}`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`))"; entrypoints = "websecure"; tls.certResolver = "ionos"; service = "${serviceName}-server-h2c"; priority = 100; }; # Backend (relay, WebSocket, API, OAuth2) "${serviceName}-backend" = { rule = "Host(`${domain}`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`))"; entrypoints = "websecure"; tls.certResolver = "ionos"; service = "${serviceName}-server"; priority = 100; }; # Dashboard (catch-all, lowest priority) "${serviceName}-dashboard" = { rule = "Host(`${domain}`)"; entrypoints = "websecure"; tls.certResolver = "ionos"; service = "${serviceName}-dashboard"; priority = 1; }; }; }; # TCP for proxy TLS passthrough tcp = { services."${serviceName}-proxy-tls".loadBalancer.servers = [ {address = "${proxyIp}:8443";} ]; routers."${serviceName}-proxy-passthrough" = { entryPoints = ["websecure"]; rule = "HostSNI(`*`)"; service = "${serviceName}-proxy-tls"; priority = 1; tls.passthrough = true; }; }; # ServersTransport for proxy protocol v2 (optional) serversTransports."pp-v2" = { proxyProtocol.version = 2; }; }; networking.firewall.allowedUDPPorts = [ 3478 # STUN 51820 # WireGuard for proxy ]; }