31 inherit (utils) escapeSystemdExecArgs genJqSecretsReplacementSnippet;
33 stateDir = "/var/lib/netbird-mgmt";
35 settingsFormat = pkgs.formats.json { };
41 URI = "stun:${cfg.turnDomain}:3478";
51 URI = "turn:${cfg.turnDomain}:${builtins.toString cfg.turnPort}";
57 CredentialsTTL = "12h";
58 Secret = "not-secure-secret";
59 TimeBasedCredentials = false;
64 URI = "${cfg.domain}:443";
70 TrustedHTTPProxies = [ ];
71 TrustedHTTPProxiesCount = 0;
72 TrustedPeers = [ "0.0.0.0/0" ];
75 Datadir = "${stateDir}/data";
76 DataStoreEncryptionKey = "very-insecure-key";
82 Address = "127.0.0.1:${builtins.toString cfg.port}";
83 IdpSignKeyRefreshEnabled = true;
84 OIDCConfigEndpoint = cfg.oidcConfigEndpoint;
94 GrantType = "client_credentials";
98 Auth0ClientCredentials = null;
99 AzureClientCredentials = null;
100 KeycloakClientCredentials = null;
101 ZitadelClientCredentials = null;
104 DeviceAuthorizationFlow = {
107 Audience = "netbird";
109 ClientID = "netbird";
110 TokenEndpoint = null;
111 DeviceAuthEndpoint = "";
112 Scope = "openid profile email";
117 PKCEAuthorizationFlow = {
119 Audience = "netbird";
120 ClientID = "netbird";
122 AuthorizationEndpoint = "";
124 Scope = "openid profile email";
125 RedirectURLs = [ "http://localhost:53000" ];
131 managementConfig = recursiveUpdate defaultSettings cfg.settings;
133 managementFile = settingsFormat.generate "config.json" managementConfig;
135 cfg = config.services.netbird.server.management;
139 options.services.netbird.server.management = {
140 enable = mkEnableOption "Netbird Management Service";
142 package = mkPackageOption pkgs "netbird" { };
146 description = "The domain under which the management API runs.";
149 turnDomain = mkOption {
151 description = "The domain of the TURN server to use.";
154 turnPort = mkOption {
158 The port of the TURN server to use.
162 dnsDomain = mkOption {
164 default = "netbird.selfhosted";
165 description = "Domain used for peer resolution.";
168 singleAccountModeDomain = mkOption {
170 default = "netbird.selfhosted";
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.
178 disableAnonymousMetrics = mkOption {
181 description = "Disables push of anonymous usage metrics to NetBird.";
184 disableSingleAccountMode = mkOption {
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.
196 description = "Internal port of the management server.";
199 extraOptions = mkOption {
203 Additional options given to netbird-mgmt as commandline arguments.
207 oidcConfigEndpoint = mkOption {
209 description = "The oidc discovery endpoint.";
210 example = "https://example.eu.auth0.com/.well-known/openid-configuration";
213 settings = mkOption {
214 inherit (settingsFormat) type;
216 defaultText = literalExpression ''
221 URI = "stun:''${cfg.turnDomain}:3478";
231 URI = "turn:''${cfg.turnDomain}:3478";
232 Username = "netbird";
233 Password = "netbird";
237 CredentialsTTL = "12h";
238 Secret = "not-secure-secret";
239 TimeBasedCredentials = false;
244 URI = "''${cfg.domain}:443";
250 TrustedHTTPProxies = [ ];
251 TrustedHTTPProxiesCount = 0;
252 TrustedPeers = [ "0.0.0.0/0" ];
255 Datadir = "''${stateDir}/data";
256 DataStoreEncryptionKey = "genEVP6j/Yp2EeVujm0zgqXrRos29dQkpvX0hHdEUlQ=";
257 StoreConfig = { Engine = "sqlite"; };
260 Address = "127.0.0.1:''${builtins.toString cfg.port}";
261 IdpSignKeyRefreshEnabled = true;
262 OIDCConfigEndpoint = cfg.oidcConfigEndpoint;
266 ManagerType = "none";
270 ClientID = "netbird";
272 GrantType = "client_credentials";
276 Auth0ClientCredentials = null;
277 AzureClientCredentials = null;
278 KeycloakClientCredentials = null;
279 ZitadelClientCredentials = null;
282 DeviceAuthorizationFlow = {
285 Audience = "netbird";
287 ClientID = "netbird";
288 TokenEndpoint = null;
289 DeviceAuthEndpoint = "";
290 Scope = "openid profile email offline_access api";
295 PKCEAuthorizationFlow = {
297 Audience = "netbird";
298 ClientID = "netbird";
300 AuthorizationEndpoint = "";
302 Scope = "openid profile email offline_access api";
303 RedirectURLs = "http://localhost:53000";
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.
321 DataStoreEncryptionKey = {
322 _secret = "/run/agenix/netbird_mgmt-data_store_encryption_key";
327 logLevel = mkOption {
335 description = "Log level of the netbird services.";
338 enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird management service";
341 config = mkIf cfg.enable {
346 optional check "${name} is world-readable in the Nix Store, you should provide it as a _secret."
350 check = builtins.isString managementConfig.TURNConfig.Secret;
351 name = "The TURNConfig.secret";
354 check = builtins.isString managementConfig.DataStoreEncryptionKey;
355 name = "The DataStoreEncryptionKey";
358 check = any (T: (T ? Password) && builtins.isString T.Password) managementConfig.TURNConfig.Turns;
359 name = "A Turn configuration's password";
363 systemd.services.netbird-management = {
364 description = "The management server for Netbird, a wireguard VPN";
365 documentation = [ "https://netbird.io/docs/" ];
367 after = [ "network.target" ];
368 wantedBy = [ "multi-user.target" ];
369 restartTriggers = [ managementFile ];
371 preStart = genJqSecretsReplacementSnippet managementConfig "${stateDir}/management.json";
374 ExecStart = escapeSystemdExecArgs (
376 (getExe' cfg.package "netbird-mgmt")
380 "${stateDir}/management.json"
397 "--idp-sign-key-refresh-enabled"
398 # Domain for internal resolution
399 "--single-account-mode-domain"
400 cfg.singleAccountModeDomain
402 ++ (optional cfg.disableAnonymousMetrics "--disable-anonymous-metrics")
403 ++ (optional cfg.disableSingleAccountMode "--disable-single-account-mode")
407 RuntimeDirectory = "netbird-mgmt";
412 WorkingDirectory = stateDir;
415 LockPersonality = true;
416 MemoryDenyWriteExecute = true;
417 NoNewPrivileges = true;
418 PrivateMounts = true;
421 ProtectControlGroups = true;
423 ProtectHostname = true;
424 ProtectKernelLogs = true;
425 ProtectKernelModules = true;
426 ProtectKernelTunables = true;
427 ProtectSystem = true;
429 RestrictNamespaces = true;
430 RestrictRealtime = true;
431 RestrictSUIDSGID = true;
434 stopIfChanged = false;
437 services.nginx = mkIf cfg.enableNginx {
440 virtualHosts.${cfg.domain} = {
442 "/api".proxyPass = "http://localhost:${builtins.toString cfg.port}";
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;
449 grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
451 grpc_pass grpc://localhost:${builtins.toString cfg.port};
452 grpc_read_timeout 1d;
453 grpc_send_timeout 1d;
454 grpc_socket_keepalive on;