Compare commits

...

8 commits

Author SHA1 Message Date
f894c27799 Pass docker group id as string for dockhand 2026-04-27 23:54:37 +02:00
11ed5a80d7 Disable automatic reboots after upgrades in busch
Due to encrypted root the server does not boot without input.
Missing tpm2 does not allow for unattended unlocks.
2026-04-27 23:51:13 +02:00
4881f836c9 Allow disabling automatic reboots in module 2026-04-27 23:50:54 +02:00
530695d941 Add netbird service to busch 2026-04-27 23:29:22 +02:00
04ba2761b5 Use user module for busch 2026-04-27 23:12:58 +02:00
81f4554dd7 Add dependencies between netbird and traefik containers
When setting the explicit ip of the traefik container in the webproxy
network, this resolves the ip of the traefik container changing between
restarts.
2026-04-27 23:09:34 +02:00
f7c4620378 Enable netbird-docker on srv03 2026-04-25 23:58:47 +02:00
d1cfaf7acf Add netbird-docker volume 2026-04-25 23:58:32 +02:00
9 changed files with 260 additions and 19 deletions

8
flake.lock generated
View file

@ -338,11 +338,11 @@
"secrets": { "secrets": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1777076192, "lastModified": 1777152364,
"narHash": "sha256-N7n2OPN2IRWwL73Cr6mc5nNhucHmMeFas9hQ/NF0bFg=", "narHash": "sha256-yS8TxtPFFf7xIDNbsErZUnTBLn2fnyCcC4On+t3v1Zs=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "34ff1c4b0460a2e103a8fec183f53f274dc123ed", "rev": "bfb7da1297d73100a56a044d09792fc6e59357e6",
"revCount": 32, "revCount": 34,
"type": "git", "type": "git",
"url": "ssh://git@git.jfreudenberger.de/JuliusFreudenberger/nix-private.git" "url": "ssh://git@git.jfreudenberger.de/JuliusFreudenberger/nix-private.git"
}, },

View file

@ -120,6 +120,9 @@
specialArgs = { specialArgs = {
inherit inputs outputs; inherit inputs outputs;
pkgs-unstable = import nixpkgs-unstable {
inherit system;
};
}; };
modules = [ modules = [

View file

@ -1,4 +1,4 @@
{ inputs, outputs, config, lib, pkgs, ... }: { inputs, outputs, config, lib, pkgs, pkgs-unstable, ... }:
{ {
imports = imports =
@ -7,6 +7,7 @@
../../modules/nix.nix ../../modules/nix.nix
../../modules/auto-upgrade.nix ../../modules/auto-upgrade.nix
../../users/julius/nixos-server.nix
../../modules/locale.nix ../../modules/locale.nix
../../modules/server-cli.nix ../../modules/server-cli.nix
../../modules/sshd.nix ../../modules/sshd.nix
@ -23,21 +24,29 @@
}; };
tmp.useTmpfs = true; tmp.useTmpfs = true;
}; };
system.autoUpgrade.allowReboot = false;
networking.hostName = "busch"; # Define your hostname. networking.hostName = "busch"; # Define your hostname.
users = {
users = { services.netbird = {
julius = { package = pkgs-unstable.netbird;
isNormalUser = true; useRoutingFeatures = "both";
uid = 1000; clients.wt0 = {
extraGroups = [ "wheel" "julius" ]; hardened = false;
login = {
enable = true;
setupKeyFile = (pkgs.writeText "setupKey" ''
A99F5508-D543-40B7-A31A-A8931B1AE246
'').outPath;
}; };
}; port = 51820;
groups = { environment = {
julius = { NB_MANAGEMENT_URL = "https://netbird.jfreudenberger.de";
gid = 1000;
}; };
}; };
}; };
systemd.services.${config.services.netbird.clients.wt0.service.name}.path = [ pkgs.shadow ];
nix.settings = { nix.settings = {
substituters = [ substituters = [

View file

@ -16,6 +16,7 @@
../../modules/docker.nix ../../modules/docker.nix
../../modules/traefik.nix ../../modules/traefik.nix
../../modules/pocket-id.nix ../../modules/pocket-id.nix
../../modules/netbird-docker.nix
../../modules/auto-upgrade.nix ../../modules/auto-upgrade.nix
"${inputs.secrets}/modules/opkssh.nix" "${inputs.secrets}/modules/opkssh.nix"
# Include the results of the hardware scan. # Include the results of the hardware scan.
@ -42,6 +43,24 @@
}; };
environmentFile = config.age.secrets.pocket-id.path; environmentFile = config.age.secrets.pocket-id.path;
}; };
netbird-docker = {
enable = true;
secrets = config.age.secrets.netbird-server;
proxy = {
domain = "netbird.jfreudenberger.de";
token-secret = config.age.secrets.netbird-proxy;
};
};
netbird.server = let
domain = "netbird.jfreudenberger.de";
in {
domain = domain;
management.domain = domain;
dashboard.domain = domain;
signal.domain = domain;
management.oidcConfigEndpoint = "https://login.jfreudenberger.de/.well-known/openid-configuration";
};
}; };
systemd.network = { systemd.network = {

View file

@ -3,5 +3,7 @@
age.secrets = { age.secrets = {
inwx.file = "${inputs.secrets}/secrets/dns-management/inwx"; inwx.file = "${inputs.secrets}/secrets/dns-management/inwx";
pocket-id.file = "${inputs.secrets}/secrets/srv03/pocket-id"; pocket-id.file = "${inputs.secrets}/secrets/srv03/pocket-id";
netbird-server.file = "${inputs.secrets}/secrets/srv03/netbird-server";
netbird-proxy.file = "${inputs.secrets}/secrets/srv03/netbird-proxy";
}; };
} }

