fix(vm): global eindeutige VNC-Displays (kein Doppel-Binding) + Gateway per-boot
- project_vms.add_vm: VNC-Display wird GLOBAL eindeutig vergeben (ueber alle Projekte). vnc_display<=0 oder belegtes Display → naechstes freies. Updates behalten ihr Display. Verhindert Port-Konflikt (5901) wenn mehrere VMs laufen. Getestet: vm1..3 → 1,2,3; Wunsch 2 (belegt) → 4; Update behaelt Display. - vm_register-Tool + Seed-Regel: vnc_display weglassen, wird auto-vergeben. - Gateway-IP: der Brain liest sie aus /proc/net/route bei JEDEM Boot frisch (Docker-IP-Aenderung nach Netz-Neuaufbau wird abgefangen); boot-Antwort enthaelt vnc_bind zur Transparenz. py_compile clean. Brain-only, Deploy: brain rebuild. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+37
-10
@@ -51,13 +51,30 @@ def get_vm(project_id: str, name: str) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
def _used_displays(data: dict, exclude: object = None) -> set:
|
||||
"""Alle VNC-Displays, die ueber ALLE Projekte belegt sind (exclude = eine
|
||||
VM-Dict-Instanz, die ignoriert wird — fuer Updates)."""
|
||||
used = set()
|
||||
for lst in data.values():
|
||||
for v in lst:
|
||||
if v is exclude:
|
||||
continue
|
||||
try:
|
||||
used.add(int(v.get("vnc_display", 1)))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return used
|
||||
|
||||
|
||||
def add_vm(project_id: str, name: str, arch: str, iso: str = "",
|
||||
floppy: str = "", disk: str = "",
|
||||
vnc_display: int = 1, mem: int = 1024) -> dict:
|
||||
"""Registriert/aktualisiert eine VM. Medien (disk/floppy/iso) sind optional —
|
||||
vnc_display: int = 0, mem: int = 1024) -> dict:
|
||||
"""Registriert/aktualisiert eine VM. Medien (disk/floppy/iso) optional —
|
||||
leer = aria-vm erkennt disk.qcow2/floppy.img/cdrom.iso im VM-Ordner selbst.
|
||||
ARIA setzt hier, was sie zum Task passend gebaut hat (Festplatte fuer DOS-
|
||||
Spiele, Diskette fuer OS-Dev, ISO fuer Installer)."""
|
||||
|
||||
Das VNC-Display wird GLOBAL eindeutig vergeben (ueber alle Projekte), damit
|
||||
mehrere laufende VMs nicht denselben Port doppelt binden. vnc_display<=0 oder
|
||||
ein bereits belegtes Display → automatisch das naechste freie."""
|
||||
if not NAME_RE.match(name or ""):
|
||||
raise ValueError(f"Ungueltiger VM-Name: {name!r} (nur a-z0-9_-, max 40)")
|
||||
if arch not in VALID_ARCH:
|
||||
@@ -65,13 +82,23 @@ def add_vm(project_id: str, name: str, arch: str, iso: str = "",
|
||||
data = _load()
|
||||
lst = data.setdefault(project_id or "", [])
|
||||
now = int(time.time())
|
||||
existing = next((v for v in lst if v.get("name") == name), None)
|
||||
|
||||
used = _used_displays(data, exclude=existing)
|
||||
req = int(vnc_display or 0)
|
||||
if req <= 0 and existing: # Update ohne Display-Wunsch → behalten
|
||||
req = int(existing.get("vnc_display", 0) or 0)
|
||||
if req <= 0 or req in used: # frei/eindeutig machen
|
||||
req = 1
|
||||
while req in used:
|
||||
req += 1
|
||||
|
||||
fields = {"arch": arch, "iso": iso, "floppy": floppy, "disk": disk,
|
||||
"vnc_display": int(vnc_display), "mem": int(mem), "updated_at": now}
|
||||
for v in lst:
|
||||
if v.get("name") == name:
|
||||
v.update(fields)
|
||||
_save(data)
|
||||
return v
|
||||
"vnc_display": req, "mem": int(mem), "updated_at": now}
|
||||
if existing:
|
||||
existing.update(fields)
|
||||
_save(data)
|
||||
return existing
|
||||
vm = {"name": name, "created_at": now, **fields}
|
||||
lst.append(vm)
|
||||
_save(data)
|
||||
|
||||
Reference in New Issue
Block a user