fix(topology): remove DMZ gateway auto-attach on LAN create

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.
This commit is contained in:
2026-04-22 17:14:09 -04:00
parent 8632cee40a
commit 6f537f52c2
2 changed files with 2 additions and 53 deletions

View File

@@ -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"],

View File

@@ -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),