Compare commits
4 commits
610ed948b1
...
0daa9d00e4
Author | SHA1 | Date | |
---|---|---|---|
Patrick | 0daa9d00e4 | ||
Patrick | 807b0fdb2a | ||
Patrick | 2619c11018 | ||
Patrick | 5bafab2e9d |
BIN
.data/homebox.db
Normal file
BIN
.data/homebox.db
Normal file
Binary file not shown.
BIN
.data/homebox.db-shm
Normal file
BIN
.data/homebox.db-shm
Normal file
Binary file not shown.
BIN
.data/homebox.db-wal
Normal file
BIN
.data/homebox.db-wal
Normal file
Binary file not shown.
|
@ -3,6 +3,8 @@
|
|||
lib,
|
||||
...
|
||||
}: {
|
||||
disabledModules = ["services/networking/netbird/server.nix"];
|
||||
imports = [../../modules/netbird/server.nix];
|
||||
wireguard.elisabeth = {
|
||||
client.via = "elisabeth";
|
||||
firewallRuleForNode.elisabeth.allowedTCPPorts = [80 3000 3001];
|
||||
|
@ -33,7 +35,7 @@
|
|||
domain = "netbird.${config.secrets.secrets.global.domains.web}";
|
||||
|
||||
dashboard = {
|
||||
enableNginx = lib.mkForce true;
|
||||
enableNginx = true;
|
||||
settings = {
|
||||
AUTH_AUTHORITY = "https://auth.${config.secrets.secrets.global.domains.web}/oauth2/openid/netbird";
|
||||
};
|
||||
|
@ -52,23 +54,12 @@
|
|||
settings = {
|
||||
TURNConfig = {
|
||||
Secret._secret = config.age.secrets.coturnSecret.path;
|
||||
# TODO I think this is broken
|
||||
Turns = [
|
||||
{
|
||||
Proto = "udp";
|
||||
URI = "turn:${config.services.netbird.server.management.turnDomain}:${builtins.toString config.services.netbird.server.management.turnPort}";
|
||||
Username = "netbird";
|
||||
|
||||
Password._secret = config.age.secrets.coturnPassword.path;
|
||||
}
|
||||
];
|
||||
};
|
||||
DataStoreEncryptionKey._secret = config.age.secrets.dataEnc.path;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
security.acme.certs = lib.mkForce {};
|
||||
environment.persistence."/persist".directories = [
|
||||
{
|
||||
directory = "/var/lib/netbird-mgmt";
|
||||
|
|
162
modules/netbird/coturn.nix
Normal file
162
modules/netbird/coturn.nix
Normal file
|
@ -0,0 +1,162 @@
|
|||
{
|
||||
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.
|
||||
Be advised this will be world readable in the nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
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 = mkIf cfg.useAcmeCertificates {${cfg.domain}.postRun = "systemctl restart coturn.service";};
|
||||
|
||||
networking.firewall = {
|
||||
allowedUDPPorts = cfg.openPorts;
|
||||
allowedTCPPorts = cfg.openPorts;
|
||||
|
||||
allowedUDPPortRanges = with config.services.coturn; [
|
||||
{
|
||||
from = min-port;
|
||||
to = max-port;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
]);
|
||||
}
|
189
modules/netbird/dashboard.nix
Normal file
189
modules/netbird/dashboard.nix
Normal file
|
@ -0,0 +1,189 @@
|
|||
{
|
||||
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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
461
modules/netbird/management.nix
Normal file
461
modules/netbird/management.nix
Normal file
|
@ -0,0 +1,461 @@
|
|||
{
|
||||
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
modules/netbird/server.md
Normal file
42
modules/netbird/server.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# 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";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
89
modules/netbird/server.nix
Normal file
89
modules/netbird/server.nix
Normal file
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
mkDefault
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
optionalAttrs
|
||||
;
|
||||
|
||||
inherit (lib.types) str;
|
||||
|
||||
cfg = config.services.netbird.server;
|
||||
in {
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [thubrecht patrickdag];
|
||||
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 = {
|
||||
domain = mkDefault cfg.domain;
|
||||
enable = mkDefault cfg.enable;
|
||||
enableNginx = mkDefault cfg.enableNginx;
|
||||
|
||||
managementServer = "https://${cfg.domain}";
|
||||
};
|
||||
|
||||
management =
|
||||
{
|
||||
domain = mkDefault cfg.domain;
|
||||
enable = mkDefault cfg.enable;
|
||||
enableNginx = mkDefault cfg.enableNginx;
|
||||
}
|
||||
// (optionalAttrs cfg.coturn.enable rec {
|
||||
turnDomain = cfg.domain;
|
||||
turnPort = config.services.coturn.tls-listening-port;
|
||||
# We cannot merge a list of attrsets so we have to redefine the whole list
|
||||
settings = {
|
||||
TURNConfig.Turns = mkDefault [
|
||||
{
|
||||
Proto = "udp";
|
||||
URI = "turn:${turnDomain}:${builtins.toString turnPort}";
|
||||
Username = "netbird";
|
||||
Password =
|
||||
if (cfg.coturn.password != null)
|
||||
then cfg.coturn.password
|
||||
else {_secret = cfg.coturn.passwordFile;};
|
||||
}
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
signal = {
|
||||
domain = mkDefault cfg.domain;
|
||||
enable = mkDefault cfg.enable;
|
||||
enableNginx = mkDefault cfg.enableNginx;
|
||||
};
|
||||
|
||||
coturn = {
|
||||
domain = mkDefault cfg.domain;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
120
modules/netbird/signal.nix
Normal file
120
modules/netbird/signal.nix
Normal file
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
zsh-histdb = super.callPackage ./zsh-histdb.nix {};
|
||||
your_spotify = super.callPackage ./your_spotify.nix {};
|
||||
actual = super.callPackage ./actual.nix {};
|
||||
homebox = super.callPackage ./homebox.nix {};
|
||||
deploy = super.callPackage ./deploy.nix {};
|
||||
minify = super.callPackage ./minify {};
|
||||
j-link = super.callPackage ./jlink.nix {};
|
||||
|
|
132
pkgs/homebox.nix
Normal file
132
pkgs/homebox.nix
Normal file
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
stdenvNoCC,
|
||||
jq,
|
||||
moreutils,
|
||||
nodePackages,
|
||||
cacert,
|
||||
lib,
|
||||
buildGoModule,
|
||||
fetchFromGitHub,
|
||||
}: let
|
||||
pname = "homebox";
|
||||
version = "0.10.3";
|
||||
src = "${fetchFromGitHub {
|
||||
owner = "hay-kot";
|
||||
repo = "homebox";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-Hej/dM0BgtRWiMOpp/SDVr3H1IbYb935T1pfX8apjpE=";
|
||||
# Inspired by: https://github.com/NixOS/nixpkgs/blob/f2d7a289c5a5ece8521dd082b81ac7e4a57c2c5c/pkgs/applications/graphics/pdfcpu/default.nix#L20-L32
|
||||
# The intention here is to write the information into files in the `src`'s
|
||||
# `$out`, and use them later in other phases (in this case `preBuild`).
|
||||
# In order to keep determinism, we also delete the `.git` directory
|
||||
# afterwards, imitating the default behavior of `leaveDotGit = false`.
|
||||
# More info about git log format can be found at `git-log(1)` manpage.
|
||||
leaveDotGit = true;
|
||||
postFetch = ''
|
||||
cd "$out"
|
||||
git log -1 --pretty=%H > "backend/COMMIT"
|
||||
git log -1 --pretty=%cd --date=format:'%Y-%m-%dT%H:%M:%SZ' > "backend/SOURCE_DATE"
|
||||
rm -rf ".git"
|
||||
'';
|
||||
}}";
|
||||
|
||||
frontend = stdenvNoCC.mkDerivation {
|
||||
pname = "${pname}-frontend";
|
||||
inherit version;
|
||||
|
||||
src = "${src}/frontend";
|
||||
|
||||
preBuild = ''
|
||||
export HOME=$(mktemp -d)
|
||||
export STORE_PATH=$(mktemp -d)
|
||||
|
||||
pnpm config set store-dir "${pnpm-deps}"
|
||||
pnpm install --offline --frozen-lockfile --shamefully-hoist
|
||||
patchShebangs node_modules/{*,.*}
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
pnpm build
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
env.NUXT_TELEMETRY_DISABLED = 1;
|
||||
|
||||
nativeBuildInputs = [
|
||||
nodePackages.pnpm
|
||||
#breakpointHook
|
||||
];
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out
|
||||
cp -r .output/public/* $out/
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
pnpm-deps = stdenvNoCC.mkDerivation {
|
||||
pname = "${pname}-pnpm-deps";
|
||||
inherit version;
|
||||
src = "${src}/frontend";
|
||||
|
||||
nativeBuildInputs = [
|
||||
jq
|
||||
moreutils
|
||||
nodePackages.pnpm
|
||||
cacert
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
export HOME=$(mktemp -d)
|
||||
pnpm config set store-dir $out
|
||||
# This version of the package has different versions of esbuild as a dependency.
|
||||
# You can use the command below to get esbuild binaries for a specific platform and calculate hashes for that platforms. (linux, darwin for os, and x86, arm64, ia32 for cpu)
|
||||
# cat package.json | jq '.pnpm.supportedArchitectures += { "os": ["linux"], "cpu": ["arm64"] }' | sponge package.json
|
||||
pnpm install --frozen-lockfile --ignore-script
|
||||
|
||||
# Remove timestamp and sort the json files.
|
||||
rm -rf $out/v3/tmp
|
||||
for f in $(find $out -name "*.json"); do
|
||||
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
|
||||
jq --sort-keys . $f | sponge $f
|
||||
done
|
||||
'';
|
||||
|
||||
dontBuild = true;
|
||||
dontFixup = true;
|
||||
outputHashMode = "recursive";
|
||||
outputHash = "sha256-BVZSdc8e6v+paMzMYazEdnKSNw+OnCpjSzGSEKxVl24=";
|
||||
};
|
||||
in
|
||||
buildGoModule {
|
||||
inherit pname version;
|
||||
src = "${src}/backend";
|
||||
|
||||
vendorHash = "sha256-TtFz+dDpoMs3PAQjiYQm1+Q6prn4Hiaf7xqWt41oY7w=";
|
||||
|
||||
CGO_ENABLED = 0;
|
||||
GOOS = "linux";
|
||||
doCheck = false;
|
||||
|
||||
# options used by upstream:
|
||||
# https://github.com/simulot/immich-go/blob/0.13.2/.goreleaser.yaml
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-extldflags=-static"
|
||||
"-X main.version=${version}"
|
||||
];
|
||||
|
||||
preBuild = ''
|
||||
ldflags+=" -X main.commit=$(cat COMMIT)"
|
||||
ldflags+=" -X main.date=$(cat SOURCE_DATE)"
|
||||
mkdir -p ./app/api/static/public
|
||||
cp -r ${frontend}/* ./app/api/static/public
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
};
|
||||
}
|
|
@ -52,6 +52,7 @@
|
|||
zls.enable = true;
|
||||
pyright.enable = true;
|
||||
texlab.enable = true;
|
||||
ocamllsp.enable = true;
|
||||
java-language-server.enable = true;
|
||||
nil_ls = {
|
||||
enable = true;
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
mumble
|
||||
zotero
|
||||
timer
|
||||
|
||||
ocaml
|
||||
dune_3
|
||||
ocamlformat # formatter
|
||||
ocamlPackages.ocaml-lsp
|
||||
ocamlPackages.utop
|
||||
ocamlPackages.mparser
|
||||
ocamlPackages.ounit2
|
||||
ocamlPackages.qcheck
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue