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

nixos/netbird-server: init module

Changed files
+1076
nixos
doc
manual
release-notes
modules
pkgs
by-name
ne
netbird-dashboard
+2
nixos/doc/manual/release-notes/rl-2405.section.md
···
- [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).
- [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).
- [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).
···
- [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).
+
- A self-hosted management server for the [Netbird](https://netbird.io). Available as [services.netbird.server](#opt-services.netbird.server.enable).
+
- [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).
- [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
···
./services/networking/ndppd.nix
./services/networking/nebula.nix
./services/networking/netbird.nix
./services/networking/netclient.nix
./services/networking/networkd-dispatcher.nix
./services/networking/networkmanager.nix
···
./services/networking/ndppd.nix
./services/networking/nebula.nix
./services/networking/netbird.nix
+
./services/networking/netbird/server.nix
./services/networking/netclient.nix
./services/networking/networkd-dispatcher.nix
./services/networking/networkmanager.nix
+160
nixos/modules/services/networking/netbird/coturn.nix
···
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
+
let
+
inherit (lib)
+
getExe
+
literalExpression
+
mkAfter
+
mkEnableOption
+
mkIf
+
mkMerge
+
mkOption
+
optionalAttrs
+
optionalString
+
;
+
+
inherit (lib.types)
+
bool
+
listOf
+
nullOr
+
path
+
port
+
str
+
;
+
+
cfg = config.services.netbird.server.coturn;
+
in
+
+
{
+
options.services.netbird.server.coturn = {
+
enable = mkEnableOption "a Coturn server for Netbird, will also open the firewall on the configured range";
+
+
useAcmeCertificates = mkOption {
+
type = bool;
+
default = false;
+
description = ''
+
Whether to use ACME certificates corresponding to the given domain for the server.
+
'';
+
};
+
+
domain = mkOption {
+
type = str;
+
description = "The domain under which the coturn server runs.";
+
};
+
+
user = mkOption {
+
type = str;
+
default = "netbird";
+
description = ''
+
The username used by netbird to connect to the coturn server.
+
'';
+
};
+
+
password = mkOption {
+
type = nullOr str;
+
default = null;
+
description = ''
+
The password of the user used by netbird to connect to the coturn server.
+
'';
+
};
+
+
passwordFile = mkOption {
+
type = nullOr path;
+
default = null;
+
description = ''
+
The path to a file containing the password of the user used by netbird to connect to the coturn server.
+
'';
+
};
+
+
openPorts = mkOption {
+
type = listOf port;
+
default = with config.services.coturn; [
+
listening-port
+
alt-listening-port
+
tls-listening-port
+
alt-tls-listening-port
+
];
+
defaultText = literalExpression ''
+
with config.services.coturn; [
+
listening-port
+
alt-listening-port
+
tls-listening-port
+
alt-tls-listening-port
+
];
+
'';
+
+
description = ''
+
The list of ports used by coturn for listening to open in the firewall.
+
'';
+
};
+
};
+
+
config = mkIf cfg.enable (mkMerge [
+
{
+
assertions = [
+
{
+
assertion = (cfg.password == null) != (cfg.passwordFile == null);
+
message = "Exactly one of `password` or `passwordFile` must be given for the coturn setup.";
+
}
+
];
+
+
services.coturn =
+
{
+
enable = true;
+
+
realm = cfg.domain;
+
lt-cred-mech = true;
+
no-cli = true;
+
+
extraConfig = ''
+
fingerprint
+
user=${cfg.user}:${if cfg.password != null then cfg.password else "@password@"}
+
no-software-attribute
+
'';
+
}
+
// (optionalAttrs cfg.useAcmeCertificates {
+
cert = "@cert@";
+
pkey = "@pkey@";
+
});
+
+
systemd.services.coturn =
+
let
+
dir = config.security.acme.certs.${cfg.domain}.directory;
+
preStart' =
+
(optionalString (cfg.passwordFile != null) ''
+
${getExe pkgs.replace-secret} @password@ ${cfg.passwordFile} /run/coturn/turnserver.cfg
+
'')
+
+ (optionalString cfg.useAcmeCertificates ''
+
${getExe pkgs.replace-secret} @cert@ "$CREDENTIALS_DIRECTORY/cert.pem" /run/coturn/turnserver.cfg
+
${getExe pkgs.replace-secret} @pkey@ "$CREDENTIALS_DIRECTORY/pkey.pem" /run/coturn/turnserver.cfg
+
'');
+
in
+
(optionalAttrs (preStart' != "") { preStart = mkAfter preStart'; })
+
// (optionalAttrs cfg.useAcmeCertificates {
+
serviceConfig.LoadCredential = [
+
"cert.pem:${dir}/fullchain.pem"
+
"pkey.pem:${dir}/key.pem"
+
];
+
});
+
+
security.acme.certs.${cfg.domain}.postRun = optionalString cfg.useAcmeCertificates "systemctl restart coturn.service";
+
+
networking.firewall = {
+
allowedUDPPorts = cfg.openPorts;
+
allowedTCPPorts = cfg.openPorts;
+
+
allowedUDPPortRanges = [
+
{
+
from = cfg.minPort;
+
to = cfg.maxPort;
+
}
+
];
+
};
+
}
+
]);
+
}
+186
nixos/modules/services/networking/netbird/dashboard.nix
···
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
+
let
+
inherit (lib)
+
boolToString
+
concatStringsSep
+
hasAttr
+
isBool
+
mapAttrs
+
mkDefault
+
mkEnableOption
+
mkIf
+
mkOption
+
mkPackageOption
+
;
+
+
inherit (lib.types)
+
attrsOf
+
bool
+
either
+
package
+
str
+
submodule
+
;
+
+
toStringEnv = value: if isBool value then boolToString value else toString value;
+
+
cfg = config.services.netbird.server.dashboard;
+
in
+
+
{
+
options.services.netbird.server.dashboard = {
+
enable = mkEnableOption "the static netbird dashboard frontend";
+
+
package = mkPackageOption pkgs "netbird-dashboard" { };
+
+
enableNginx = mkEnableOption "Nginx reverse-proxy to serve the dashboard.";
+
+
domain = mkOption {
+
type = str;
+
default = "localhost";
+
description = "The domain under which the dashboard runs.";
+
};
+
+
managementServer = mkOption {
+
type = str;
+
description = "The address of the management server, used for the API endpoints.";
+
};
+
+
settings = mkOption {
+
type = submodule { freeformType = attrsOf (either str bool); };
+
+
defaultText = ''
+
{
+
AUTH_AUDIENCE = "netbird";
+
AUTH_CLIENT_ID = "netbird";
+
AUTH_SUPPORTED_SCOPES = "openid profile email";
+
NETBIRD_TOKEN_SOURCE = "idToken";
+
USE_AUTH0 = false;
+
}
+
'';
+
+
description = ''
+
An attribute set that will be used to substitute variables when building the dashboard.
+
Any values set here will be templated into the frontend and be public for anyone that can reach your website.
+
The exact values sadly aren't documented anywhere.
+
A starting point when searching for valid values is this [script](https://github.com/netbirdio/dashboard/blob/main/docker/init_react_envs.sh)
+
The only mandatory value is 'AUTH_AUTHORITY' as we cannot set a default value here.
+
'';
+
};
+
+
finalDrv = mkOption {
+
readOnly = true;
+
type = package;
+
description = ''
+
The derivation containing the final templated dashboard.
+
'';
+
};
+
};
+
+
config = mkIf cfg.enable {
+
assertions = [
+
{
+
assertion = hasAttr "AUTH_AUTHORITY" cfg.settings;
+
message = "The setting AUTH_AUTHORITY is required for the dasboard to function.";
+
}
+
];
+
+
services.netbird.server.dashboard = {
+
settings =
+
{
+
# Due to how the backend and frontend work this secret will be templated into the backend
+
# and then served statically from your website
+
# This enables you to login without the normally needed indirection through the backend
+
# but this also means anyone that can reach your website can
+
# fetch this secret, which is why there is no real need to put it into
+
# special options as its public anyway
+
# As far as I know leaking this secret is just
+
# an information leak as one can fetch some basic app
+
# informations from the IDP
+
# To actually do something one still needs to have login
+
# data and this secret so this being public will not
+
# suffice for anything just decreasing security
+
AUTH_CLIENT_SECRET = "";
+
+
NETBIRD_MGMT_API_ENDPOINT = cfg.managementServer;
+
NETBIRD_MGMT_GRPC_API_ENDPOINT = cfg.managementServer;
+
}
+
// (mapAttrs (_: mkDefault) {
+
# Those values have to be easily overridable
+
AUTH_AUDIENCE = "netbird"; # must be set for your devices to be able to log in
+
AUTH_CLIENT_ID = "netbird";
+
AUTH_SUPPORTED_SCOPES = "openid profile email";
+
NETBIRD_TOKEN_SOURCE = "idToken";
+
USE_AUTH0 = false;
+
});
+
+
# The derivation containing the templated dashboard
+
finalDrv =
+
pkgs.runCommand "netbird-dashboard"
+
{
+
nativeBuildInputs = [ pkgs.gettext ];
+
env = {
+
ENV_STR = concatStringsSep " " [
+
"$AUTH_AUDIENCE"
+
"$AUTH_AUTHORITY"
+
"$AUTH_CLIENT_ID"
+
"$AUTH_CLIENT_SECRET"
+
"$AUTH_REDIRECT_URI"
+
"$AUTH_SILENT_REDIRECT_URI"
+
"$AUTH_SUPPORTED_SCOPES"
+
"$NETBIRD_DRAG_QUERY_PARAMS"
+
"$NETBIRD_GOOGLE_ANALYTICS_ID"
+
"$NETBIRD_HOTJAR_TRACK_ID"
+
"$NETBIRD_MGMT_API_ENDPOINT"
+
"$NETBIRD_MGMT_GRPC_API_ENDPOINT"
+
"$NETBIRD_TOKEN_SOURCE"
+
"$USE_AUTH0"
+
];
+
} // (mapAttrs (_: toStringEnv) cfg.settings);
+
}
+
''
+
cp -R ${cfg.package} build
+
+
find build -type d -exec chmod 755 {} \;
+
OIDC_TRUSTED_DOMAINS="build/OidcTrustedDomains.js"
+
+
envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS.tmpl" > "$OIDC_TRUSTED_DOMAINS"
+
+
for f in $(grep -R -l AUTH_SUPPORTED_SCOPES build/); do
+
mv "$f" "$f.copy"
+
envsubst "$ENV_STR" < "$f.copy" > "$f"
+
rm "$f.copy"
+
done
+
+
cp -R build $out
+
'';
+
};
+
+
services.nginx = mkIf cfg.enableNginx {
+
enable = true;
+
+
virtualHosts.${cfg.domain} = {
+
locations = {
+
"/" = {
+
root = cfg.finalDrv;
+
tryFiles = "$uri $uri.html $uri/ =404";
+
};
+
+
"/404.html".extraConfig = ''
+
internal;
+
'';
+
};
+
+
extraConfig = ''
+
error_page 404 /404.html;
+
'';
+
};
+
};
+
};
+
}
+460
nixos/modules/services/networking/netbird/management.nix
···
···
+
{
+
config,
+
lib,
+
pkgs,
+
utils,
+
...
+
}:
+
+
let
+
inherit (lib)
+
any
+
concatMap
+
getExe'
+
literalExpression
+
mkEnableOption
+
mkIf
+
mkOption
+
mkPackageOption
+
optional
+
recursiveUpdate
+
;
+
+
inherit (lib.types)
+
bool
+
enum
+
listOf
+
port
+
str
+
;
+
+
inherit (utils) escapeSystemdExecArgs genJqSecretsReplacementSnippet;
+
+
stateDir = "/var/lib/netbird-mgmt";
+
+
settingsFormat = pkgs.formats.json { };
+
+
defaultSettings = {
+
Stuns = [
+
{
+
Proto = "udp";
+
URI = "stun:${cfg.turnDomain}:3478";
+
Username = "";
+
Password = null;
+
}
+
];
+
+
TURNConfig = {
+
Turns = [
+
{
+
Proto = "udp";
+
URI = "turn:${cfg.turnDomain}:${builtins.toString cfg.turnPort}";
+
Username = "netbird";
+
Password = "netbird";
+
}
+
];
+
+
CredentialsTTL = "12h";
+
Secret = "not-secure-secret";
+
TimeBasedCredentials = false;
+
};
+
+
Signal = {
+
Proto = "https";
+
URI = "${cfg.domain}:443";
+
Username = "";
+
Password = null;
+
};
+
+
ReverseProxy = {
+
TrustedHTTPProxies = [ ];
+
TrustedHTTPProxiesCount = 0;
+
TrustedPeers = [ "0.0.0.0/0" ];
+
};
+
+
Datadir = "${stateDir}/data";
+
DataStoreEncryptionKey = "very-insecure-key";
+
StoreConfig = {
+
Engine = "sqlite";
+
};
+
+
HttpConfig = {
+
Address = "127.0.0.1:${builtins.toString cfg.port}";
+
IdpSignKeyRefreshEnabled = true;
+
OIDCConfigEndpoint = cfg.oidcConfigEndpoint;
+
};
+
+
IdpManagerConfig = {
+
ManagerType = "none";
+
ClientConfig = {
+
Issuer = "";
+
TokenEndpoint = "";
+
ClientID = "netbird";
+
ClientSecret = "";
+
GrantType = "client_credentials";
+
};
+
+
ExtraConfig = { };
+
Auth0ClientCredentials = null;
+
AzureClientCredentials = null;
+
KeycloakClientCredentials = null;
+
ZitadelClientCredentials = null;
+
};
+
+
DeviceAuthorizationFlow = {
+
Provider = "none";
+
ProviderConfig = {
+
Audience = "netbird";
+
Domain = null;
+
ClientID = "netbird";
+
TokenEndpoint = null;
+
DeviceAuthEndpoint = "";
+
Scope = "openid profile email";
+
UseIDToken = false;
+
};
+
};
+
+
PKCEAuthorizationFlow = {
+
ProviderConfig = {
+
Audience = "netbird";
+
ClientID = "netbird";
+
ClientSecret = "";
+
AuthorizationEndpoint = "";
+
TokenEndpoint = "";
+
Scope = "openid profile email";
+
RedirectURLs = [ "http://localhost:53000" ];
+
UseIDToken = false;
+
};
+
};
+
};
+
+
managementConfig = recursiveUpdate defaultSettings cfg.settings;
+
+
managementFile = settingsFormat.generate "config.json" managementConfig;
+
+
cfg = config.services.netbird.server.management;
+
in
+
+
{
+
options.services.netbird.server.management = {
+
enable = mkEnableOption "Netbird Management Service.";
+
+
package = mkPackageOption pkgs "netbird" { };
+
+
domain = mkOption {
+
type = str;
+
description = "The domain under which the management API runs.";
+
};
+
+
turnDomain = mkOption {
+
type = str;
+
description = "The domain of the TURN server to use.";
+
};
+
+
turnPort = mkOption {
+
type = port;
+
default = 3478;
+
description = ''
+
The port of the TURN server to use.
+
'';
+
};
+
+
dnsDomain = mkOption {
+
type = str;
+
default = "netbird.selfhosted";
+
description = "Domain used for peer resolution.";
+
};
+
+
singleAccountModeDomain = mkOption {
+
type = str;
+
default = "netbird.selfhosted";
+
description = ''
+
Enables single account mode.
+
This means that all the users will be under the same account grouped by the specified domain.
+
If the installation has more than one account, the property is ineffective.
+
'';
+
};
+
+
disableAnonymousMetrics = mkOption {
+
type = bool;
+
default = true;
+
description = "Disables push of anonymous usage metrics to NetBird.";
+
};
+
+
disableSingleAccountMode = mkOption {
+
type = bool;
+
default = false;
+
description = ''
+
If set to true, disables single account mode.
+
The `singleAccountModeDomain` property will be ignored and every new user will have a separate NetBird account.
+
'';
+
};
+
+
port = mkOption {
+
type = port;
+
default = 8011;
+
description = "Internal port of the management server.";
+
};
+
+
extraOptions = mkOption {
+
type = listOf str;
+
default = [ ];
+
description = ''
+
Additional options given to netbird-mgmt as commandline arguments.
+
'';
+
};
+
+
oidcConfigEndpoint = mkOption {
+
type = str;
+
description = "The oidc discovery endpoint.";
+
example = "https://example.eu.auth0.com/.well-known/openid-configuration";
+
};
+
+
settings = mkOption {
+
inherit (settingsFormat) type;
+
+
defaultText = literalExpression ''
+
defaultSettings = {
+
Stuns = [
+
{
+
Proto = "udp";
+
URI = "stun:''${cfg.turnDomain}:3478";
+
Username = "";
+
Password = null;
+
}
+
];
+
+
TURNConfig = {
+
Turns = [
+
{
+
Proto = "udp";
+
URI = "turn:''${cfg.turnDomain}:3478";
+
Username = "netbird";
+
Password = "netbird";
+
}
+
];
+
+
CredentialsTTL = "12h";
+
Secret = "not-secure-secret";
+
TimeBasedCredentials = false;
+
};
+
+
Signal = {
+
Proto = "https";
+
URI = "''${cfg.domain}:443";
+
Username = "";
+
Password = null;
+
};
+
+
ReverseProxy = {
+
TrustedHTTPProxies = [ ];
+
TrustedHTTPProxiesCount = 0;
+
TrustedPeers = [ "0.0.0.0/0" ];
+
};
+
+
Datadir = "''${stateDir}/data";
+
DataStoreEncryptionKey = "genEVP6j/Yp2EeVujm0zgqXrRos29dQkpvX0hHdEUlQ=";
+
StoreConfig = { Engine = "sqlite"; };
+
+
HttpConfig = {
+
Address = "127.0.0.1:''${builtins.toString cfg.port}";
+
IdpSignKeyRefreshEnabled = true;
+
OIDCConfigEndpoint = cfg.oidcConfigEndpoint;
+
};
+
+
IdpManagerConfig = {
+
ManagerType = "none";
+
ClientConfig = {
+
Issuer = "";
+
TokenEndpoint = "";
+
ClientID = "netbird";
+
ClientSecret = "";
+
GrantType = "client_credentials";
+
};
+
+
ExtraConfig = { };
+
Auth0ClientCredentials = null;
+
AzureClientCredentials = null;
+
KeycloakClientCredentials = null;
+
ZitadelClientCredentials = null;
+
};
+
+
DeviceAuthorizationFlow = {
+
Provider = "none";
+
ProviderConfig = {
+
Audience = "netbird";
+
Domain = null;
+
ClientID = "netbird";
+
TokenEndpoint = null;
+
DeviceAuthEndpoint = "";
+
Scope = "openid profile email offline_access api";
+
UseIDToken = false;
+
};
+
};
+
+
PKCEAuthorizationFlow = {
+
ProviderConfig = {
+
Audience = "netbird";
+
ClientID = "netbird";
+
ClientSecret = "";
+
AuthorizationEndpoint = "";
+
TokenEndpoint = "";
+
Scope = "openid profile email offline_access api";
+
RedirectURLs = "http://localhost:53000";
+
UseIDToken = false;
+
};
+
};
+
};
+
'';
+
+
default = { };
+
+
description = ''
+
Configuration of the netbird management server.
+
Options containing secret data should be set to an attribute set containing the attribute _secret
+
- a string pointing to a file containing the value the option should be set to.
+
See the example to get a better picture of this: in the resulting management.json file,
+
the `DataStoreEncryptionKey` key will be set to the contents of the /run/agenix/netbird_mgmt-data_store_encryption_key file.
+
'';
+
+
example = {
+
DataStoreEncryptionKey = {
+
_secret = "/run/agenix/netbird_mgmt-data_store_encryption_key";
+
};
+
};
+
};
+
+
logLevel = mkOption {
+
type = enum [
+
"ERROR"
+
"WARN"
+
"INFO"
+
"DEBUG"
+
];
+
default = "INFO";
+
description = "Log level of the netbird services.";
+
};
+
+
enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird management service.";
+
};
+
+
config = mkIf cfg.enable {
+
warnings =
+
concatMap
+
(
+
{ check, name }:
+
optional check "${name} is world-readable in the Nix Store, you should provide it as a _secret."
+
)
+
[
+
{
+
check = builtins.isString managementConfig.TURNConfig.Secret;
+
name = "The TURNConfig.secret";
+
}
+
{
+
check = builtins.isString managementConfig.DataStoreEncryptionKey;
+
name = "The DataStoreEncryptionKey";
+
}
+
{
+
check = any (T: (T ? Password) && builtins.isString T.Password) managementConfig.TURNConfig.Turns;
+
name = "A Turn configuration's password";
+
}
+
];
+
+
systemd.services.netbird-management = {
+
description = "The management server for Netbird, a wireguard VPN";
+
documentation = [ "https://netbird.io/docs/" ];
+
+
after = [ "network.target" ];
+
wantedBy = [ "multi-user.target" ];
+
restartTriggers = [ managementFile ];
+
+
preStart = genJqSecretsReplacementSnippet managementConfig "${stateDir}/management.json";
+
+
serviceConfig = {
+
ExecStart = escapeSystemdExecArgs (
+
[
+
(getExe' cfg.package "netbird-mgmt")
+
"management"
+
# Config file
+
"--config"
+
"${stateDir}/management.json"
+
# Data directory
+
"--datadir"
+
"${stateDir}/data"
+
# DNS domain
+
"--dns-domain"
+
cfg.dnsDomain
+
# Port to listen on
+
"--port"
+
cfg.port
+
# Log to stdout
+
"--log-file"
+
"console"
+
# Log level
+
"--log-level"
+
cfg.logLevel
+
#
+
"--idp-sign-key-refresh-enabled"
+
# Domain for internal resolution
+
"--single-account-mode-domain"
+
cfg.singleAccountModeDomain
+
]
+
++ (optional cfg.disableAnonymousMetrics "--disable-anonymous-metrics")
+
++ (optional cfg.disableSingleAccountMode "--disable-single-account-mode")
+
++ cfg.extraOptions
+
);
+
Restart = "always";
+
RuntimeDirectory = "netbird-mgmt";
+
StateDirectory = [
+
"netbird-mgmt"
+
"netbird-mgmt/data"
+
];
+
WorkingDirectory = stateDir;
+
+
# hardening
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateMounts = true;
+
PrivateTmp = true;
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectSystem = true;
+
RemoveIPC = true;
+
RestrictNamespaces = true;
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
};
+
+
stopIfChanged = false;
+
};
+
+
services.nginx = mkIf cfg.enableNginx {
+
enable = true;
+
+
virtualHosts.${cfg.domain} = {
+
locations = {
+
"/api".proxyPass = "http://localhost:${builtins.toString cfg.port}";
+
+
"/management.ManagementService/".extraConfig = ''
+
# This is necessary so that grpc connections do not get closed early
+
# see https://stackoverflow.com/a/67805465
+
client_body_timeout 1d;
+
+
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+
grpc_pass grpc://localhost:${builtins.toString cfg.port};
+
grpc_read_timeout 1d;
+
grpc_send_timeout 1d;
+
grpc_socket_keepalive on;
+
'';
+
};
+
};
+
};
+
};
+
}
+42
nixos/modules/services/networking/netbird/server.md
···
···
+
# Netbird server {#module-services-netbird-server}
+
+
NetBird is a VPN built on top of WireGuard® making it easy to create secure private networks for your organization or home.
+
+
## Quickstart {#module-services-netbird-server-quickstart}
+
+
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).
+
+
There are quite a few settings that need to be passed to Netbird for it to function, and a minimal config looks like :
+
+
```nix
+
services.netbird.server = {
+
enable = true;
+
+
domain = "netbird.example.selfhosted";
+
+
enableNginx = true;
+
+
coturn = {
+
enable = true;
+
+
passwordFile = "/path/to/a/secret/password";
+
};
+
+
management = {
+
oidcConfigEndpoint = "https://sso.example.selfhosted/oauth2/openid/netbird/.well-known/openid-configuration";
+
+
settings = {
+
TURNConfig = {
+
Turns = [
+
{
+
Proto = "udp";
+
URI = "turn:netbird.example.selfhosted:3478";
+
Username = "netbird";
+
Password._secret = "/path/to/a/secret/password";
+
}
+
];
+
};
+
};
+
};
+
};
+
```
+67
nixos/modules/services/networking/netbird/server.nix
···
···
+
{ config, lib, ... }:
+
+
let
+
inherit (lib)
+
mkEnableOption
+
mkIf
+
mkOption
+
optionalAttrs
+
;
+
+
inherit (lib.types) str;
+
+
cfg = config.services.netbird.server;
+
in
+
+
{
+
meta = {
+
maintainers = with lib.maintainers; [ thubrecht ];
+
doc = ./server.md;
+
};
+
+
# Import the separate components
+
imports = [
+
./coturn.nix
+
./dashboard.nix
+
./management.nix
+
./signal.nix
+
];
+
+
options.services.netbird.server = {
+
enable = mkEnableOption "Netbird Server stack, comprising the dashboard, management API and signal service";
+
+
enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird server services.";
+
+
domain = mkOption {
+
type = str;
+
description = "The domain under which the netbird server runs.";
+
};
+
};
+
+
config = mkIf cfg.enable {
+
services.netbird.server = {
+
dashboard = {
+
inherit (cfg) enable domain enableNginx;
+
+
managementServer = "https://${cfg.domain}";
+
};
+
+
management =
+
{
+
inherit (cfg) enable domain enableNginx;
+
}
+
// (optionalAttrs cfg.coturn.enable {
+
turnDomain = cfg.domain;
+
turnPort = config.services.coturn.tls-listening-port;
+
});
+
+
signal = {
+
inherit (cfg) enable domain enableNginx;
+
};
+
+
coturn = {
+
inherit (cfg) domain;
+
};
+
};
+
};
+
}
+123
nixos/modules/services/networking/netbird/signal.nix
···
···
+
{
+
config,
+
lib,
+
pkgs,
+
utils,
+
...
+
}:
+
+
let
+
inherit (lib)
+
getExe'
+
mkEnableOption
+
mkIf
+
mkPackageOption
+
mkOption
+
;
+
+
inherit (lib.types) enum port str;
+
+
inherit (utils) escapeSystemdExecArgs;
+
+
cfg = config.services.netbird.server.signal;
+
in
+
+
{
+
options.services.netbird.server.signal = {
+
enable = mkEnableOption "Netbird's Signal Service";
+
+
package = mkPackageOption pkgs "netbird" { };
+
+
enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird signal service.";
+
+
domain = mkOption {
+
type = str;
+
description = "The domain name for the signal service.";
+
};
+
+
port = mkOption {
+
type = port;
+
default = 8012;
+
description = "Internal port of the signal server.";
+
};
+
+
logLevel = mkOption {
+
type = enum [
+
"ERROR"
+
"WARN"
+
"INFO"
+
"DEBUG"
+
];
+
default = "INFO";
+
description = "Log level of the netbird signal service.";
+
};
+
};
+
+
config = mkIf cfg.enable {
+
systemd.services.netbird-signal = {
+
after = [ "network.target" ];
+
wantedBy = [ "multi-user.target" ];
+
+
serviceConfig = {
+
ExecStart = escapeSystemdExecArgs [
+
(getExe' cfg.package "netbird-signal")
+
"run"
+
# Port to listen on
+
"--port"
+
cfg.port
+
# Log to stdout
+
"--log-file"
+
"console"
+
# Log level
+
"--log-level"
+
cfg.logLevel
+
];
+
+
Restart = "always";
+
RuntimeDirectory = "netbird-mgmt";
+
StateDirectory = "netbird-mgmt";
+
WorkingDirectory = "/var/lib/netbird-mgmt";
+
+
# hardening
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateMounts = true;
+
PrivateTmp = true;
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectSystem = true;
+
RemoveIPC = true;
+
RestrictNamespaces = true;
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
};
+
+
stopIfChanged = false;
+
};
+
+
services.nginx = mkIf cfg.enableNginx {
+
enable = true;
+
+
virtualHosts.${cfg.domain} = {
+
locations."/signalexchange.SignalExchange/".extraConfig = ''
+
# This is necessary so that grpc connections do not get closed early
+
# see https://stackoverflow.com/a/67805465
+
client_body_timeout 1d;
+
+
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+
grpc_pass grpc://localhost:${builtins.toString cfg.port};
+
grpc_read_timeout 1d;
+
grpc_send_timeout 1d;
+
grpc_socket_keepalive on;
+
'';
+
};
+
};
+
};
+
}
+35
pkgs/by-name/ne/netbird-dashboard/package.nix
···
···
+
{
+
lib,
+
buildNpmPackage,
+
fetchFromGitHub,
+
}:
+
+
buildNpmPackage rec {
+
pname = "netbird-dashboard";
+
version = "2.3.0";
+
+
src = fetchFromGitHub {
+
owner = "netbirdio";
+
repo = "dashboard";
+
rev = "v${version}";
+
hash = "sha256-4aZ7WGLhjpTHOggJ+sRdln0YOG3jf7TKT9/bH/n3LmQ=";
+
};
+
+
npmDepsHash = "sha256-ts3UuThIMf+wwSr3DpZ+k1i9RnHi/ltvhD/7lomVxQk=";
+
npmFlags = [ "--legacy-peer-deps" ];
+
+
installPhase = ''
+
cp -R out $out
+
'';
+
+
env = {
+
CYPRESS_INSTALL_BINARY = 0;
+
};
+
+
meta = with lib; {
+
description = "NetBird Management Service Web UI Panel";
+
homepage = "https://github.com/netbirdio/dashboard";
+
license = licenses.bsd3;
+
maintainers = with maintainers; [ thubrecht ];
+
};
+
}