fix(satellite): Selbstdiagnose fuers Netz — warnt bei Docker-/NAT-Netz

Symptom: Satellit fand nur Docker-Container (192.168.65.x / 172.18.x) statt der
echten LAN-Geraete. Ursache ist kein Bug, sondern das Deployment-Netz: auf Docker
Desktop (Mac/Windows) ist network_mode:host das Docker-VM-NAT, nicht das echte LAN
— mDNS/SSDP erreichen die realen Geraete nicht.

- satellite.py: _net_context() ermittelt primary_ip + alle IPs und WARNT, wenn der
  Satellit in einem Docker-/NAT-Netz laeuft (192.168.65.x oder 172.16-31.x). Netz-
  Info wird in sat_hello + sat_devices mitgeschickt und beim Start geloggt.
- Diagnostic: zeigt Netz (primary_ip) pro Satellit + eine rote ⚠-Box mit der
  Warnung, wenn er im falschen Netz sitzt.
- README/compose: klar dokumentiert, dass der Satellit im ECHTEN Ziel-LAN laufen
  muss (Linux Docker Engine ODER nativ python satellite.py); Docker-Desktop-Falle.

py/node clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 19:46:34 +02:00
co-authored by Claude Opus 4.8
parent 1dc8c0936f
commit 57e13800e0
5 changed files with 91 additions and 9 deletions
+52 -1
View File
@@ -102,6 +102,52 @@ if CONTROL_ENABLED:
CAPABILITIES += CONTROL_ALLOWLIST
# ─── Netz-Kontext / Selbstdiagnose ──────────────────────────────────
def _in_docker_bridge(ip: str) -> bool:
# Docker-Default-Bridge-Range 172.16.0.0/12
try:
a, b = ip.split(".")[:2]
return a == "172" and 16 <= int(b) <= 31
except Exception:
return False
def _net_context() -> dict:
"""Ermittelt in welchem Netz der Satellit LAeUFT — und warnt, wenn das ein
Docker-/NAT-Netz ist (dann erreicht Discovery das echte LAN nicht)."""
ips: list[str] = []
primary = ""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(1)
s.connect(("8.8.8.8", 80))
primary = s.getsockname()[0]
s.close()
except Exception:
pass
try:
for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET):
ip = info[4][0]
if ip and not ip.startswith("127.") and ip not in ips:
ips.append(ip)
except Exception:
pass
if primary and primary not in ips:
ips.insert(0, primary)
p = primary or (ips[0] if ips else "")
warning = ""
if p.startswith("192.168.65.") or _in_docker_bridge(p):
warning = (f"Satellit laeuft in einem Docker-/NAT-Netz ({p}), NICHT im echten LAN. "
"mDNS/SSDP erreichen die realen Geraete so nicht. Auf Docker Desktop "
"(Mac/Windows) geht LAN-Discovery nicht — den Satelliten NATIV (python "
"satellite.py) oder auf einem Linux-Host im Ziel-LAN betreiben.")
return {"primary_ip": p, "ips": ips, "warning": warning}
NET = _net_context()
# ─── Discovery ──────────────────────────────────────────────────────
def _discover_mdns(timeout: float) -> list[dict]:
@@ -467,6 +513,7 @@ class Satellite:
"location": SATELLITE_LOCATION,
"caps": CAPABILITIES,
"control": CONTROL_ENABLED,
"net": NET,
},
"timestamp": int(time.time() * 1000),
})
@@ -496,7 +543,8 @@ class Satellite:
await self._send({
"type": "sat_devices",
"payload": {"requestId": req_id, "satellite": SATELLITE_ID,
"location": SATELLITE_LOCATION, "devices": devices},
"location": SATELLITE_LOCATION, "devices": devices,
"net": NET},
"timestamp": int(time.time() * 1000),
})
@@ -566,6 +614,9 @@ class Satellite:
def main() -> None:
logger.info("ARIA Satellit startet — id=%s location=%s control=%s",
SATELLITE_ID, SATELLITE_LOCATION, CONTROL_ENABLED)
logger.info("Netz: primary_ip=%s alle=%s", NET.get("primary_ip"), NET.get("ips"))
if NET.get("warning"):
logger.warning("⚠ %s", NET["warning"])
try:
asyncio.run(Satellite().run())
except KeyboardInterrupt: