From d1cfaf7acf7ac94bb138c3a892314f39d3c66288 Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Sat, 25 Apr 2026 23:57:30 +0200 Subject: [PATCH 1/8] Add netbird-docker volume --- flake.lock | 8 +- modules/netbird-docker.nix | 202 +++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 modules/netbird-docker.nix diff --git a/flake.lock b/flake.lock index 78c0194..e7ad6de 100644 --- a/flake.lock +++ b/flake.lock @@ -338,11 +338,11 @@ "secrets": { "flake": false, "locked": { - "lastModified": 1777076192, - "narHash": "sha256-N7n2OPN2IRWwL73Cr6mc5nNhucHmMeFas9hQ/NF0bFg=", + "lastModified": 1777152364, + "narHash": "sha256-yS8TxtPFFf7xIDNbsErZUnTBLn2fnyCcC4On+t3v1Zs=", "ref": "refs/heads/main", - "rev": "34ff1c4b0460a2e103a8fec183f53f274dc123ed", - "revCount": 32, + "rev": "bfb7da1297d73100a56a044d09792fc6e59357e6", + "revCount": 34, "type": "git", "url": "ssh://git@git.jfreudenberger.de/JuliusFreudenberger/nix-private.git" }, diff --git a/modules/netbird-docker.nix b/modules/netbird-docker.nix new file mode 100644 index 0000000..1ba10b2 --- /dev/null +++ b/modules/netbird-docker.nix @@ -0,0 +1,202 @@ +{ + pkgs, + utils, + config, + lib, + ... +}: +let + + cfg = config.services.netbird-docker; + netbirdCfg = config.services.netbird; + + serverVersion = "0.69.0"; + dashboardVersion = "2.37.1"; + +in { + + options.services.netbird-docker = { + enable = lib.mkEnableOption "Netbird Server stack, comprising the dashboard, management API, signal service, relay and STUN server"; + enableLocalAuth = lib.mkEnableOption "local authentication"; + proxy = lib.mkOption { + description = "Configuration for proxy"; + type = lib.types.submodule { + options = { + domain = lib.mkOption { + description = "Domain the proxy is reachable at. Custom domains will need to add a CNAME record of the wildcard subdomain to this domain."; + type = lib.types.str; + }; + token-secret = lib.mkOption { + description = '' + Proxy token in env-file notation. + Name of the environment variable is `NB_PROXY_TOKEN`. + Create the proxy token after netbird is installed with the following command: docker exec -it netbird-server /go/bin/netbird-server --config /etc/netbird/config.yaml token create --name local + ''; + type = lib.types.anything; + }; + }; + }; + }; + secrets = lib.mkOption { + description = '' + Secret for combined server in env-file notation. + Name of the relevant environment variables: + - NETBIRD_RELAY_AUTH_SECRET - Shared authentication secret for relay + - NETBIRD_DATASTORE_ENC_KEY - Encryption key for sensitive data + ''; + type = lib.types.anything; + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.oci-containers.containers = { + netbird-dashboard = { + image = "netbirdio/dashboard:v${dashboardVersion}"; + autoStart = true; + networks = [ + "webproxy" + ]; + environment = { + NETBIRD_MGMT_API_ENDPOINT = "https://${netbirdCfg.server.management.domain}"; + NETBIRD_MGMT_GRPC_API_ENDPOINT = "https://${netbirdCfg.server.management.domain}"; + AUTH_AUDIENCE="netbird-dashboard"; + AUTH_CLIENT_ID="netbird-dashboard"; + AUTH_CLIENT_SECRET=""; + AUTH_AUTHORITY = "https://${netbirdCfg.server.domain}/oauth2"; + USE_AUTH0="false"; + AUTH_SUPPORTED_SCOPES="openid profile email groups"; + AUTH_REDIRECT_URI="/nb-auth"; + AUTH_SILENT_REDIRECT_URI="/nb-silent-auth"; + NGINX_SSL_PORT="443"; + LETSENCRYPT_DOMAIN="none"; + }; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.netbird-dashboard.rule" = "Host(`${netbirdCfg.server.dashboard.domain}`)"; + "traefik.http.routers.netbird-dashboard.entrypoints" = "websecure"; + "traefik.http.routers.netbird-dashboard.tls" = "true"; + "traefik.http.routers.netbird-dashboard.tls.certresolver" = "letsencrypt"; + "traefik.http.routers.netbird-dashboard.service" = "dashboard"; + "traefik.http.routers.netbird-dashboard.priority" = "1"; + "traefik.http.services.dashboard.loadbalancer.server.port" = "80"; + }; + }; + netbird-server = { + image = "netbirdio/netbird-server:${serverVersion}"; + autoStart = true; + networks = [ + "webproxy" + ]; + entrypoint = "/bin/sh"; + cmd = [ + "-c" + ''sed -e "s|__AUTH_SECRET__|$NETBIRD_RELAY_AUTH_SECRET|" -e "s|__DATASTORE_ENC_KEY__|$NETBIRD_DATASTORE_ENC_KEY|" /etc/netbird/config.yaml.tmpl > /etc/netbird/config.yaml && /go/bin/netbird-server --config /etc/netbird/config.yaml'' + ]; + ports = [ + "3478:3478/udp" + ]; + volumes = let + server-config = (pkgs.formats.yaml {}).generate "netbird-server-config" { + server = { + listenAddress = ":80"; + exposedAddress = "https://${netbirdCfg.server.domain}:443"; + stunPorts = [ 3478 ]; + metricsPort = 9090; + healthCheckAddress = ":9000"; + logLevel = netbirdCfg.server.management.logLevel; + logFile = "console"; + authSecret = "__AUTH_SECRET__"; + dataDir = "/var/lib/netbird"; + auth = { + issuer = "https://${netbirdCfg.server.dashboard.domain}/oauth2"; + localAuthDisabled = !cfg.enableLocalAuth; + signKeyRefreshEnabled = true; + dashboardRedirectURIs = [ + "https://${netbirdCfg.server.dashboard.domain}/nb-auth" + "https://${netbirdCfg.server.dashboard.domain}/nb-silent-auth" + ]; + cliRedirectURIs = [ + "http://localhost:53000" + ]; + }; + reverseProxy.trustedHTTPProxies = [ + "172.18.0.2/32" + ]; + store = { + engine = "sqlite"; + encryptionKey = "__DATASTORE_ENC_KEY__"; + }; + }; + }; + in [ + "${server-config}:/etc/netbird/config.yaml.tmpl" + ]; + environmentFiles = [ + cfg.secrets.path + ]; + extraOptions = [ + ''--mount=type=volume,source=netbird_data,target=/var/lib/netbird,volume-driver=local'' + ]; + labels = { + "traefik.enable" = "true"; + "traefik.http.routers.netbird-grpc.rule" = "Host(`${netbirdCfg.server.signal.domain}`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`))"; + "traefik.http.routers.netbird-grpc.entrypoints" = "websecure"; + "traefik.http.routers.netbird-grpc.tls" = "true"; + "traefik.http.routers.netbird-grpc.tls.certresolver" = "letsencrypt"; + "traefik.http.routers.netbird-grpc.service" = "netbird-server-h2c"; + "traefik.http.routers.netbird-grpc.priority" = "100"; + "traefik.http.routers.netbird-backend.rule" = "Host(`${netbirdCfg.server.domain}`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`))"; + "traefik.http.routers.netbird-backend.entrypoints" = "websecure"; + "traefik.http.routers.netbird-backend.tls" = "true"; + "traefik.http.routers.netbird-backend.tls.certresolver" = "letsencrypt"; + "traefik.http.routers.netbird-backend.service" = "netbird-server"; + "traefik.http.routers.netbird-backend.priority" = "100"; + "traefik.http.services.netbird-server.loadbalancer.server.port" = "80"; + "traefik.http.services.netbird-server-h2c.loadbalancer.server.port" = "80"; + "traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme" = "h2c"; + }; + }; + netbird-proxy = { + image = "netbirdio/reverse-proxy:${serverVersion}"; + autoStart = true; + ports = [ + "51820:51820/udp" + ]; + networks = [ + "webproxy" + ]; + dependsOn = [ + "netbird-server" + ]; + environment = { + NB_PROXY_MANAGEMENT_ADDRESS="http://netbird-server:80"; + NB_PROXY_ALLOW_INSECURE="true"; + NB_PROXY_DOMAIN = cfg.proxy.domain; + NB_PROXY_ADDRESS = ":8443"; + NB_PROXY_CERTIFICATE_DIRECTORY = "/certs"; + NB_PROXY_ACME_CERTIFICATES = "true"; + NB_PROXY_ACME_CHALLENGE_TYPE = "tls-alpn-01"; + NB_PROXY_FORWARDED_PROTO = "https"; + NB_PROXY_PROXY_PROTOCOL = "true"; + NB_PROXY_TRUSTED_PROXIES = "172.18.0.2"; + }; + environmentFiles = [ + cfg.proxy.token-secret.path + ]; + extraOptions = [ + ''--mount=type=volume,source=netbird_proxy_certs,target=/certs,volume-driver=local'' + ]; + labels = { + "traefik.enable" = "true"; + "traefik.tcp.routers.proxy-passthrough.entrypoints" = "websecure"; + "traefik.tcp.routers.proxy-passthrough.rule" = "HostSNI(`*`)"; + "traefik.tcp.routers.proxy-passthrough.tls.passthrough" = "true"; + "traefik.tcp.routers.proxy-passthrough.service" = "proxy-tls"; + "traefik.tcp.routers.proxy-passthrough.priority" = "1"; + "traefik.tcp.services.proxy-tls.loadbalancer.server.port" = "8443"; + "traefik.tcp.services.proxy-tls.loadbalancer.serverstransport" = "pp-v2@file"; + }; + }; + }; + }; +} From f7c4620378d36b51f70dd7793ffaa5e9b5edecb5 Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Sat, 25 Apr 2026 23:58:47 +0200 Subject: [PATCH 2/8] Enable netbird-docker on srv03 --- hosts/srv03/default.nix | 19 +++++++++++++++++++ hosts/srv03/secrets.nix | 2 ++ 2 files changed, 21 insertions(+) diff --git a/hosts/srv03/default.nix b/hosts/srv03/default.nix index b56205c..1cc29bf 100644 --- a/hosts/srv03/default.nix +++ b/hosts/srv03/default.nix @@ -16,6 +16,7 @@ ../../modules/docker.nix ../../modules/traefik.nix ../../modules/pocket-id.nix + ../../modules/netbird-docker.nix ../../modules/auto-upgrade.nix "${inputs.secrets}/modules/opkssh.nix" # Include the results of the hardware scan. @@ -42,6 +43,24 @@ }; environmentFile = config.age.secrets.pocket-id.path; }; + + netbird-docker = { + enable = true; + secrets = config.age.secrets.netbird-server; + proxy = { + domain = "netbird.jfreudenberger.de"; + token-secret = config.age.secrets.netbird-proxy; + }; + }; + netbird.server = let + domain = "netbird.jfreudenberger.de"; + in { + domain = domain; + management.domain = domain; + dashboard.domain = domain; + signal.domain = domain; + management.oidcConfigEndpoint = "https://login.jfreudenberger.de/.well-known/openid-configuration"; + }; }; systemd.network = { diff --git a/hosts/srv03/secrets.nix b/hosts/srv03/secrets.nix index 2a119d0..e7368f7 100644 --- a/hosts/srv03/secrets.nix +++ b/hosts/srv03/secrets.nix @@ -3,5 +3,7 @@ age.secrets = { inwx.file = "${inputs.secrets}/secrets/dns-management/inwx"; pocket-id.file = "${inputs.secrets}/secrets/srv03/pocket-id"; + netbird-server.file = "${inputs.secrets}/secrets/srv03/netbird-server"; + netbird-proxy.file = "${inputs.secrets}/secrets/srv03/netbird-proxy"; }; } From 81f4554dd714861c3d6ac3653291ede5225e1914 Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Mon, 27 Apr 2026 23:07:20 +0200 Subject: [PATCH 3/8] Add dependencies between netbird and traefik containers When setting the explicit ip of the traefik container in the webproxy network, this resolves the ip of the traefik container changing between restarts. --- modules/netbird-docker.nix | 6 ++++++ modules/traefik.nix | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/netbird-docker.nix b/modules/netbird-docker.nix index 1ba10b2..076fdaf 100644 --- a/modules/netbird-docker.nix +++ b/modules/netbird-docker.nix @@ -80,6 +80,9 @@ in { "traefik.http.routers.netbird-dashboard.priority" = "1"; "traefik.http.services.dashboard.loadbalancer.server.port" = "80"; }; + dependsOn = [ + "netbird-server" + ]; }; netbird-server = { image = "netbirdio/netbird-server:${serverVersion}"; @@ -155,6 +158,9 @@ in { "traefik.http.services.netbird-server-h2c.loadbalancer.server.port" = "80"; "traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme" = "h2c"; }; + dependsOn = [ + "traefik" + ]; }; netbird-proxy = { image = "netbirdio/reverse-proxy:${serverVersion}"; diff --git a/modules/traefik.nix b/modules/traefik.nix index 8bea41f..8888dac 100644 --- a/modules/traefik.nix +++ b/modules/traefik.nix @@ -76,6 +76,7 @@ in { ]; extraOptions = [ ''--mount=type=volume,source=certs,target=/certs,volume-driver=local'' + "--ip=172.18.0.2" "--add-host=host.docker.internal:host-gateway" "--health-cmd=wget --spider --quiet http://localhost:8080/ping" "--health-interval=10s" @@ -130,7 +131,5 @@ in { ''; }; - networking.firewall.extraCommands = "iptables -t nat -I PREROUTING -s 172.18.0.0/16 -d 172.18.0.0/16 -j MASQUERADE"; - }; } From 04ba2761b5c12814d18adcef04e70e839636a165 Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Mon, 27 Apr 2026 23:12:58 +0200 Subject: [PATCH 4/8] Use user module for busch --- hosts/busch/default.nix | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/hosts/busch/default.nix b/hosts/busch/default.nix index 199d471..57c0f0f 100644 --- a/hosts/busch/default.nix +++ b/hosts/busch/default.nix @@ -7,6 +7,7 @@ ../../modules/nix.nix ../../modules/auto-upgrade.nix + ../../users/julius/nixos-server.nix ../../modules/locale.nix ../../modules/server-cli.nix ../../modules/sshd.nix @@ -24,20 +25,6 @@ tmp.useTmpfs = true; }; networking.hostName = "busch"; # Define your hostname. - users = { - users = { - julius = { - isNormalUser = true; - uid = 1000; - extraGroups = [ "wheel" "julius" ]; - }; - }; - groups = { - julius = { - gid = 1000; - }; - }; - }; nix.settings = { substituters = [ From 530695d94123bcf79db04e0803b826a27f305c3e Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Mon, 27 Apr 2026 23:29:22 +0200 Subject: [PATCH 5/8] Add netbird service to busch --- flake.nix | 3 +++ hosts/busch/default.nix | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 74a3034..d3bb15e 100644 --- a/flake.nix +++ b/flake.nix @@ -120,6 +120,9 @@ specialArgs = { inherit inputs outputs; + pkgs-unstable = import nixpkgs-unstable { + inherit system; + }; }; modules = [ diff --git a/hosts/busch/default.nix b/hosts/busch/default.nix index 57c0f0f..d05120d 100644 --- a/hosts/busch/default.nix +++ b/hosts/busch/default.nix @@ -1,4 +1,4 @@ -{ inputs, outputs, config, lib, pkgs, ... }: +{ inputs, outputs, config, lib, pkgs, pkgs-unstable, ... }: { imports = @@ -26,6 +26,25 @@ }; networking.hostName = "busch"; # Define your hostname. + services.netbird = { + package = pkgs-unstable.netbird; + useRoutingFeatures = "both"; + clients.wt0 = { + hardened = false; + login = { + enable = true; + setupKeyFile = (pkgs.writeText "setupKey" '' + A99F5508-D543-40B7-A31A-A8931B1AE246 + '').outPath; + }; + port = 51820; + environment = { + NB_MANAGEMENT_URL = "https://netbird.jfreudenberger.de"; + }; + }; + }; + systemd.services.${config.services.netbird.clients.wt0.service.name}.path = [ pkgs.shadow ]; + nix.settings = { substituters = [ "https://cache.saumon.network/proxmox-nixos" From 4881f836c983a4808da91737a2d279ee45ad4cf7 Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Mon, 27 Apr 2026 23:50:54 +0200 Subject: [PATCH 6/8] Allow disabling automatic reboots in module --- modules/auto-upgrade.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auto-upgrade.nix b/modules/auto-upgrade.nix index 3dc9849..27bb8bc 100644 --- a/modules/auto-upgrade.nix +++ b/modules/auto-upgrade.nix @@ -1,6 +1,7 @@ { inputs, pkgs, + lib, ... }: { system.autoUpgrade = { @@ -12,7 +13,7 @@ flake = inputs.self.outPath; dates = "02:00"; randomizedDelaySec = "45min"; - allowReboot = true; + allowReboot = lib.mkDefault true; rebootWindow = { lower = "01:00"; upper = "05:00"; From 11ed5a80d71623e1e7ed6f3e0f581a14bb295f3d Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Mon, 27 Apr 2026 23:51:13 +0200 Subject: [PATCH 7/8] Disable automatic reboots after upgrades in busch Due to encrypted root the server does not boot without input. Missing tpm2 does not allow for unattended unlocks. --- hosts/busch/default.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hosts/busch/default.nix b/hosts/busch/default.nix index d05120d..e6efa4c 100644 --- a/hosts/busch/default.nix +++ b/hosts/busch/default.nix @@ -24,6 +24,9 @@ }; tmp.useTmpfs = true; }; + + system.autoUpgrade.allowReboot = false; + networking.hostName = "busch"; # Define your hostname. services.netbird = { From f894c27799cff19a0f52ea44db60faff4f2b91cd Mon Sep 17 00:00:00 2001 From: JuliusFreudenberger Date: Mon, 27 Apr 2026 23:54:37 +0200 Subject: [PATCH 8/8] Pass docker group id as string for dockhand --- modules/dockhand.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dockhand.nix b/modules/dockhand.nix index 1e8f57b..5f43cda 100644 --- a/modules/dockhand.nix +++ b/modules/dockhand.nix @@ -38,7 +38,7 @@ in { }; extraOptions = [ ''--mount=type=volume,source=dockhand-data,target=/app/data,volume-driver=local'' - ''--group-add=${config.ids.gids.docker}'' + ''--group-add=${toString config.ids.gids.docker}'' ]; }; };