netbird: 0.49.0 -> 0.54.0 + split up + relicense

- 0.53.0+ relicensed the server components
- as a result split into per-component packages
- adds missing 2 upload & relay components
- the tested behavior has changed

Changed files
+171 -70
nixos
doc
manual
release-notes
modules
services
networking
tests
pkgs
by-name
ne
netbird
netbird-management
netbird-relay
netbird-signal
netbird-ui
netbird-upload
+2
nixos/doc/manual/release-notes/rl-2511.section.md
···
- `services.monero` now includes the `environmentFile` option for adding secrets to the Monero daemon config.
+
- `services.netbird.server` now uses dedicated packages split out due to relicensing of server components to AGPLv3 with version `0.53.0`,
+
- The new option [networking.ipips](#opt-networking.ipips) has been added to create IP within IP kind of tunnels (including 4in6, ip6ip6 and ipip).
With the existing [networking.sits](#opt-networking.sits) option (6in4), it is now possible to create all combinations of IPv4 and IPv6 encapsulation.
+1 -1
nixos/modules/services/networking/netbird/management.nix
···
options.services.netbird.server.management = {
enable = mkEnableOption "Netbird Management Service";
-
package = mkPackageOption pkgs "netbird" { };
+
package = mkPackageOption pkgs "netbird-management" { };
domain = mkOption {
type = str;
+1 -1
nixos/modules/services/networking/netbird/signal.nix
···
options.services.netbird.server.signal = {
enable = mkEnableOption "Netbird's Signal Service";
-
package = mkPackageOption pkgs "netbird" { };
+
package = mkPackageOption pkgs "netbird-signal" { };
enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird signal service";
+72 -28
nixos/tests/netbird.nix
···
];
nodes = {
-
clients =
+
node =
{ ... }:
{
services.netbird.enable = true;
···
};
};
-
# TODO: confirm the whole solution is working end-to-end when netbird server is implemented
-
testScript = ''
-
start_all()
-
def did_start(node, name, interval=0.5, timeout=10):
-
node.wait_for_unit(f"{name}.service")
-
node.wait_for_file(f"/var/run/{name}/sock")
-
# `netbird status` returns a full "Disconnected" status during initialization
-
# only after a while passes it starts returning "NeedsLogin" help message
-
-
start = time.time()
-
output = node.succeed(f"{name} status")
-
while "Disconnected" in output and (time.time() - start) < timeout:
-
time.sleep(interval)
-
output = node.succeed(f"{name} status")
-
assert "NeedsLogin" in output
-
-
did_start(clients, "netbird")
-
did_start(clients, "netbird-custom")
-
'';
-
/*
-
`netbird status` used to print `Daemon status: NeedsLogin`
-
https://github.com/netbirdio/netbird/blob/23a14737974e3849fa86408d136cc46db8a885d0/client/cmd/status.go#L154-L164
-
as the first line, but now it is just:
+
Historically waiting for the NetBird client daemon initialization helped catch number of bugs with the service,
+
so we keep try to keep it here in as much details as it makes sense.
-
Daemon version: 0.26.3
-
CLI version: 0.26.3
-
Management: Disconnected
+
Initially `netbird status` returns a "Disconnected" messages:
+
OS: linux/amd64
+
Daemon version: 0.54.0
+
CLI version: 0.54.0
+
Profile: default
+
Management: Disconnected, reason: rpc error: code = FailedPrecondition desc = failed connecting to Management Service : context deadline exceeded
Signal: Disconnected
Relays: 0/0 Available
Nameservers: 0/0 Available
···
NetBird IP: N/A
Interface type: N/A
Quantum resistance: false
-
Routes: -
+
Lazy connection: false
+
Networks: -
+
Forwarding rules: 0
Peers count: 0/0 Connected
+
+
After a while passes it should start returning "NeedsLogin" help message.
+
+
As of ~0.53.0+ in ~30 second intervals the `netbird status` instead of "NeedsLogin" it briefly (for under 2 seconds) crashes with:
+
+
Error: status failed: failed connecting to Management Service : context deadline exceeded
+
+
This might be related to the following log line:
+
+
2025-08-11T15:03:25Z ERRO shared/management/client/grpc.go:65: failed creating connection to Management Service: context deadline exceeded
*/
+
# TODO: confirm the whole solution is working end-to-end when netbird server is implemented
+
testScript = ''
+
import textwrap
+
import time
+
+
start_all()
+
+
def run_with_debug(node, cmd, check=True, display=True, **kwargs):
+
cmd = f"{cmd} 2>&1"
+
start = time.time()
+
ret, output = node.execute(cmd, **kwargs)
+
duration = time.time() - start
+
txt = f">>> {cmd=} {ret=} {duration=:.2f}:\n{textwrap.indent(output, '... ')}"
+
if check:
+
assert ret == 0, txt
+
if display:
+
print(txt)
+
return ret, output
+
+
def wait_until_rcode(node, cmd, rcode=0, retries=30, **kwargs):
+
def check_success(_last_try):
+
nonlocal output
+
ret, output = run_with_debug(node, cmd, **kwargs)
+
return ret == rcode
+
+
kwargs.setdefault('check', False)
+
output = None
+
with node.nested(f"waiting for {cmd=} to exit with {rcode=}"):
+
retry(check_success, retries)
+
return output
+
+
instances = ["netbird", "netbird-custom"]
+
+
for name in instances:
+
node.wait_for_unit(f"{name}.service")
+
node.wait_for_file(f"/var/run/{name}/sock")
+
+
for name in instances:
+
wait_until_rcode(node, f"{name} status |& grep -C20 Disconnected", 0, retries=5)
+
''
+
# The status used to turn into `NeedsLogin`, but recently started crashing instead.
+
# leaving the snippets in here, in case some update goes back to the old behavior and can be tested again
+
+ lib.optionalString false ''
+
for name in instances:
+
#wait_until_rcode(node, f"{name} status |& grep -C20 NeedsLogin", 0, retries=20)
+
output = wait_until_rcode(node, f"{name} status", 1, retries=61)
+
msg = "Error: status failed: failed connecting to Management Service : context deadline exceeded"
+
assert output.strip() == msg, f"expected {msg=}, got {output=} instead"
+
wait_until_rcode(node, f"{name} status |& grep -C20 Disconnected", 0, retries=10)
+
'';
}
+5
pkgs/by-name/ne/netbird-management/package.nix
···
+
{ netbird }:
+
+
netbird.override {
+
componentName = "management";
+
}
+5
pkgs/by-name/ne/netbird-relay/package.nix
···
+
{ netbird }:
+
+
netbird.override {
+
componentName = "relay";
+
}
+5
pkgs/by-name/ne/netbird-signal/package.nix
···
+
{ netbird }:
+
+
netbird.override {
+
componentName = "signal";
+
}
+1 -1
pkgs/by-name/ne/netbird-ui/package.nix
···
{ netbird }:
netbird.override {
-
ui = true;
+
componentName = "ui";
}
+5
pkgs/by-name/ne/netbird-upload/package.nix
···
+
{ netbird }:
+
+
netbird.override {
+
componentName = "upload";
+
}
+74 -39
pkgs/by-name/ne/netbird/package.nix
···
libX11,
libXcursor,
libXxf86vm,
-
ui ? false,
netbird-ui,
versionCheckHook,
+
componentName ? "client",
}:
let
-
modules =
-
if ui then
-
{
-
"client/ui" = "netbird-ui";
-
}
-
else
-
{
-
client = "netbird";
-
management = "netbird-mgmt";
-
signal = "netbird-signal";
-
};
+
/*
+
License tagging is based off:
+
- https://github.com/netbirdio/netbird/blob/9e95841252c62b50ae93805c8dfd2b749ac95ea7/LICENSES/REUSE.toml
+
- https://github.com/netbirdio/netbird/blob/9e95841252c62b50ae93805c8dfd2b749ac95ea7/LICENSE#L1-L2
+
*/
+
availableComponents = {
+
client = {
+
module = "client";
+
binaryName = "netbird";
+
license = lib.licenses.bsd3;
+
versionCheckProgramArg = "version";
+
hasCompletion = true;
+
};
+
ui = {
+
module = "client/ui";
+
binaryName = "netbird-ui";
+
license = lib.licenses.bsd3;
+
};
+
upload = {
+
module = "upload-server";
+
binaryName = "netbird-upload";
+
license = lib.licenses.bsd3;
+
};
+
management = {
+
module = "management";
+
binaryName = "netbird-mgmt";
+
license = lib.licenses.agpl3Only;
+
versionCheckProgramArg = "--version";
+
hasCompletion = true;
+
};
+
signal = {
+
module = "signal";
+
binaryName = "netbird-signal";
+
license = lib.licenses.agpl3Only;
+
hasCompletion = true;
+
};
+
relay = {
+
module = "relay";
+
binaryName = "netbird-relay";
+
license = lib.licenses.agpl3Only;
+
};
+
};
+
isUI = componentName == "ui";
+
component = availableComponents.${componentName};
in
buildGoModule (finalAttrs: {
-
pname = "netbird";
-
version = "0.49.0";
+
pname = "netbird-${componentName}";
+
version = "0.54.0";
src = fetchFromGitHub {
owner = "netbirdio";
repo = "netbird";
tag = "v${finalAttrs.version}";
-
hash = "sha256-Hv0A9/NTMzRAf9YvYGvRLyy2gdigF9y2NfylE8bLcTw=";
+
hash = "sha256-qKYJa7q7scEbbxLHaosaurrjXR5ABxCAnuUcy80yKEc=";
};
-
vendorHash = "sha256-t/X/muMwHVwg8Or+pFTSEQEsnkKLuApoVUmMhyCImWI=";
+
vendorHash = "sha256-uVVm+iDGP2eZ5GVXWJrWZQ7LpHdZccRIiHPIFs6oAPo=";
-
nativeBuildInputs = [ installShellFiles ] ++ lib.optional ui pkg-config;
+
nativeBuildInputs = [ installShellFiles ] ++ lib.optional isUI pkg-config;
-
buildInputs = lib.optionals (stdenv.hostPlatform.isLinux && ui) [
+
buildInputs = lib.optionals (stdenv.hostPlatform.isLinux && isUI) [
gtk3
libayatana-appindicator
libX11
···
libXxf86vm
];
-
subPackages = lib.attrNames modules;
+
subPackages = [ component.module ];
ldflags = [
"-s"
···
'';
postInstall =
-
lib.concatStringsSep "\n" (
-
lib.mapAttrsToList (
-
module: binary:
-
''
-
mv $out/bin/${lib.last (lib.splitString "/" module)} $out/bin/${binary}
+
let
+
builtBinaryName = lib.last (lib.splitString "/" component.module);
+
in
+
''
+
mv $out/bin/${builtBinaryName} $out/bin/${component.binaryName}
+
''
+
+
+
lib.optionalString
+
(stdenv.buildPlatform.canExecute stdenv.hostPlatform && (component.hasCompletion or false))
''
-
+ lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform && !ui) ''
-
installShellCompletion --cmd ${binary} \
-
--bash <($out/bin/${binary} completion bash) \
-
--fish <($out/bin/${binary} completion fish) \
-
--zsh <($out/bin/${binary} completion zsh)
+
installShellCompletion --cmd ${component.binaryName} \
+
--bash <($out/bin/${component.binaryName} completion bash) \
+
--fish <($out/bin/${component.binaryName} completion fish) \
+
--zsh <($out/bin/${component.binaryName} completion zsh)
''
-
) modules
-
)
-
+ lib.optionalString (stdenv.hostPlatform.isLinux && ui) ''
+
# assemble & adjust netbird.desktop files for the GUI
+
+ lib.optionalString (stdenv.hostPlatform.isLinux && isUI) ''
install -Dm644 "$src/client/ui/assets/netbird-systemtray-connected.png" "$out/share/pixmaps/netbird.png"
install -Dm644 "$src/client/ui/build/netbird.desktop" "$out/share/applications/netbird.desktop"
substituteInPlace $out/share/applications/netbird.desktop \
-
--replace-fail "Exec=/usr/bin/netbird-ui" "Exec=$out/bin/netbird-ui"
+
--replace-fail "Exec=/usr/bin/netbird-ui" "Exec=$out/bin/${component.binaryName}"
'';
nativeInstallCheckInputs = [
versionCheckHook
];
-
versionCheckProgram = "${placeholder "out"}/bin/${finalAttrs.meta.mainProgram}";
-
versionCheckProgramArg = "version";
-
# Disabled for the `netbird-ui` version because it does a network request.
-
doInstallCheck = !ui;
+
versionCheckProgram = "${placeholder "out"}/bin/${component.binaryName}";
+
versionCheckProgramArg = component.versionCheckProgramArg or "version";
+
doInstallCheck = component ? versionCheckProgramArg;
passthru = {
tests = {
···
homepage = "https://netbird.io";
changelog = "https://github.com/netbirdio/netbird/releases/tag/v${finalAttrs.version}";
description = "Connect your devices into a single secure private WireGuard®-based mesh network with SSO/MFA and simple access controls";
-
license = lib.licenses.bsd3;
+
license = component.license;
maintainers = with lib.maintainers; [
+
nazarewk
saturn745
loc
];
-
mainProgram = if ui then "netbird-ui" else "netbird";
+
mainProgram = component.binaryName;
};
})