dev_gateway #22

Merged
robert merged 14 commits from dev_gateway into main 2026-10-07 16:34:47 +02:00
2 changed files with 72 additions and 6 deletions
Showing only changes of commit d9a10f76ea - Show all commits

View file

@ -214,7 +214,21 @@ def run_connect(gateway_url: str, registration_token: str, box_name: str) -> Non
systemd-run from `POST /api/gateway/connect`), writing state systemd-run from `POST /api/gateway/connect`), writing state
transitions as it goes so the UI can poll instead of the request transitions as it goes so the UI can poll instead of the request
thread blocking on a slow/unreachable gateway. thread blocking on a slow/unreachable gateway.
A registration saved by an earlier attempt whose tunnel failed to come
up is reused rather than registering again — re-registering would leave
the first box (and its wg-easy peer) orphaned on the gateway.
""" """
existing = load_config()
if existing is not None:
if existing["gateway_url"] != gateway_url.rstrip("/"):
write_state(
"error",
error=f"already registered with {existing['gateway_url']} — disconnect first",
)
return
config = existing
else:
write_state("registering") write_state("registering")
try: try:
config = register(gateway_url, registration_token, box_name) config = register(gateway_url, registration_token, box_name)

View file

@ -280,6 +280,58 @@ def test_run_connect_tunnel_failure_writes_error_state(gw, monkeypatch):
assert gw.load_config()["box_id"] == "abc" assert gw.load_config()["box_id"] == "abc"
def test_run_connect_retry_after_tunnel_failure_reuses_registration(gw, monkeypatch):
register_calls = []
def urlopen(req, timeout):
register_calls.append(req.full_url)
return _FakeResp({"box_id": "abc", "box_token": "abc.x", "wg": WG})
monkeypatch.setattr("urllib.request.urlopen", urlopen)
def failing_tunnel(enabled, wg=None):
raise gw.wireguard.WireguardError("systemctl failed: unit missing")
monkeypatch.setattr(gw.wireguard, "set_tunnel_enabled", failing_tunnel)
gw.run_connect("https://gw.example.com", "tok", "my-box")
assert gw.read_state()["stage"] == "error"
tunnel_calls = []
monkeypatch.setattr(
gw.wireguard,
"set_tunnel_enabled",
lambda enabled, wg=None: tunnel_calls.append((enabled, wg)),
)
gw.run_connect("https://gw.example.com/", "tok", "my-box")
# Registered exactly once; the retry only brought the tunnel up.
assert register_calls == ["https://gw.example.com/v1/boxes/register"]
assert tunnel_calls == [(True, WG)]
assert gw.read_state()["stage"] == "done"
assert gw.load_config()["box_id"] == "abc"
def test_run_connect_refuses_a_different_gateway_while_registered(gw, monkeypatch):
monkeypatch.setattr(
"urllib.request.urlopen",
lambda req, timeout: _FakeResp({"box_id": "abc", "box_token": "abc.x", "wg": WG}),
)
gw.register("https://gw.example.com", "tok", "box")
def must_not_call(req, timeout):
raise AssertionError("must not register with a second gateway")
monkeypatch.setattr("urllib.request.urlopen", must_not_call)
monkeypatch.setattr(gw.wireguard, "set_tunnel_enabled", lambda enabled, wg=None: None)
gw.run_connect("https://other.example.com", "tok", "box")
state = gw.read_state()
assert state["stage"] == "error"
assert "disconnect first" in state["error"]
assert gw.load_config()["gateway_url"] == "https://gw.example.com"
def test_deregister_failure_keeps_local_config_for_retry(gw, monkeypatch): def test_deregister_failure_keeps_local_config_for_retry(gw, monkeypatch):
monkeypatch.setattr( monkeypatch.setattr(
"urllib.request.urlopen", "urllib.request.urlopen",