vm_register + Registry + boot fassen jetzt das ECHTE Boot-Medium (disk/floppy/ iso) — Eintrag, Startbefehl und App-Start spiegeln die reale Config. Leer = aria-vm erkennt disk.qcow2/floppy.img/cdrom.iso selbst. - project_vms.add_vm: floppy/disk-Felder. - main.py: VmAddBody + _vm_boot_args (baut --disk/--floppy/--iso), boot-Endpoint + boot_cmd nutzen sie. - agent.py: vm_register-Tool bekommt disk/floppy/iso. - Seed-Regel: URTEIL — VM nur wenn sinnvoll; Medium aus der Situation (Festplatte fuer DOS-Spiele, Diskette fuer OS-Dev, ISO fuer Installer); Architektur zum Task (ARM=aarch64, emuliert/langsam ok); Aenderungs-Zyklus stop→aendern→boot. py_compile clean. aria-vm schon live auf dem Host. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
"""
|
|
project_vms — Registry der QEMU-VMs PRO PROJEKT.
|
|
|
|
Persistenz: /shared/config/project_vms.json → { project_id: [ {vm}, ... ] }.
|
|
Eine VM = {name, arch, iso, vnc_display, mem, created_at, updated_at}. Der echte
|
|
Start/Stop laeuft ueber `aria-vm` auf dem Host (SSH aria-wohnung, siehe main.py);
|
|
diese Datei haelt nur die Zuordnung VM ↔ Projekt + die Startparameter, damit die
|
|
Liste im Desktop-Panel der App pro Projekt erscheint (auch wenn leer).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
VMS_FILE = Path(os.environ.get("PROJECT_VMS_FILE", "/shared/config/project_vms.json"))
|
|
NAME_RE = re.compile(r"^[a-zA-Z0-9_-]{1,40}$")
|
|
VALID_ARCH = {"x86_64", "amd64", "i386", "i686", "x86", "arm", "aarch64",
|
|
"mips", "mipsel", "mips64", "ppc", "ppc64", "riscv64", "sparc"}
|
|
|
|
|
|
def _load() -> dict:
|
|
if not VMS_FILE.exists():
|
|
return {}
|
|
try:
|
|
data = json.loads(VMS_FILE.read_text(encoding="utf-8"))
|
|
return data if isinstance(data, dict) else {}
|
|
except Exception:
|
|
return {}
|
|
|
|
|
|
def _save(data: dict) -> None:
|
|
VMS_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = VMS_FILE.with_suffix(".tmp")
|
|
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
tmp.replace(VMS_FILE)
|
|
|
|
|
|
def list_vms(project_id: str) -> list[dict]:
|
|
return _load().get(project_id or "", [])
|
|
|
|
|
|
def get_vm(project_id: str, name: str) -> Optional[dict]:
|
|
for v in list_vms(project_id):
|
|
if v.get("name") == name:
|
|
return v
|
|
return None
|
|
|
|
|
|
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 —
|
|
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)."""
|
|
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:
|
|
raise ValueError(f"Unbekannte Architektur: {arch!r}")
|
|
data = _load()
|
|
lst = data.setdefault(project_id or "", [])
|
|
now = int(time.time())
|
|
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
|
|
vm = {"name": name, "created_at": now, **fields}
|
|
lst.append(vm)
|
|
_save(data)
|
|
return vm
|
|
|
|
|
|
def remove_vm(project_id: str, name: str) -> bool:
|
|
data = _load()
|
|
lst = data.get(project_id or "")
|
|
if not lst:
|
|
return False
|
|
new = [v for v in lst if v.get("name") != name]
|
|
if len(new) == len(lst):
|
|
return False
|
|
data[project_id or ""] = new
|
|
_save(data)
|
|
return True
|