""" 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