From 6f537f52c2ad1d9901bf9f45d7f2a1e6a8887d37 Mon Sep 17 00:00:00 2001 From: anti Date: Wed, 22 Apr 2026 17:14:09 -0400 Subject: [PATCH] fix(topology): remove DMZ gateway auto-attach on LAN create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /topologies/{id}/lans previously called _auto_attach_gateway() whenever a non-DMZ LAN was created, which wired the DMZ gateway decky to every new subnet. That's why a deployed gateway ended up with eth0..ethN on every LAN regardless of what the user drew in MazeNET. Drop the auto-attach helper entirely. The DMZ_ORPHAN deploy-time validator (decnet/topology/validate.py:65-110) stays strict — users must explicitly wire the gateway to each subnet they want bridged, which is the whole point of having a topology editor. useMazeApi.ts: drop stale auto-bridge reference from comment. --- decnet/web/router/topology/api_lan_crud.py | 50 ------------------- .../src/components/MazeNET/useMazeApi.ts | 5 +- 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/decnet/web/router/topology/api_lan_crud.py b/decnet/web/router/topology/api_lan_crud.py index 147fc01e..ae15d394 100644 --- a/decnet/web/router/topology/api_lan_crud.py +++ b/decnet/web/router/topology/api_lan_crud.py @@ -77,59 +77,9 @@ async def api_create_lan( if row is None: # pragma: no cover — would mean insert vanished raise HTTPException(status_code=500, detail="LAN insert vanished") - # Auto-bridge: if this is a non-DMZ LAN, attach the topology's - # DMZ gateway (the decky with forwards_l3=True that lives on the - # DMZ LAN) to it. Satisfies the DMZ_ORPHAN invariant by - # construction — every internal LAN always has a bridge path. - if not body.is_dmz: - try: - await _auto_attach_gateway(topology_id, lan_id) - except Exception as exc: - # Best-effort: if the gateway is missing or the edge can't - # be written, the deploy-time validator will surface it. - log.warning( - "auto-bridge skipped for LAN %s in topology %s: %s", - lan_id, topology_id, exc, - ) - return LANRow(**row) -async def _auto_attach_gateway(topology_id: str, new_lan_id: str) -> None: - """Attach the topology's DMZ gateway to a newly created non-DMZ LAN.""" - lans = await repo.list_lans_for_topology(topology_id) - dmz = next((lan for lan in lans if lan.get("is_dmz")), None) - if dmz is None: - return - - edges = await repo.list_topology_edges(topology_id) - gateway_uuid: str | None = None - for e in edges: - if e["lan_id"] != dmz["id"]: - continue - if e.get("forwards_l3"): - gateway_uuid = e["decky_uuid"] - break - if gateway_uuid is None: - return - - if any( - e["decky_uuid"] == gateway_uuid and e["lan_id"] == new_lan_id - for e in edges - ): - return - - await repo.add_topology_edge( - { - "topology_id": topology_id, - "decky_uuid": gateway_uuid, - "lan_id": new_lan_id, - "is_bridge": True, - "forwards_l3": True, - } - ) - - @router.patch( "/{topology_id}/lans/{lan_id}", tags=["MazeNET Topologies"], diff --git a/decnet_web/src/components/MazeNET/useMazeApi.ts b/decnet_web/src/components/MazeNET/useMazeApi.ts index 96e11730..96008e0c 100644 --- a/decnet_web/src/components/MazeNET/useMazeApi.ts +++ b/decnet_web/src/components/MazeNET/useMazeApi.ts @@ -89,9 +89,8 @@ export function adaptTopology(detail: TopologyDetail): HydratedTopology { // Home LAN = first edge; a multi-homed gateway is drawn inside its // home LAN, membership in others is expressed via the edge list. - // Gateways (forwards_l3) MUST render inside a DMZ — auto-bridge adds - // subnet edges after the original DMZ edge, but edge ordering from the - // backend is not guaranteed, so we pick DMZ explicitly for gateways. + // Gateways (forwards_l3) MUST render inside a DMZ — edge ordering from + // the backend is not guaranteed, so we pick the DMZ edge explicitly. const dmzIds = new Set(detail.lans.filter((l) => l.is_dmz).map((l) => l.id)); const gatewayUuids = new Set( detail.edges.filter((e) => e.forwards_l3).map((e) => e.decky_uuid),