fix(vnc): Bridge erreicht QEMU-VNC (Kern-Bug) + Screenshots ins Projekt

Live-VNC rendert nie, weil die Bridge die VM nie erreichte: QEMU band 127.0.0.1
(Host-Loopback) und host.docker.internal resolved in der Bridge gar nicht.
- aria-vm: --vnc-bind <ip> (Default 127.0.0.1 / env ARIA_VM_VNC_BIND).
- Brain: _docker_gateway() (aus /proc/net/route) → boot bindet VNC an die
  Docker-Gateway-IP (container-erreichbar, NICHT im LAN/Internet).
- docker-compose: bridge bekommt extra_hosts host.docker.internal:host-gateway.
- Screenshot-Endpoint kopiert das PNG zusaetzlich nach
  /shared/projects/<pid>/screenshots/ → erscheint im Dateien-Panel.

End-to-end auf dem Host validiert: Bridge liest den RFB-Handshake
(b'RFB 003.008\n') von der Gateway-gebundenen VNC. aria-vm bereits live.
py/bash clean. Deploy: brain rebuild + bridge NEU (extra_hosts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 00:20:21 +02:00
co-authored by Claude Opus 4.8
parent 5890c17ec0
commit d38d62ba21
3 changed files with 45 additions and 3 deletions
+35 -1
View File
@@ -946,6 +946,23 @@ def project_file(project_id: str, path: str, binary: bool = False):
_ARIA_VM_HOST = os.environ.get("ARIA_VM_SSH_HOST", "aria-wohnung")
def _docker_gateway() -> str:
"""Docker-Gateway-IP (= Host-IP auf dem Container-Netz), an die QEMU sein VNC
binden soll: von der Bridge erreichbar, aber NICHT im LAN/Internet. Aus
/proc/net/route (Default-Route), kein `ip`-Tool noetig."""
try:
import socket as _sock
import struct as _struct
with open("/proc/net/route") as f:
for line in f.readlines()[1:]:
fields = line.strip().split()
if len(fields) >= 3 and fields[1] == "00000000" and int(fields[3], 16) & 2:
return _sock.inet_ntoa(_struct.pack("<L", int(fields[2], 16)))
except Exception:
pass
return ""
def _ssh_aria_vm(*args: str, timeout: int = 25):
import subprocess
cmd = ["ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=8",
@@ -1038,7 +1055,13 @@ def project_vm_boot(project_id: str, name: str):
vm = project_vms_mod.get_vm(project_id, name)
if not vm:
raise HTTPException(status_code=404, detail=f"VM '{name}' nicht gefunden")
rc, out, err = _ssh_aria_vm(*_vm_boot_args(vm), timeout=40)
# VNC an die Docker-Gateway-IP binden, damit die Bridge den Stream tunneln
# kann (Loopback ist von Containern nicht erreichbar). NICHT im LAN sichtbar.
boot_args = _vm_boot_args(vm)
gw = _docker_gateway()
if gw:
boot_args += ["--vnc-bind", gw]
rc, out, err = _ssh_aria_vm(*boot_args, timeout=40)
return {"ok": rc == 0, "name": name, "vnc_port": 5900 + int(vm.get("vnc_display", 1)),
"output": (out.strip() or err.strip())[:500]}
@@ -1074,7 +1097,18 @@ def project_vm_screenshot(project_id: str, name: str):
raise HTTPException(status_code=500, detail=f"Screenshot-Datei nicht gefunden: {local}")
with open(local, "rb") as f:
data = f.read()
# Auch ins Projekt kopieren → taucht im Dateien-Panel auf.
proj_rel = ""
try:
shots_dir = os.path.join(_project_dir(project_id), "screenshots")
os.makedirs(shots_dir, exist_ok=True)
with open(os.path.join(shots_dir, os.path.basename(local)), "wb") as f:
f.write(data)
proj_rel = "screenshots/" + os.path.basename(local)
except Exception:
proj_rel = ""
return {"ok": True, "name": name, "filename": os.path.basename(local),
"projectPath": proj_rel,
"base64": base64.b64encode(data).decode("ascii")}