feat(satellite): der Satelliten-Rechner steht selbst mit in der Geraeteliste

Der Satellit laeuft auf einem ganz normalen Rechner — bei Stefan auf
duffy-desktop. Dort laufen typischerweise noch andere Dienste (FreeCAD-MCP,
Web-App, Dateiserver). Bisher war ausgerechnet dieser Rechner unsichtbar: man
findet sich nicht per mDNS/ARP selbst.

Er erscheint jetzt als normales Geraet — ansprechbar wie jedes andere, nur
zusaetzlich mit 🛰️ markiert und blau abgesetzt, damit klar ist, dass dort der
Satellit traegt. Abschaltbar ueber LIST_SELF=false, falls man den Traeger
bewusst heraushalten will.

Nebeneffekt, der ein echtes Problem loest: der eigene Rechner gilt damit als
"gefunden" und ist ohne Eintrag in TUNNEL_ALLOW tunnelbar. Genau der Fall, um
den es ging — der FreeCAD-MCP laeuft auf dem Satelliten-Host selbst.

Beim Zusammenfuehren bleibt die Markierung erhalten: findet mDNS denselben
Rechner nochmal, haette der Merge isSatellite sonst verschluckt. hostname kommt
aus demselben Grund mit in die erhaltenen Felder.

Dazu ein ✎-Knopf pro Geraet in der Diagnostic: Namen vergeben, damit ARIA
"duffy-desktop" ansprechen kann statt einer IP. Der Name haengt an der MAC und
ueberlebt einen DHCP-Wechsel.

8 Pruefungen: Selbst-Eintrag vollstaendig, Markierung ueberlebt den Merge,
mDNS-Infos bleiben erhalten, eigener Rechner ohne TUNNEL_ALLOW erlaubt, fremde
IP weiterhin blockiert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-04 18:49:49 +02:00
co-authored by Claude Opus 5
parent cfd17b200a
commit 6404b99510
4 changed files with 95 additions and 7 deletions
+9
View File
@@ -96,6 +96,15 @@ DEVICE_CACHE_TTL_SEC=120 # wie lange ein Inventar als "frisch" gilt
# Bridge macht daraus einen lokalen Port (aria-bridge:19000...).
#
# DEFAULT AUS: das ist ein Weg in dein Netz, den schaltet man bewusst ein.
# Soll der Rechner, auf dem der Satellit laeuft, SELBST in der Geraeteliste
# auftauchen? Er wird per mDNS/ARP nicht gefunden (man findet sich nicht
# selbst), ist aber oft der interessanteste Host — dort laufen typischerweise
# weitere Dienste (FreeCAD-MCP, Web-App, Dateiserver). Er erscheint dann als
# normales Geraet, zusaetzlich mit 🛰️ gekennzeichnet, und laesst sich genauso
# ansprechen. Nebeneffekt: er gilt damit als "gefunden" und ist ohne Eintrag in
# TUNNEL_ALLOW tunnelbar.
LIST_SELF=true
TUNNEL_ENABLED=false
# Welche Ziele sind erlaubt? LEER = nur Geraete, die der Satellit bei der Suche
+44 -3
View File
@@ -151,6 +151,12 @@ CONTROL_ALLOWLIST = [
# DEFAULT AUS. Das ist ein Loch in ein fremdes Netz — das schaltet man bewusst
# ein, nicht aus Versehen.
TUNNEL_ENABLED = _env_bool("TUNNEL_ENABLED", False)
# Soll der Rechner, auf dem der Satellit laeuft, selbst in der Geraeteliste
# auftauchen? Default JA — meist ist er ein ganz normaler Host mit weiteren
# Diensten. Wer den Satelliten-Rechner bewusst heraushalten will, setzt das
# auf false.
LIST_SELF = _env_bool("LIST_SELF", True)
# Welche Ziele? Leer = nur Geraete, die der Satellit bei der Suche auch
# wirklich GEFUNDEN hat (beliebiger Port). Damit kann der Tunnel nichts
# ansteuern, was nicht ohnehin im LAN sichtbar ist — insbesondere nichts im
@@ -613,8 +619,11 @@ def _merge_devices(*lists) -> list[dict]:
merged = {**d, **{k: v for k, v in cur.items() if v}}
if order.get(d.get("via"), 0) >= order.get(cur.get("via"), 0):
merged.update({k: v for k, v in d.items() if v})
# DIAL-URL / mac aus beiden behalten
for key in ("dialAppUrl", "mac", "model", "manufacturer"):
# DIAL-URL / mac aus beiden behalten. isSatellite/satelliteId
# muessen mit: der Satellit findet sich selbst auch per mDNS,
# und dann duerfte die Kennzeichnung nicht verlorengehen.
for key in ("dialAppUrl", "mac", "model", "manufacturer",
"isSatellite", "satelliteId", "hostname"):
merged[key] = d.get(key) or cur.get(key) or merged.get(key)
by_ip[ip] = {k: v for k, v in merged.items() if v not in (None, "")}
return list(by_ip.values()) + loose
@@ -655,6 +664,36 @@ def _names_save() -> None:
logger.warning("device_names.json schreiben fehlgeschlagen: %s", exc)
def _self_device() -> dict:
"""Der Rechner, auf dem der Satellit SELBST laeuft, als normales Geraet.
Er taucht in der Suche nicht auf (man findet sich nicht per mDNS/ARP
selbst), ist aber oft der interessanteste Host im Netz: hier laufen
typischerweise noch andere Dienste — ein FreeCAD-MCP, eine Web-App, ein
Dateiserver. Ohne diesen Eintrag waere ausgerechnet der Rechner unsichtbar,
auf dem ARIA schon eine Verbindung hat.
`isSatellite` kennzeichnet ihn; ansonsten ist er ein Geraet wie jedes
andere und laesst sich genauso ansprechen.
"""
ip = (NET or {}).get("primary_ip") or ""
if not ip:
return {}
d = {
"ip": ip,
"mac": (NET or {}).get("mac") or "",
"via": "self",
"isSatellite": True,
"satelliteId": SATELLITE_ID,
"type": "host",
}
try:
d["hostname"] = socket.gethostname()
except Exception:
pass
return {k: v for k, v in d.items() if v not in (None, "")}
def _name_key(dev: dict) -> str:
mac = (dev.get("mac") or "").strip().lower()
return mac if mac else (dev.get("ip") or "").strip()
@@ -1778,7 +1817,9 @@ class Satellite:
mdns = await loop.run_in_executor(None, _discover_mdns, DISCOVER_TIMEOUT_SEC)
ssdp = await loop.run_in_executor(None, _discover_ssdp, DISCOVER_TIMEOUT_SEC)
arp = await loop.run_in_executor(None, _discover_arp)
self._devices = _apply_names(_merge_devices(mdns, ssdp, arp))
_self = [_self_device()] if LIST_SELF else []
_self = [d for d in _self if d]
self._devices = _apply_names(_merge_devices(mdns, ssdp, arp, _self))
if SNMP_DISCOVERY:
await self._enrich_snmp(self._devices)
await self._enrich_adb(self._devices)