View file

@ -1,6 +1,7 @@
{ {
inputs, inputs,
pkgs, pkgs,
lib,
... ...
}: { }: {
system.autoUpgrade = { system.autoUpgrade = {
@ -12,7 +13,7 @@
flake = inputs.self.outPath; flake = inputs.self.outPath;
dates = "02:00"; dates = "02:00";
randomizedDelaySec = "45min"; randomizedDelaySec = "45min";
allowReboot = true; allowReboot = lib.mkDefault true;
rebootWindow = { rebootWindow = {
lower = "01:00"; lower = "01:00";
upper = "05:00"; upper = "05:00";

View file

@ -38,7 +38,7 @@ in {
}; };
extraOptions = [ extraOptions = [
''--mount=type=volume,source=dockhand-data,target=/app/data,volume-driver=local'' ''--mount=type=volume,source=dockhand-data,target=/app/data,volume-driver=local''
''--group-add=${config.ids.gids.docker}'' ''--group-add=${toString config.ids.gids.docker}''
]; ];
}; };
}; };

208
modules/netbird-docker.nix Normal file
View file

@ -0,0 +1,208 @@
{
pkgs,
utils,
config,
lib,
...
}:
let
cfg = config.services.netbird-docker;
netbirdCfg = config.services.netbird;
serverVersion = "0.69.0";
dashboardVersion = "2.37.1";
in {
options.services.netbird-docker = {
enable = lib.mkEnableOption "Netbird Server stack, comprising the dashboard, management API, signal service, relay and STUN server";
enableLocalAuth = lib.mkEnableOption "local authentication";
proxy = lib.mkOption {
description = "Configuration for proxy";
type = lib.types.submodule {
options = {
domain = lib.mkOption {
description = "Domain the proxy is reachable at. Custom domains will need to add a CNAME record of the wildcard subdomain to this domain.";
type = lib.types.str;
};
token-secret = lib.mkOption {
description = ''
Proxy token in env-file notation.
Name of the environment variable is `NB_PROXY_TOKEN`.
Create the proxy token after netbird is installed with the following command: docker exec -it netbird-server /go/bin/netbird-server --config /etc/netbird/config.yaml token create --name local
'';
type = lib.types.anything;
};
};
};
};
secrets = lib.mkOption {
description = ''
Secret for combined server in env-file notation.
Name of the relevant environment variables:
- NETBIRD_RELAY_AUTH_SECRET - Shared authentication secret for relay
- NETBIRD_DATASTORE_ENC_KEY - Encryption key for sensitive data
'';
type = lib.types.anything;
};
};
config = lib.mkIf cfg.enable {
virtualisation.oci-containers.containers = {
netbird-dashboard = {
image = "netbirdio/dashboard:v${dashboardVersion}";
autoStart = true;
networks = [
"webproxy"
];
environment = {
NETBIRD_MGMT_API_ENDPOINT = "https://${netbirdCfg.server.management.domain}";
NETBIRD_MGMT_GRPC_API_ENDPOINT = "https://${netbirdCfg.server.management.domain}";
AUTH_AUDIENCE="netbird-dashboard";
AUTH_CLIENT_ID="netbird-dashboard";
AUTH_CLIENT_SECRET="";
AUTH_AUTHORITY = "https://${netbirdCfg.server.domain}/oauth2";
USE_AUTH0="false";
AUTH_SUPPORTED_SCOPES="openid profile email groups";
AUTH_REDIRECT_URI="/nb-auth";
AUTH_SILENT_REDIRECT_URI="/nb-silent-auth";
NGINX_SSL_PORT="443";
LETSENCRYPT_DOMAIN="none";
};
labels = {
"traefik.enable" = "true";
"traefik.http.routers.netbird-dashboard.rule" = "Host(`${netbirdCfg.server.dashboard.domain}`)";
"traefik.http.routers.netbird-dashboard.entrypoints" = "websecure";
"traefik.http.routers.netbird-dashboard.tls" = "true";
"traefik.http.routers.netbird-dashboard.tls.certresolver" = "letsencrypt";
"traefik.http.routers.netbird-dashboard.service" = "dashboard";
"traefik.http.routers.netbird-dashboard.priority" = "1";
"traefik.http.services.dashboard.loadbalancer.server.port" = "80";
};
dependsOn = [
"netbird-server"
];
};
netbird-server = {
image = "netbirdio/netbird-server:${serverVersion}";
autoStart = true;
networks = [
"webproxy"
];
entrypoint = "/bin/sh";
cmd = [
"-c"
''sed -e "s|__AUTH_SECRET__|$NETBIRD_RELAY_AUTH_SECRET|" -e "s|__DATASTORE_ENC_KEY__|$NETBIRD_DATASTORE_ENC_KEY|" /etc/netbird/config.yaml.tmpl > /etc/netbird/config.yaml && /go/bin/netbird-server --config /etc/netbird/config.yaml''
];
ports = [
"3478:3478/udp"
];
volumes = let
server-config = (pkgs.formats.yaml {}).generate "netbird-server-config" {
server = {
listenAddress = ":80";
exposedAddress = "https://${netbirdCfg.server.domain}:443";
stunPorts = [ 3478 ];
metricsPort = 9090;
healthCheckAddress = ":9000";
logLevel = netbirdCfg.server.management.logLevel;
logFile = "console";
authSecret = "__AUTH_SECRET__";
dataDir = "/var/lib/netbird";
auth = {
issuer = "https://${netbirdCfg.server.dashboard.domain}/oauth2";
localAuthDisabled = !cfg.enableLocalAuth;
signKeyRefreshEnabled = true;
dashboardRedirectURIs = [
"https://${netbirdCfg.server.dashboard.domain}/nb-auth"
"https://${netbirdCfg.server.dashboard.domain}/nb-silent-auth"
];
cliRedirectURIs = [
"http://localhost:53000"
];
};
reverseProxy.trustedHTTPProxies = [
"172.18.0.2/32"
];
store = {
engine = "sqlite";
encryptionKey = "__DATASTORE_ENC_KEY__";
};
};
};
in [
"${server-config}:/etc/netbird/config.yaml.tmpl"
];
environmentFiles = [
cfg.secrets.path
];
extraOptions = [
''--mount=type=volume,source=netbird_data,target=/var/lib/netbird,volume-driver=local''
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.netbird-grpc.rule" = "Host(`${netbirdCfg.server.signal.domain}`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`))";
"traefik.http.routers.netbird-grpc.entrypoints" = "websecure";
"traefik.http.routers.netbird-grpc.tls" = "true";
"traefik.http.routers.netbird-grpc.tls.certresolver" = "letsencrypt";
"traefik.http.routers.netbird-grpc.service" = "netbird-server-h2c";
"traefik.http.routers.netbird-grpc.priority" = "100";
"traefik.http.routers.netbird-backend.rule" = "Host(`${netbirdCfg.server.domain}`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`))";
"traefik.http.routers.netbird-backend.entrypoints" = "websecure";
"traefik.http.routers.netbird-backend.tls" = "true";
"traefik.http.routers.netbird-backend.tls.certresolver" = "letsencrypt";
"traefik.http.routers.netbird-backend.service" = "netbird-server";
"traefik.http.routers.netbird-backend.priority" = "100";
"traefik.http.services.netbird-server.loadbalancer.server.port" = "80";
"traefik.http.services.netbird-server-h2c.loadbalancer.server.port" = "80";
"traefik.http.services.netbird-server-h2c.loadbalancer.server.scheme" = "h2c";
};
dependsOn = [
"traefik"
];
};
netbird-proxy = {
image = "netbirdio/reverse-proxy:${serverVersion}";
autoStart = true;
ports = [
"51820:51820/udp"
];
networks = [
"webproxy"
];
dependsOn = [
"netbird-server"
];
environment = {
NB_PROXY_MANAGEMENT_ADDRESS="http://netbird-server:80";
NB_PROXY_ALLOW_INSECURE="true";
NB_PROXY_DOMAIN = cfg.proxy.domain;
NB_PROXY_ADDRESS = ":8443";
NB_PROXY_CERTIFICATE_DIRECTORY = "/certs";
NB_PROXY_ACME_CERTIFICATES = "true";
NB_PROXY_ACME_CHALLENGE_TYPE = "tls-alpn-01";
NB_PROXY_FORWARDED_PROTO = "https";
NB_PROXY_PROXY_PROTOCOL = "true";
NB_PROXY_TRUSTED_PROXIES = "172.18.0.2";
};
environmentFiles = [
cfg.proxy.token-secret.path
];
extraOptions = [
''--mount=type=volume,source=netbird_proxy_certs,target=/certs,volume-driver=local''
];
labels = {
"traefik.enable" = "true";
"traefik.tcp.routers.proxy-passthrough.entrypoints" = "websecure";
"traefik.tcp.routers.proxy-passthrough.rule" = "HostSNI(`*`)";
"traefik.tcp.routers.proxy-passthrough.tls.passthrough" = "true";
"traefik.tcp.routers.proxy-passthrough.service" = "proxy-tls";
"traefik.tcp.routers.proxy-passthrough.priority" = "1";
"traefik.tcp.services.proxy-tls.loadbalancer.server.port" = "8443";
"traefik.tcp.services.proxy-tls.loadbalancer.serverstransport" = "pp-v2@file";
};
};
};
};
}

View file

@ -76,6 +76,7 @@ in {
]; ];
extraOptions = [ extraOptions = [
''--mount=type=volume,source=certs,target=/certs,volume-driver=local'' ''--mount=type=volume,source=certs,target=/certs,volume-driver=local''
"--ip=172.18.0.2"
"--add-host=host.docker.internal:host-gateway" "--add-host=host.docker.internal:host-gateway"
"--health-cmd=wget --spider --quiet http://localhost:8080/ping" "--health-cmd=wget --spider --quiet http://localhost:8080/ping"
"--health-interval=10s" "--health-interval=10s"
@ -130,7 +131,5 @@ in {
''; '';
}; };
networking.firewall.extraCommands = "iptables -t nat -I PREROUTING -s 172.18.0.0/16 -d 172.18.0.0/16 -j MASQUERADE";
}; };
} }