diff --git a/CHANGELOG.md b/CHANGELOG.md index 03c3f6c..b67a196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.34.58] - 2026-10-20 + +### Fixed +- **An unattended run reported NetBird not joined on a machine already connected to it.** With 0.34.57 a new installation joins NetBird by default; run with `--yes` or without a terminal on a host already in NDP's network (another Endpoint on it had joined), the installer said it needed `sudo` and could not ask for the password, and wrote `NETBIRD_ENABLED=False`. It checked for passwordless `sudo` before looking at the existing connection, which needs no `sudo`. It now reads the connection first (`netbird status` as the user, through `sudo` only if that fails) and only requires `sudo` to install or join. New test in `install/tests/test_netbird_affinities.py`. Verified on a host already connected to NetBird: re-running the installer unattended on a second Endpoint recorded the connection (`NETBIRD_ENABLED=True`, the NetBird address, reported in its metrics), with the rest of its `.env` unchanged (#367). + ## [0.34.57] - 2026-10-20 ### Changed diff --git a/api/config/swagger_settings.py b/api/config/swagger_settings.py index 2c94c0d..6e59a6c 100644 --- a/api/config/swagger_settings.py +++ b/api/config/swagger_settings.py @@ -12,7 +12,7 @@ class Settings(BaseSettings): swagger_title: str = "API Documentation" swagger_description: str = "This is the API documentation." - swagger_version: str = "0.34.57" + swagger_version: str = "0.34.58" root_path: str = "" # API root path prefix (e.g., "/test" or "") is_public: bool = True metrics_endpoint: str = "https://federation.ndp.utah.edu/metrics/" diff --git a/install/install.sh b/install/install.sh index 926584b..5224f29 100755 --- a/install/install.sh +++ b/install/install.sh @@ -1299,8 +1299,11 @@ if [[ "$want_affinities" == "yes" ]]; then fi netbird_status() { - # The client's own view: management connection and address, as JSON. - "${netbird_sudo[@]}" netbird status --json 2>/dev/null || true + # The client's own view: management connection and address, as JSON. Read + # as the user first, which needs no sudo; through sudo only if that fails. + netbird status --json 2>/dev/null \ + || { [[ ${#netbird_sudo[@]} -gt 0 ]] && "${netbird_sudo[@]}" netbird status --json 2>/dev/null; } \ + || true } netbird_field() { @@ -1345,10 +1348,18 @@ join_netbird() { (Federation 1.12.9 or later, with --config-id) or --netbird-management-url." || return 1 fi netbird_sudo=() + # A machine already connected (another Endpoint on this host joined it) needs + # no sudo at all: seeing the connection does not (issue #367). + already_connected="no" + if command -v netbird >/dev/null 2>&1 \ + && [[ "$(netbird_status | netbird_field connected)" == "yes" ]]; then + already_connected="yes" + fi if [[ "$(id -u)" != "0" ]]; then netbird_sudo=(sudo) # Asked for its password only where someone can answer. - if [[ "$want_netbird" == "default" ]] && ! interactive && ! sudo -n true 2>/dev/null; then + if [[ "$want_netbird" == "default" && "$already_connected" == "no" ]] \ + && ! interactive && ! sudo -n true 2>/dev/null; then netbird_stop "it needs sudo, and sudo would ask for a password nobody can type here." || return 1 fi fi diff --git a/install/tests/test_netbird_affinities.py b/install/tests/test_netbird_affinities.py index bae0c17..e0a7ae4 100644 --- a/install/tests/test_netbird_affinities.py +++ b/install/tests/test_netbird_affinities.py @@ -460,3 +460,21 @@ def test_by_default_without_passwordless_sudo_it_carries_on(host, federation): assert "needs sudo" in result.stderr assert "netbird up" not in _netbird_calls(host) assert _env(host)["NETBIRD_ENABLED"] == "False" + + +def test_an_unattended_run_on_a_connected_machine_records_netbird(host, federation): + """Issue #367: seeing an existing connection needs no sudo.""" + federation["config"] = _configuration(netbird_management_url=MANAGEMENT_URL) + # Already connected, as the second Endpoint on a host that joined before. + (host["tmp"] / "netbird.joined").touch() + # sudo that would need a password: -n refuses, anything else fails too. + (host["stubs"] / "sudo").write_text("#!/bin/sh\nexit 1\n", encoding="utf-8") + + result = _install(host, federation) + + assert result.returncode == 0, result.stdout + result.stderr + assert "needs sudo" not in result.stderr + assert "netbird up" not in _netbird_calls(host) + assert _env(host)["NETBIRD_ENABLED"] == "True" + assert _env(host)["NETBIRD_IP"] == "100.64.0.7" + assert _state(host)["NETBIRD"] == "yes"