Merge pull request #247118 from Tom-Hubrecht/netbird-server

nixos/netbird-server: init module

authored by Ryan Lahfa and committed by GitHub 21bd30ea 0faf57ff

+1076
+2
nixos/doc/manual/release-notes/rl-2405.section.md
··· 137 137 138 138 - [Suwayomi Server](https://github.com/Suwayomi/Suwayomi-Server), a free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org). Available as [services.suwayomi-server](#opt-services.suwayomi-server.enable). 139 139 140 + - A self-hosted management server for the [Netbird](https://netbird.io). Available as [services.netbird.server](#opt-services.netbird.server.enable). 141 + 140 142 - [ping_exporter](https://github.com/czerwonk/ping_exporter), a Prometheus exporter for ICMP echo requests. Available as [services.prometheus.exporters.ping](#opt-services.prometheus.exporters.ping.enable). 141 143 142 144 - [Prometheus DNSSEC Exporter](https://github.com/chrj/prometheus-dnssec-exporter), check for validity and expiration in DNSSEC signatures and expose metrics for Prometheus. Available as [services.prometheus.exporters.dnssec](#opt-services.prometheus.exporters.dnssec.enable).
+1
nixos/modules/module-list.nix
··· 1070 1070 ./services/networking/ndppd.nix 1071 1071 ./services/networking/nebula.nix 1072 1072 ./services/networking/netbird.nix 1073 + ./services/networking/netbird/server.nix 1073 1074 ./services/networking/netclient.nix 1074 1075 ./services/networking/networkd-dispatcher.nix 1075 1076 ./services/networking/networkmanager.nix
+160
nixos/modules/services/networking/netbird/coturn.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 + 8 + let 9 + inherit (lib) 10 + getExe 11 + literalExpression 12 + mkAfter 13 + mkEnableOption 14 + mkIf 15 + mkMerge 16 + mkOption 17 + optionalAttrs 18 + optionalString 19 + ; 20 + 21 + inherit (lib.types) 22 + bool 23 + listOf 24 + nullOr 25 + path 26 + port 27 + str 28 + ; 29 + 30 + cfg = config.services.netbird.server.coturn; 31 + in 32 + 33 + { 34 + options.services.netbird.server.coturn = { 35 + enable = mkEnableOption "a Coturn server for Netbird, will also open the firewall on the configured range"; 36 + 37 + useAcmeCertificates = mkOption { 38 + type = bool; 39 + default = false; 40 + description = '' 41 + Whether to use ACME certificates corresponding to the given domain for the server. 42 + ''; 43 + }; 44 + 45 + domain = mkOption { 46 + type = str; 47 + description = "The domain under which the coturn server runs."; 48 + }; 49 + 50 + user = mkOption { 51 + type = str; 52 + default = "netbird"; 53 + description = '' 54 + The username used by netbird to connect to the coturn server. 55 + ''; 56 + }; 57 + 58 + password = mkOption { 59 + type = nullOr str; 60 + default = null; 61 + description = '' 62 + The password of the user used by netbird to connect to the coturn server. 63 + ''; 64 + }; 65 + 66 + passwordFile = mkOption { 67 + type = nullOr path; 68 + default = null; 69 + description = '' 70 + The path to a file containing the password of the user used by netbird to connect to the coturn server. 71 + ''; 72 + }; 73 + 74 + openPorts = mkOption { 75 + type = listOf port; 76 + default = with config.services.coturn; [ 77 + listening-port 78 + alt-listening-port 79 + tls-listening-port 80 + alt-tls-listening-port 81 + ]; 82 + defaultText = literalExpression '' 83 + with config.services.coturn; [ 84 + listening-port 85 + alt-listening-port 86 + tls-listening-port 87 + alt-tls-listening-port 88 + ]; 89 + ''; 90 + 91 + description = '' 92 + The list of ports used by coturn for listening to open in the firewall. 93 + ''; 94 + }; 95 + }; 96 + 97 + config = mkIf cfg.enable (mkMerge [ 98 + { 99 + assertions = [ 100 + { 101 + assertion = (cfg.password == null) != (cfg.passwordFile == null); 102 + message = "Exactly one of `password` or `passwordFile` must be given for the coturn setup."; 103 + } 104 + ]; 105 + 106 + services.coturn = 107 + { 108 + enable = true; 109 + 110 + realm = cfg.domain; 111 + lt-cred-mech = true; 112 + no-cli = true; 113 + 114 + extraConfig = '' 115 + fingerprint 116 + user=${cfg.user}:${if cfg.password != null then cfg.password else "@password@"} 117 + no-software-attribute 118 + ''; 119 + } 120 + // (optionalAttrs cfg.useAcmeCertificates { 121 + cert = "@cert@"; 122 + pkey = "@pkey@"; 123 + }); 124 + 125 + systemd.services.coturn = 126 + let 127 + dir = config.security.acme.certs.${cfg.domain}.directory; 128 + preStart' = 129 + (optionalString (cfg.passwordFile != null) '' 130 + ${getExe pkgs.replace-secret} @password@ ${cfg.passwordFile} /run/coturn/turnserver.cfg 131 + '') 132 + + (optionalString cfg.useAcmeCertificates '' 133 + ${getExe pkgs.replace-secret} @cert@ "$CREDENTIALS_DIRECTORY/cert.pem" /run/coturn/turnserver.cfg 134 + ${getExe pkgs.replace-secret} @pkey@ "$CREDENTIALS_DIRECTORY/pkey.pem" /run/coturn/turnserver.cfg 135 + ''); 136 + in 137 + (optionalAttrs (preStart' != "") { preStart = mkAfter preStart'; }) 138 + // (optionalAttrs cfg.useAcmeCertificates { 139 + serviceConfig.LoadCredential = [ 140 + "cert.pem:${dir}/fullchain.pem" 141 + "pkey.pem:${dir}/key.pem" 142 + ]; 143 + }); 144 + 145 + security.acme.certs.${cfg.domain}.postRun = optionalString cfg.useAcmeCertificates "systemctl restart coturn.service"; 146 + 147 + networking.firewall = { 148 + allowedUDPPorts = cfg.openPorts; 149 + allowedTCPPorts = cfg.openPorts; 150 + 151 + allowedUDPPortRanges = [ 152 + { 153 + from = cfg.minPort; 154 + to = cfg.maxPort; 155 + } 156 + ]; 157 + }; 158 + } 159 + ]); 160 + }
+186
nixos/modules/services/networking/netbird/dashboard.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 + 8 + let 9 + inherit (lib) 10 + boolToString 11 + concatStringsSep 12 + hasAttr 13 + isBool 14 + mapAttrs 15 + mkDefault 16 + mkEnableOption 17 + mkIf 18 + mkOption 19 + mkPackageOption 20 + ; 21 + 22 + inherit (lib.types) 23 + attrsOf 24 + bool 25 + either 26 + package 27 + str 28 + submodule 29 + ; 30 + 31 + toStringEnv = value: if isBool value then boolToString value else toString value; 32 + 33 + cfg = config.services.netbird.server.dashboard; 34 + in 35 + 36 + { 37 + options.services.netbird.server.dashboard = { 38 + enable = mkEnableOption "the static netbird dashboard frontend"; 39 + 40 + package = mkPackageOption pkgs "netbird-dashboard" { }; 41 + 42 + enableNginx = mkEnableOption "Nginx reverse-proxy to serve the dashboard."; 43 + 44 + domain = mkOption { 45 + type = str; 46 + default = "localhost"; 47 + description = "The domain under which the dashboard runs."; 48 + }; 49 + 50 + managementServer = mkOption { 51 + type = str; 52 + description = "The address of the management server, used for the API endpoints."; 53 + }; 54 + 55 + settings = mkOption { 56 + type = submodule { freeformType = attrsOf (either str bool); }; 57 + 58 + defaultText = '' 59 + { 60 + AUTH_AUDIENCE = "netbird"; 61 + AUTH_CLIENT_ID = "netbird"; 62 + AUTH_SUPPORTED_SCOPES = "openid profile email"; 63 + NETBIRD_TOKEN_SOURCE = "idToken"; 64 + USE_AUTH0 = false; 65 + } 66 + ''; 67 + 68 + description = '' 69 + An attribute set that will be used to substitute variables when building the dashboard. 70 + Any values set here will be templated into the frontend and be public for anyone that can reach your website. 71 + The exact values sadly aren't documented anywhere. 72 + A starting point when searching for valid values is this [script](https://github.com/netbirdio/dashboard/blob/main/docker/init_react_envs.sh) 73 + The only mandatory value is 'AUTH_AUTHORITY' as we cannot set a default value here. 74 + ''; 75 + }; 76 + 77 + finalDrv = mkOption { 78 + readOnly = true; 79 + type = package; 80 + description = '' 81 + The derivation containing the final templated dashboard. 82 + ''; 83 + }; 84 + }; 85 + 86 + config = mkIf cfg.enable { 87 + assertions = [ 88 + { 89 + assertion = hasAttr "AUTH_AUTHORITY" cfg.settings; 90 + message = "The setting AUTH_AUTHORITY is required for the dasboard to function."; 91 + } 92 + ]; 93 + 94 + services.netbird.server.dashboard = { 95 + settings = 96 + { 97 + # Due to how the backend and frontend work this secret will be templated into the backend 98 + # and then served statically from your website 99 + # This enables you to login without the normally needed indirection through the backend 100 + # but this also means anyone that can reach your website can 101 + # fetch this secret, which is why there is no real need to put it into 102 + # special options as its public anyway 103 + # As far as I know leaking this secret is just 104 + # an information leak as one can fetch some basic app 105 + # informations from the IDP 106 + # To actually do something one still needs to have login 107 + # data and this secret so this being public will not 108 + # suffice for anything just decreasing security 109 + AUTH_CLIENT_SECRET = ""; 110 + 111 + NETBIRD_MGMT_API_ENDPOINT = cfg.managementServer; 112 + NETBIRD_MGMT_GRPC_API_ENDPOINT = cfg.managementServer; 113 + } 114 + // (mapAttrs (_: mkDefault) { 115 + # Those values have to be easily overridable 116 + AUTH_AUDIENCE = "netbird"; # must be set for your devices to be able to log in 117 + AUTH_CLIENT_ID = "netbird"; 118 + AUTH_SUPPORTED_SCOPES = "openid profile email"; 119 + NETBIRD_TOKEN_SOURCE = "idToken"; 120 + USE_AUTH0 = false; 121 + }); 122 + 123 + # The derivation containing the templated dashboard 124 + finalDrv = 125 + pkgs.runCommand "netbird-dashboard" 126 + { 127 + nativeBuildInputs = [ pkgs.gettext ]; 128 + env = { 129 + ENV_STR = concatStringsSep " " [ 130 + "$AUTH_AUDIENCE" 131 + "$AUTH_AUTHORITY" 132 + "$AUTH_CLIENT_ID" 133 + "$AUTH_CLIENT_SECRET" 134 + "$AUTH_REDIRECT_URI" 135 + "$AUTH_SILENT_REDIRECT_URI" 136 + "$AUTH_SUPPORTED_SCOPES" 137 + "$NETBIRD_DRAG_QUERY_PARAMS" 138 + "$NETBIRD_GOOGLE_ANALYTICS_ID" 139 + "$NETBIRD_HOTJAR_TRACK_ID" 140 + "$NETBIRD_MGMT_API_ENDPOINT" 141 + "$NETBIRD_MGMT_GRPC_API_ENDPOINT" 142 + "$NETBIRD_TOKEN_SOURCE" 143 + "$USE_AUTH0" 144 + ]; 145 + } // (mapAttrs (_: toStringEnv) cfg.settings); 146 + } 147 + '' 148 + cp -R ${cfg.package} build 149 + 150 + find build -type d -exec chmod 755 {} \; 151 + OIDC_TRUSTED_DOMAINS="build/OidcTrustedDomains.js" 152 + 153 + envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS.tmpl" > "$OIDC_TRUSTED_DOMAINS" 154 + 155 + for f in $(grep -R -l AUTH_SUPPORTED_SCOPES build/); do 156 + mv "$f" "$f.copy" 157 + envsubst "$ENV_STR" < "$f.copy" > "$f" 158 + rm "$f.copy" 159 + done 160 + 161 + cp -R build $out 162 + ''; 163 + }; 164 + 165 + services.nginx = mkIf cfg.enableNginx { 166 + enable = true; 167 + 168 + virtualHosts.${cfg.domain} = { 169 + locations = { 170 + "/" = { 171 + root = cfg.finalDrv; 172 + tryFiles = "$uri $uri.html $uri/ =404"; 173 + }; 174 + 175 + "/404.html".extraConfig = '' 176 + internal; 177 + ''; 178 + }; 179 + 180 + extraConfig = '' 181 + error_page 404 /404.html; 182 + ''; 183 + }; 184 + }; 185 + }; 186 + }
+460
nixos/modules/services/networking/netbird/management.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + utils, 6 + ... 7 + }: 8 + 9 + let 10 + inherit (lib) 11 + any 12 + concatMap 13 + getExe' 14 + literalExpression 15 + mkEnableOption 16 + mkIf 17 + mkOption 18 + mkPackageOption 19 + optional 20 + recursiveUpdate 21 + ; 22 + 23 + inherit (lib.types) 24 + bool 25 + enum 26 + listOf 27 + port 28 + str 29 + ; 30 + 31 + inherit (utils) escapeSystemdExecArgs genJqSecretsReplacementSnippet; 32 + 33 + stateDir = "/var/lib/netbird-mgmt"; 34 + 35 + settingsFormat = pkgs.formats.json { }; 36 + 37 + defaultSettings = { 38 + Stuns = [ 39 + { 40 + Proto = "udp"; 41 + URI = "stun:${cfg.turnDomain}:3478"; 42 + Username = ""; 43 + Password = null; 44 + } 45 + ]; 46 + 47 + TURNConfig = { 48 + Turns = [ 49 + { 50 + Proto = "udp"; 51 + URI = "turn:${cfg.turnDomain}:${builtins.toString cfg.turnPort}"; 52 + Username = "netbird"; 53 + Password = "netbird"; 54 + } 55 + ]; 56 + 57 + CredentialsTTL = "12h"; 58 + Secret = "not-secure-secret"; 59 + TimeBasedCredentials = false; 60 + }; 61 + 62 + Signal = { 63 + Proto = "https"; 64 + URI = "${cfg.domain}:443"; 65 + Username = ""; 66 + Password = null; 67 + }; 68 + 69 + ReverseProxy = { 70 + TrustedHTTPProxies = [ ]; 71 + TrustedHTTPProxiesCount = 0; 72 + TrustedPeers = [ "0.0.0.0/0" ]; 73 + }; 74 + 75 + Datadir = "${stateDir}/data"; 76 + DataStoreEncryptionKey = "very-insecure-key"; 77 + StoreConfig = { 78 + Engine = "sqlite"; 79 + }; 80 + 81 + HttpConfig = { 82 + Address = "127.0.0.1:${builtins.toString cfg.port}"; 83 + IdpSignKeyRefreshEnabled = true; 84 + OIDCConfigEndpoint = cfg.oidcConfigEndpoint; 85 + }; 86 + 87 + IdpManagerConfig = { 88 + ManagerType = "none"; 89 + ClientConfig = { 90 + Issuer = ""; 91 + TokenEndpoint = ""; 92 + ClientID = "netbird"; 93 + ClientSecret = ""; 94 + GrantType = "client_credentials"; 95 + }; 96 + 97 + ExtraConfig = { }; 98 + Auth0ClientCredentials = null; 99 + AzureClientCredentials = null; 100 + KeycloakClientCredentials = null; 101 + ZitadelClientCredentials = null; 102 + }; 103 + 104 + DeviceAuthorizationFlow = { 105 + Provider = "none"; 106 + ProviderConfig = { 107 + Audience = "netbird"; 108 + Domain = null; 109 + ClientID = "netbird"; 110 + TokenEndpoint = null; 111 + DeviceAuthEndpoint = ""; 112 + Scope = "openid profile email"; 113 + UseIDToken = false; 114 + }; 115 + }; 116 + 117 + PKCEAuthorizationFlow = { 118 + ProviderConfig = { 119 + Audience = "netbird"; 120 + ClientID = "netbird"; 121 + ClientSecret = ""; 122 + AuthorizationEndpoint = ""; 123 + TokenEndpoint = ""; 124 + Scope = "openid profile email"; 125 + RedirectURLs = [ "http://localhost:53000" ]; 126 + UseIDToken = false; 127 + }; 128 + }; 129 + }; 130 + 131 + managementConfig = recursiveUpdate defaultSettings cfg.settings; 132 + 133 + managementFile = settingsFormat.generate "config.json" managementConfig; 134 + 135 + cfg = config.services.netbird.server.management; 136 + in 137 + 138 + { 139 + options.services.netbird.server.management = { 140 + enable = mkEnableOption "Netbird Management Service."; 141 + 142 + package = mkPackageOption pkgs "netbird" { }; 143 + 144 + domain = mkOption { 145 + type = str; 146 + description = "The domain under which the management API runs."; 147 + }; 148 + 149 + turnDomain = mkOption { 150 + type = str; 151 + description = "The domain of the TURN server to use."; 152 + }; 153 + 154 + turnPort = mkOption { 155 + type = port; 156 + default = 3478; 157 + description = '' 158 + The port of the TURN server to use. 159 + ''; 160 + }; 161 + 162 + dnsDomain = mkOption { 163 + type = str; 164 + default = "netbird.selfhosted"; 165 + description = "Domain used for peer resolution."; 166 + }; 167 + 168 + singleAccountModeDomain = mkOption { 169 + type = str; 170 + default = "netbird.selfhosted"; 171 + description = '' 172 + Enables single account mode. 173 + This means that all the users will be under the same account grouped by the specified domain. 174 + If the installation has more than one account, the property is ineffective. 175 + ''; 176 + }; 177 + 178 + disableAnonymousMetrics = mkOption { 179 + type = bool; 180 + default = true; 181 + description = "Disables push of anonymous usage metrics to NetBird."; 182 + }; 183 + 184 + disableSingleAccountMode = mkOption { 185 + type = bool; 186 + default = false; 187 + description = '' 188 + If set to true, disables single account mode. 189 + The `singleAccountModeDomain` property will be ignored and every new user will have a separate NetBird account. 190 + ''; 191 + }; 192 + 193 + port = mkOption { 194 + type = port; 195 + default = 8011; 196 + description = "Internal port of the management server."; 197 + }; 198 + 199 + extraOptions = mkOption { 200 + type = listOf str; 201 + default = [ ]; 202 + description = '' 203 + Additional options given to netbird-mgmt as commandline arguments. 204 + ''; 205 + }; 206 + 207 + oidcConfigEndpoint = mkOption { 208 + type = str; 209 + description = "The oidc discovery endpoint."; 210 + example = "https://example.eu.auth0.com/.well-known/openid-configuration"; 211 + }; 212 + 213 + settings = mkOption { 214 + inherit (settingsFormat) type; 215 + 216 + defaultText = literalExpression '' 217 + defaultSettings = { 218 + Stuns = [ 219 + { 220 + Proto = "udp"; 221 + URI = "stun:''${cfg.turnDomain}:3478"; 222 + Username = ""; 223 + Password = null; 224 + } 225 + ]; 226 + 227 + TURNConfig = { 228 + Turns = [ 229 + { 230 + Proto = "udp"; 231 + URI = "turn:''${cfg.turnDomain}:3478"; 232 + Username = "netbird"; 233 + Password = "netbird"; 234 + } 235 + ]; 236 + 237 + CredentialsTTL = "12h"; 238 + Secret = "not-secure-secret"; 239 + TimeBasedCredentials = false; 240 + }; 241 + 242 + Signal = { 243 + Proto = "https"; 244 + URI = "''${cfg.domain}:443"; 245 + Username = ""; 246 + Password = null; 247 + }; 248 + 249 + ReverseProxy = { 250 + TrustedHTTPProxies = [ ]; 251 + TrustedHTTPProxiesCount = 0; 252 + TrustedPeers = [ "0.0.0.0/0" ]; 253 + }; 254 + 255 + Datadir = "''${stateDir}/data"; 256 + DataStoreEncryptionKey = "genEVP6j/Yp2EeVujm0zgqXrRos29dQkpvX0hHdEUlQ="; 257 + StoreConfig = { Engine = "sqlite"; }; 258 + 259 + HttpConfig = { 260 + Address = "127.0.0.1:''${builtins.toString cfg.port}"; 261 + IdpSignKeyRefreshEnabled = true; 262 + OIDCConfigEndpoint = cfg.oidcConfigEndpoint; 263 + }; 264 + 265 + IdpManagerConfig = { 266 + ManagerType = "none"; 267 + ClientConfig = { 268 + Issuer = ""; 269 + TokenEndpoint = ""; 270 + ClientID = "netbird"; 271 + ClientSecret = ""; 272 + GrantType = "client_credentials"; 273 + }; 274 + 275 + ExtraConfig = { }; 276 + Auth0ClientCredentials = null; 277 + AzureClientCredentials = null; 278 + KeycloakClientCredentials = null; 279 + ZitadelClientCredentials = null; 280 + }; 281 + 282 + DeviceAuthorizationFlow = { 283 + Provider = "none"; 284 + ProviderConfig = { 285 + Audience = "netbird"; 286 + Domain = null; 287 + ClientID = "netbird"; 288 + TokenEndpoint = null; 289 + DeviceAuthEndpoint = ""; 290 + Scope = "openid profile email offline_access api"; 291 + UseIDToken = false; 292 + }; 293 + }; 294 + 295 + PKCEAuthorizationFlow = { 296 + ProviderConfig = { 297 + Audience = "netbird"; 298 + ClientID = "netbird"; 299 + ClientSecret = ""; 300 + AuthorizationEndpoint = ""; 301 + TokenEndpoint = ""; 302 + Scope = "openid profile email offline_access api"; 303 + RedirectURLs = "http://localhost:53000"; 304 + UseIDToken = false; 305 + }; 306 + }; 307 + }; 308 + ''; 309 + 310 + default = { }; 311 + 312 + description = '' 313 + Configuration of the netbird management server. 314 + Options containing secret data should be set to an attribute set containing the attribute _secret 315 + - a string pointing to a file containing the value the option should be set to. 316 + See the example to get a better picture of this: in the resulting management.json file, 317 + the `DataStoreEncryptionKey` key will be set to the contents of the /run/agenix/netbird_mgmt-data_store_encryption_key file. 318 + ''; 319 + 320 + example = { 321 + DataStoreEncryptionKey = { 322 + _secret = "/run/agenix/netbird_mgmt-data_store_encryption_key"; 323 + }; 324 + }; 325 + }; 326 + 327 + logLevel = mkOption { 328 + type = enum [ 329 + "ERROR" 330 + "WARN" 331 + "INFO" 332 + "DEBUG" 333 + ]; 334 + default = "INFO"; 335 + description = "Log level of the netbird services."; 336 + }; 337 + 338 + enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird management service."; 339 + }; 340 + 341 + config = mkIf cfg.enable { 342 + warnings = 343 + concatMap 344 + ( 345 + { check, name }: 346 + optional check "${name} is world-readable in the Nix Store, you should provide it as a _secret." 347 + ) 348 + [ 349 + { 350 + check = builtins.isString managementConfig.TURNConfig.Secret; 351 + name = "The TURNConfig.secret"; 352 + } 353 + { 354 + check = builtins.isString managementConfig.DataStoreEncryptionKey; 355 + name = "The DataStoreEncryptionKey"; 356 + } 357 + { 358 + check = any (T: (T ? Password) && builtins.isString T.Password) managementConfig.TURNConfig.Turns; 359 + name = "A Turn configuration's password"; 360 + } 361 + ]; 362 + 363 + systemd.services.netbird-management = { 364 + description = "The management server for Netbird, a wireguard VPN"; 365 + documentation = [ "https://netbird.io/docs/" ]; 366 + 367 + after = [ "network.target" ]; 368 + wantedBy = [ "multi-user.target" ]; 369 + restartTriggers = [ managementFile ]; 370 + 371 + preStart = genJqSecretsReplacementSnippet managementConfig "${stateDir}/management.json"; 372 + 373 + serviceConfig = { 374 + ExecStart = escapeSystemdExecArgs ( 375 + [ 376 + (getExe' cfg.package "netbird-mgmt") 377 + "management" 378 + # Config file 379 + "--config" 380 + "${stateDir}/management.json" 381 + # Data directory 382 + "--datadir" 383 + "${stateDir}/data" 384 + # DNS domain 385 + "--dns-domain" 386 + cfg.dnsDomain 387 + # Port to listen on 388 + "--port" 389 + cfg.port 390 + # Log to stdout 391 + "--log-file" 392 + "console" 393 + # Log level 394 + "--log-level" 395 + cfg.logLevel 396 + # 397 + "--idp-sign-key-refresh-enabled" 398 + # Domain for internal resolution 399 + "--single-account-mode-domain" 400 + cfg.singleAccountModeDomain 401 + ] 402 + ++ (optional cfg.disableAnonymousMetrics "--disable-anonymous-metrics") 403 + ++ (optional cfg.disableSingleAccountMode "--disable-single-account-mode") 404 + ++ cfg.extraOptions 405 + ); 406 + Restart = "always"; 407 + RuntimeDirectory = "netbird-mgmt"; 408 + StateDirectory = [ 409 + "netbird-mgmt" 410 + "netbird-mgmt/data" 411 + ]; 412 + WorkingDirectory = stateDir; 413 + 414 + # hardening 415 + LockPersonality = true; 416 + MemoryDenyWriteExecute = true; 417 + NoNewPrivileges = true; 418 + PrivateMounts = true; 419 + PrivateTmp = true; 420 + ProtectClock = true; 421 + ProtectControlGroups = true; 422 + ProtectHome = true; 423 + ProtectHostname = true; 424 + ProtectKernelLogs = true; 425 + ProtectKernelModules = true; 426 + ProtectKernelTunables = true; 427 + ProtectSystem = true; 428 + RemoveIPC = true; 429 + RestrictNamespaces = true; 430 + RestrictRealtime = true; 431 + RestrictSUIDSGID = true; 432 + }; 433 + 434 + stopIfChanged = false; 435 + }; 436 + 437 + services.nginx = mkIf cfg.enableNginx { 438 + enable = true; 439 + 440 + virtualHosts.${cfg.domain} = { 441 + locations = { 442 + "/api".proxyPass = "http://localhost:${builtins.toString cfg.port}"; 443 + 444 + "/management.ManagementService/".extraConfig = '' 445 + # This is necessary so that grpc connections do not get closed early 446 + # see https://stackoverflow.com/a/67805465 447 + client_body_timeout 1d; 448 + 449 + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 450 + 451 + grpc_pass grpc://localhost:${builtins.toString cfg.port}; 452 + grpc_read_timeout 1d; 453 + grpc_send_timeout 1d; 454 + grpc_socket_keepalive on; 455 + ''; 456 + }; 457 + }; 458 + }; 459 + }; 460 + }
+42
nixos/modules/services/networking/netbird/server.md
··· 1 + # Netbird server {#module-services-netbird-server} 2 + 3 + NetBird is a VPN built on top of WireGuard® making it easy to create secure private networks for your organization or home. 4 + 5 + ## Quickstart {#module-services-netbird-server-quickstart} 6 + 7 + To fully setup Netbird as a self-hosted server, we need both a Coturn server and an identity provider, the list of supported SSOs and their setup are available [on Netbird's documentation](https://docs.netbird.io/selfhosted/selfhosted-guide#step-3-configure-identity-provider-idp). 8 + 9 + There are quite a few settings that need to be passed to Netbird for it to function, and a minimal config looks like : 10 + 11 + ```nix 12 + services.netbird.server = { 13 + enable = true; 14 + 15 + domain = "netbird.example.selfhosted"; 16 + 17 + enableNginx = true; 18 + 19 + coturn = { 20 + enable = true; 21 + 22 + passwordFile = "/path/to/a/secret/password"; 23 + }; 24 + 25 + management = { 26 + oidcConfigEndpoint = "https://sso.example.selfhosted/oauth2/openid/netbird/.well-known/openid-configuration"; 27 + 28 + settings = { 29 + TURNConfig = { 30 + Turns = [ 31 + { 32 + Proto = "udp"; 33 + URI = "turn:netbird.example.selfhosted:3478"; 34 + Username = "netbird"; 35 + Password._secret = "/path/to/a/secret/password"; 36 + } 37 + ]; 38 + }; 39 + }; 40 + }; 41 + }; 42 + ```
+67
nixos/modules/services/networking/netbird/server.nix
··· 1 + { config, lib, ... }: 2 + 3 + let 4 + inherit (lib) 5 + mkEnableOption 6 + mkIf 7 + mkOption 8 + optionalAttrs 9 + ; 10 + 11 + inherit (lib.types) str; 12 + 13 + cfg = config.services.netbird.server; 14 + in 15 + 16 + { 17 + meta = { 18 + maintainers = with lib.maintainers; [ thubrecht ]; 19 + doc = ./server.md; 20 + }; 21 + 22 + # Import the separate components 23 + imports = [ 24 + ./coturn.nix 25 + ./dashboard.nix 26 + ./management.nix 27 + ./signal.nix 28 + ]; 29 + 30 + options.services.netbird.server = { 31 + enable = mkEnableOption "Netbird Server stack, comprising the dashboard, management API and signal service"; 32 + 33 + enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird server services."; 34 + 35 + domain = mkOption { 36 + type = str; 37 + description = "The domain under which the netbird server runs."; 38 + }; 39 + }; 40 + 41 + config = mkIf cfg.enable { 42 + services.netbird.server = { 43 + dashboard = { 44 + inherit (cfg) enable domain enableNginx; 45 + 46 + managementServer = "https://${cfg.domain}"; 47 + }; 48 + 49 + management = 50 + { 51 + inherit (cfg) enable domain enableNginx; 52 + } 53 + // (optionalAttrs cfg.coturn.enable { 54 + turnDomain = cfg.domain; 55 + turnPort = config.services.coturn.tls-listening-port; 56 + }); 57 + 58 + signal = { 59 + inherit (cfg) enable domain enableNginx; 60 + }; 61 + 62 + coturn = { 63 + inherit (cfg) domain; 64 + }; 65 + }; 66 + }; 67 + }
+123
nixos/modules/services/networking/netbird/signal.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + utils, 6 + ... 7 + }: 8 + 9 + let 10 + inherit (lib) 11 + getExe' 12 + mkEnableOption 13 + mkIf 14 + mkPackageOption 15 + mkOption 16 + ; 17 + 18 + inherit (lib.types) enum port str; 19 + 20 + inherit (utils) escapeSystemdExecArgs; 21 + 22 + cfg = config.services.netbird.server.signal; 23 + in 24 + 25 + { 26 + options.services.netbird.server.signal = { 27 + enable = mkEnableOption "Netbird's Signal Service"; 28 + 29 + package = mkPackageOption pkgs "netbird" { }; 30 + 31 + enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird signal service."; 32 + 33 + domain = mkOption { 34 + type = str; 35 + description = "The domain name for the signal service."; 36 + }; 37 + 38 + port = mkOption { 39 + type = port; 40 + default = 8012; 41 + description = "Internal port of the signal server."; 42 + }; 43 + 44 + logLevel = mkOption { 45 + type = enum [ 46 + "ERROR" 47 + "WARN" 48 + "INFO" 49 + "DEBUG" 50 + ]; 51 + default = "INFO"; 52 + description = "Log level of the netbird signal service."; 53 + }; 54 + }; 55 + 56 + config = mkIf cfg.enable { 57 + systemd.services.netbird-signal = { 58 + after = [ "network.target" ]; 59 + wantedBy = [ "multi-user.target" ]; 60 + 61 + serviceConfig = { 62 + ExecStart = escapeSystemdExecArgs [ 63 + (getExe' cfg.package "netbird-signal") 64 + "run" 65 + # Port to listen on 66 + "--port" 67 + cfg.port 68 + # Log to stdout 69 + "--log-file" 70 + "console" 71 + # Log level 72 + "--log-level" 73 + cfg.logLevel 74 + ]; 75 + 76 + Restart = "always"; 77 + RuntimeDirectory = "netbird-mgmt"; 78 + StateDirectory = "netbird-mgmt"; 79 + WorkingDirectory = "/var/lib/netbird-mgmt"; 80 + 81 + # hardening 82 + LockPersonality = true; 83 + MemoryDenyWriteExecute = true; 84 + NoNewPrivileges = true; 85 + PrivateMounts = true; 86 + PrivateTmp = true; 87 + ProtectClock = true; 88 + ProtectControlGroups = true; 89 + ProtectHome = true; 90 + ProtectHostname = true; 91 + ProtectKernelLogs = true; 92 + ProtectKernelModules = true; 93 + ProtectKernelTunables = true; 94 + ProtectSystem = true; 95 + RemoveIPC = true; 96 + RestrictNamespaces = true; 97 + RestrictRealtime = true; 98 + RestrictSUIDSGID = true; 99 + }; 100 + 101 + stopIfChanged = false; 102 + }; 103 + 104 + services.nginx = mkIf cfg.enableNginx { 105 + enable = true; 106 + 107 + virtualHosts.${cfg.domain} = { 108 + locations."/signalexchange.SignalExchange/".extraConfig = '' 109 + # This is necessary so that grpc connections do not get closed early 110 + # see https://stackoverflow.com/a/67805465 111 + client_body_timeout 1d; 112 + 113 + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 114 + 115 + grpc_pass grpc://localhost:${builtins.toString cfg.port}; 116 + grpc_read_timeout 1d; 117 + grpc_send_timeout 1d; 118 + grpc_socket_keepalive on; 119 + ''; 120 + }; 121 + }; 122 + }; 123 + }
+35
pkgs/by-name/ne/netbird-dashboard/package.nix
··· 1 + { 2 + lib, 3 + buildNpmPackage, 4 + fetchFromGitHub, 5 + }: 6 + 7 + buildNpmPackage rec { 8 + pname = "netbird-dashboard"; 9 + version = "2.3.0"; 10 + 11 + src = fetchFromGitHub { 12 + owner = "netbirdio"; 13 + repo = "dashboard"; 14 + rev = "v${version}"; 15 + hash = "sha256-4aZ7WGLhjpTHOggJ+sRdln0YOG3jf7TKT9/bH/n3LmQ="; 16 + }; 17 + 18 + npmDepsHash = "sha256-ts3UuThIMf+wwSr3DpZ+k1i9RnHi/ltvhD/7lomVxQk="; 19 + npmFlags = [ "--legacy-peer-deps" ]; 20 + 21 + installPhase = '' 22 + cp -R out $out 23 + ''; 24 + 25 + env = { 26 + CYPRESS_INSTALL_BINARY = 0; 27 + }; 28 + 29 + meta = with lib; { 30 + description = "NetBird Management Service Web UI Panel"; 31 + homepage = "https://github.com/netbirdio/dashboard"; 32 + license = licenses.bsd3; 33 + maintainers = with maintainers; [ thubrecht ]; 34 + }; 35 + }