···187187188188- `services.monero` now includes the `environmentFile` option for adding secrets to the Monero daemon config.
189189190190+- `services.netbird.server` now uses dedicated packages split out due to relicensing of server components to AGPLv3 with version `0.53.0`,
191191+190192- The new option [networking.ipips](#opt-networking.ipips) has been added to create IP within IP kind of tunnels (including 4in6, ip6ip6 and ipip).
191193 With the existing [networking.sits](#opt-networking.sits) option (6in4), it is now possible to create all combinations of IPv4 and IPv6 encapsulation.
192194
···3131 options.services.netbird.server.signal = {
3232 enable = mkEnableOption "Netbird's Signal Service";
33333434- package = mkPackageOption pkgs "netbird" { };
3434+ package = mkPackageOption pkgs "netbird-signal" { };
35353636 enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird signal service";
3737
+72-28
nixos/tests/netbird.nix
···77 ];
8899 nodes = {
1010- clients =
1010+ node =
1111 { ... }:
1212 {
1313 services.netbird.enable = true;
···1515 };
1616 };
17171818- # TODO: confirm the whole solution is working end-to-end when netbird server is implemented
1919- testScript = ''
2020- start_all()
2121- def did_start(node, name, interval=0.5, timeout=10):
2222- node.wait_for_unit(f"{name}.service")
2323- node.wait_for_file(f"/var/run/{name}/sock")
2424- # `netbird status` returns a full "Disconnected" status during initialization
2525- # only after a while passes it starts returning "NeedsLogin" help message
2626-2727- start = time.time()
2828- output = node.succeed(f"{name} status")
2929- while "Disconnected" in output and (time.time() - start) < timeout:
3030- time.sleep(interval)
3131- output = node.succeed(f"{name} status")
3232- assert "NeedsLogin" in output
3333-3434- did_start(clients, "netbird")
3535- did_start(clients, "netbird-custom")
3636- '';
3737-3818 /*
3939- `netbird status` used to print `Daemon status: NeedsLogin`
4040- https://github.com/netbirdio/netbird/blob/23a14737974e3849fa86408d136cc46db8a885d0/client/cmd/status.go#L154-L164
4141- as the first line, but now it is just:
1919+ Historically waiting for the NetBird client daemon initialization helped catch number of bugs with the service,
2020+ so we keep try to keep it here in as much details as it makes sense.
42214343- Daemon version: 0.26.3
4444- CLI version: 0.26.3
4545- Management: Disconnected
2222+ Initially `netbird status` returns a "Disconnected" messages:
2323+ OS: linux/amd64
2424+ Daemon version: 0.54.0
2525+ CLI version: 0.54.0
2626+ Profile: default
2727+ Management: Disconnected, reason: rpc error: code = FailedPrecondition desc = failed connecting to Management Service : context deadline exceeded
4628 Signal: Disconnected
4729 Relays: 0/0 Available
4830 Nameservers: 0/0 Available
···5032 NetBird IP: N/A
5133 Interface type: N/A
5234 Quantum resistance: false
5353- Routes: -
3535+ Lazy connection: false
3636+ Networks: -
3737+ Forwarding rules: 0
5438 Peers count: 0/0 Connected
3939+4040+ After a while passes it should start returning "NeedsLogin" help message.
4141+4242+ As of ~0.53.0+ in ~30 second intervals the `netbird status` instead of "NeedsLogin" it briefly (for under 2 seconds) crashes with:
4343+4444+ Error: status failed: failed connecting to Management Service : context deadline exceeded
4545+4646+ This might be related to the following log line:
4747+4848+ 2025-08-11T15:03:25Z ERRO shared/management/client/grpc.go:65: failed creating connection to Management Service: context deadline exceeded
5549 */
5050+ # TODO: confirm the whole solution is working end-to-end when netbird server is implemented
5151+ testScript = ''
5252+ import textwrap
5353+ import time
5454+5555+ start_all()
5656+5757+ def run_with_debug(node, cmd, check=True, display=True, **kwargs):
5858+ cmd = f"{cmd} 2>&1"
5959+ start = time.time()
6060+ ret, output = node.execute(cmd, **kwargs)
6161+ duration = time.time() - start
6262+ txt = f">>> {cmd=} {ret=} {duration=:.2f}:\n{textwrap.indent(output, '... ')}"
6363+ if check:
6464+ assert ret == 0, txt
6565+ if display:
6666+ print(txt)
6767+ return ret, output
6868+6969+ def wait_until_rcode(node, cmd, rcode=0, retries=30, **kwargs):
7070+ def check_success(_last_try):
7171+ nonlocal output
7272+ ret, output = run_with_debug(node, cmd, **kwargs)
7373+ return ret == rcode
7474+7575+ kwargs.setdefault('check', False)
7676+ output = None
7777+ with node.nested(f"waiting for {cmd=} to exit with {rcode=}"):
7878+ retry(check_success, retries)
7979+ return output
8080+8181+ instances = ["netbird", "netbird-custom"]
8282+8383+ for name in instances:
8484+ node.wait_for_unit(f"{name}.service")
8585+ node.wait_for_file(f"/var/run/{name}/sock")
8686+8787+ for name in instances:
8888+ wait_until_rcode(node, f"{name} status |& grep -C20 Disconnected", 0, retries=5)
8989+ ''
9090+ # The status used to turn into `NeedsLogin`, but recently started crashing instead.
9191+ # leaving the snippets in here, in case some update goes back to the old behavior and can be tested again
9292+ + lib.optionalString false ''
9393+ for name in instances:
9494+ #wait_until_rcode(node, f"{name} status |& grep -C20 NeedsLogin", 0, retries=20)
9595+ output = wait_until_rcode(node, f"{name} status", 1, retries=61)
9696+ msg = "Error: status failed: failed connecting to Management Service : context deadline exceeded"
9797+ assert output.strip() == msg, f"expected {msg=}, got {output=} instead"
9898+ wait_until_rcode(node, f"{name} status |& grep -C20 Disconnected", 0, retries=10)
9999+ '';
56100}