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:
+35
-1
@@ -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")
|
_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):
|
def _ssh_aria_vm(*args: str, timeout: int = 25):
|
||||||
import subprocess
|
import subprocess
|
||||||
cmd = ["ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=8",
|
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)
|
vm = project_vms_mod.get_vm(project_id, name)
|
||||||
if not vm:
|
if not vm:
|
||||||
raise HTTPException(status_code=404, detail=f"VM '{name}' nicht gefunden")
|
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)),
|
return {"ok": rc == 0, "name": name, "vnc_port": 5900 + int(vm.get("vnc_display", 1)),
|
||||||
"output": (out.strip() or err.strip())[:500]}
|
"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}")
|
raise HTTPException(status_code=500, detail=f"Screenshot-Datei nicht gefunden: {local}")
|
||||||
with open(local, "rb") as f:
|
with open(local, "rb") as f:
|
||||||
data = f.read()
|
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),
|
return {"ok": True, "name": name, "filename": os.path.basename(local),
|
||||||
|
"projectPath": proj_rel,
|
||||||
"base64": base64.b64encode(data).decode("ascii")}
|
"base64": base64.b64encode(data).decode("ascii")}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ services:
|
|||||||
- brain
|
- brain
|
||||||
networks:
|
networks:
|
||||||
- aria-net
|
- aria-net
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway" # fuer den VNC-Tunnel zum Host (QEMU)
|
||||||
ports:
|
ports:
|
||||||
- "3001:3001" # Diagnostic Web-UI (Diagnostic teilt Netzwerk mit Bridge)
|
- "3001:3001" # Diagnostic Web-UI (Diagnostic teilt Netzwerk mit Bridge)
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ cmd_boot() {
|
|||||||
local bin; bin="$(qemu_bin_for "${arch}")"
|
local bin; bin="$(qemu_bin_for "${arch}")"
|
||||||
|
|
||||||
local iso="" floppy="" disk="" bootdev="" display=1 mem=1024 machine=""
|
local iso="" floppy="" disk="" bootdev="" display=1 mem=1024 machine=""
|
||||||
|
# VNC bindet an 127.0.0.1 (Loopback) — von aussen nur ueber den RVS-Tunnel der
|
||||||
|
# Bridge erreichbar. Die Bridge (Container) kann Loopback aber NICHT erreichen;
|
||||||
|
# der Brain gibt deshalb per --vnc-bind die Docker-Gateway-IP mit (container-
|
||||||
|
# intern, NICHT im LAN/Internet). Default bleibt Loopback.
|
||||||
|
local vncbind="${ARIA_VM_VNC_BIND:-127.0.0.1}"
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--iso) iso="${2:?}"; shift 2 ;;
|
--iso) iso="${2:?}"; shift 2 ;;
|
||||||
@@ -89,6 +94,7 @@ cmd_boot() {
|
|||||||
--boot) bootdev="${2:?}"; shift 2 ;; # Boot-Reihenfolge (a/c/d)
|
--boot) bootdev="${2:?}"; shift 2 ;; # Boot-Reihenfolge (a/c/d)
|
||||||
--disk-boot) bootdev="c"; shift ;;
|
--disk-boot) bootdev="c"; shift ;;
|
||||||
--vnc-display) display="${2:?}"; shift 2 ;;
|
--vnc-display) display="${2:?}"; shift 2 ;;
|
||||||
|
--vnc-bind) vncbind="${2:?}"; shift 2 ;; # Bind-Adresse fuer -vnc
|
||||||
--mem) mem="${2:?}"; shift 2 ;;
|
--mem) mem="${2:?}"; shift 2 ;;
|
||||||
--machine) machine="${2:?}"; shift 2 ;;
|
--machine) machine="${2:?}"; shift 2 ;;
|
||||||
*) die "unbekannte Option: $1" ;;
|
*) die "unbekannte Option: $1" ;;
|
||||||
@@ -103,7 +109,7 @@ cmd_boot() {
|
|||||||
die "Keine Boot-Medien fuer '${name}' (disk.qcow2 / --iso / --floppy). Erst 'create <name> <arch> <groesse>' oder ein Medium angeben."
|
die "Keine Boot-Medien fuer '${name}' (disk.qcow2 / --iso / --floppy). Erst 'create <name> <arch> <groesse>' oder ein Medium angeben."
|
||||||
|
|
||||||
local args=(-name "${name}" -m "${mem}"
|
local args=(-name "${name}" -m "${mem}"
|
||||||
-vnc "127.0.0.1:${display}"
|
-vnc "${vncbind}:${display}"
|
||||||
-monitor "unix:${d}/monitor.sock,server,nowait"
|
-monitor "unix:${d}/monitor.sock,server,nowait"
|
||||||
-pidfile "${d}/pid" -daemonize)
|
-pidfile "${d}/pid" -daemonize)
|
||||||
[[ -n "${disk}" ]] && args+=(-drive "file=${disk},format=qcow2")
|
[[ -n "${disk}" ]] && args+=(-drive "file=${disk},format=qcow2")
|
||||||
@@ -132,7 +138,7 @@ cmd_boot() {
|
|||||||
args+=(-boot "${bootdev}")
|
args+=(-boot "${bootdev}")
|
||||||
|
|
||||||
"${bin}" "${args[@]}"
|
"${bin}" "${args[@]}"
|
||||||
echo "VM '${name}' gestartet (${arch}) — VNC 127.0.0.1:${display} (Port $((5900+display)))."
|
echo "VM '${name}' gestartet (${arch}) — VNC ${vncbind}:${display} (Port $((5900+display)))."
|
||||||
echo "vnc_display=${display} vnc_port=$((5900+display))"
|
echo "vnc_display=${display} vnc_port=$((5900+display))"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user