Files
ARIA-AGENT/aria-brain/project_vms.py
T
duffyduckandClaude Opus 4.8 055db7c059 feat(vm): Medien pro VM (disk/floppy/iso) + ARIA-Urteilswissen
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>
2026-07-20 23:51:27 +02:00

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