···187188- `services.monero` now includes the `environmentFile` option for adding secrets to the Monero daemon config.
18900190- The new option [networking.ipips](#opt-networking.ipips) has been added to create IP within IP kind of tunnels (including 4in6, ip6ip6 and ipip).
191 With the existing [networking.sits](#opt-networking.sits) option (6in4), it is now possible to create all combinations of IPv4 and IPv6 encapsulation.
192
···187188- `services.monero` now includes the `environmentFile` option for adding secrets to the Monero daemon config.
189190+- `services.netbird.server` now uses dedicated packages split out due to relicensing of server components to AGPLv3 with version `0.53.0`,
191+192- The new option [networking.ipips](#opt-networking.ipips) has been added to create IP within IP kind of tunnels (including 4in6, ip6ip6 and ipip).
193 With the existing [networking.sits](#opt-networking.sits) option (6in4), it is now possible to create all combinations of IPv4 and IPv6 encapsulation.
194
···31 options.services.netbird.server.signal = {
32 enable = mkEnableOption "Netbird's Signal Service";
3334- package = mkPackageOption pkgs "netbird" { };
3536 enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird signal service";
37
···31 options.services.netbird.server.signal = {
32 enable = mkEnableOption "Netbird's Signal Service";
3334+ package = mkPackageOption pkgs "netbird-signal" { };
3536 enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird signal service";
37
+72-28
nixos/tests/netbird.nix
···7 ];
89 nodes = {
10- clients =
11 { ... }:
12 {
13 services.netbird.enable = true;
···15 };
16 };
1718- # TODO: confirm the whole solution is working end-to-end when netbird server is implemented
19- testScript = ''
20- start_all()
21- def did_start(node, name, interval=0.5, timeout=10):
22- node.wait_for_unit(f"{name}.service")
23- node.wait_for_file(f"/var/run/{name}/sock")
24- # `netbird status` returns a full "Disconnected" status during initialization
25- # only after a while passes it starts returning "NeedsLogin" help message
26-27- start = time.time()
28- output = node.succeed(f"{name} status")
29- while "Disconnected" in output and (time.time() - start) < timeout:
30- time.sleep(interval)
31- output = node.succeed(f"{name} status")
32- assert "NeedsLogin" in output
33-34- did_start(clients, "netbird")
35- did_start(clients, "netbird-custom")
36- '';
37-38 /*
39- `netbird status` used to print `Daemon status: NeedsLogin`
40- https://github.com/netbirdio/netbird/blob/23a14737974e3849fa86408d136cc46db8a885d0/client/cmd/status.go#L154-L164
41- as the first line, but now it is just:
4243- Daemon version: 0.26.3
44- CLI version: 0.26.3
45- Management: Disconnected
00046 Signal: Disconnected
47 Relays: 0/0 Available
48 Nameservers: 0/0 Available
···50 NetBird IP: N/A
51 Interface type: N/A
52 Quantum resistance: false
53- Routes: -
0054 Peers count: 0/0 Connected
000000000055 */
0000000000000000000000000000000000000000000000000056}
···7 ];
89 nodes = {
10+ node =
11 { ... }:
12 {
13 services.netbird.enable = true;
···15 };
16 };
170000000000000000000018 /*
19+ Historically waiting for the NetBird client daemon initialization helped catch number of bugs with the service,
20+ so we keep try to keep it here in as much details as it makes sense.
02122+ Initially `netbird status` returns a "Disconnected" messages:
23+ OS: linux/amd64
24+ Daemon version: 0.54.0
25+ CLI version: 0.54.0
26+ Profile: default
27+ Management: Disconnected, reason: rpc error: code = FailedPrecondition desc = failed connecting to Management Service : context deadline exceeded
28 Signal: Disconnected
29 Relays: 0/0 Available
30 Nameservers: 0/0 Available
···32 NetBird IP: N/A
33 Interface type: N/A
34 Quantum resistance: false
35+ Lazy connection: false
36+ Networks: -
37+ Forwarding rules: 0
38 Peers count: 0/0 Connected
39+40+ After a while passes it should start returning "NeedsLogin" help message.
41+42+ As of ~0.53.0+ in ~30 second intervals the `netbird status` instead of "NeedsLogin" it briefly (for under 2 seconds) crashes with:
43+44+ Error: status failed: failed connecting to Management Service : context deadline exceeded
45+46+ This might be related to the following log line:
47+48+ 2025-08-11T15:03:25Z ERRO shared/management/client/grpc.go:65: failed creating connection to Management Service: context deadline exceeded
49 */
50+ # TODO: confirm the whole solution is working end-to-end when netbird server is implemented
51+ testScript = ''
52+ import textwrap
53+ import time
54+55+ start_all()
56+57+ def run_with_debug(node, cmd, check=True, display=True, **kwargs):
58+ cmd = f"{cmd} 2>&1"
59+ start = time.time()
60+ ret, output = node.execute(cmd, **kwargs)
61+ duration = time.time() - start
62+ txt = f">>> {cmd=} {ret=} {duration=:.2f}:\n{textwrap.indent(output, '... ')}"
63+ if check:
64+ assert ret == 0, txt
65+ if display:
66+ print(txt)
67+ return ret, output
68+69+ def wait_until_rcode(node, cmd, rcode=0, retries=30, **kwargs):
70+ def check_success(_last_try):
71+ nonlocal output
72+ ret, output = run_with_debug(node, cmd, **kwargs)
73+ return ret == rcode
74+75+ kwargs.setdefault('check', False)
76+ output = None
77+ with node.nested(f"waiting for {cmd=} to exit with {rcode=}"):
78+ retry(check_success, retries)
79+ return output
80+81+ instances = ["netbird", "netbird-custom"]
82+83+ for name in instances:
84+ node.wait_for_unit(f"{name}.service")
85+ node.wait_for_file(f"/var/run/{name}/sock")
86+87+ for name in instances:
88+ wait_until_rcode(node, f"{name} status |& grep -C20 Disconnected", 0, retries=5)
89+ ''
90+ # The status used to turn into `NeedsLogin`, but recently started crashing instead.
91+ # leaving the snippets in here, in case some update goes back to the old behavior and can be tested again
92+ + lib.optionalString false ''
93+ for name in instances:
94+ #wait_until_rcode(node, f"{name} status |& grep -C20 NeedsLogin", 0, retries=20)
95+ output = wait_until_rcode(node, f"{name} status", 1, retries=61)
96+ msg = "Error: status failed: failed connecting to Management Service : context deadline exceeded"
97+ assert output.strip() == msg, f"expected {msg=}, got {output=} instead"
98+ wait_until_rcode(node, f"{name} status |& grep -C20 Disconnected", 0, retries=10)
99+ '';
100}