Add safe ISO download and explicit run/open commands

This commit is contained in:
Codex
2026-10-13 15:14:10 +03:00
parent 1784f3a8ce
commit e6dc59b567
6 changed files with 706 additions and 4 deletions
+123
View File
@@ -0,0 +1,123 @@
"""Explicit start-if-stopped and new-ISO launch, preserving runtime authority."""
import json
import os
from pathlib import Path
from . import media, session
from .profiles import Profile, load_defaults, validate_name
from .runtime import Runtime, RuntimeError
HOST_RESERVE_MIB = 768
def available_memory_mib() -> int:
"""Linux MemAvailable accounts for reclaimable host caches."""
try:
for line in Path('/proc/meminfo').read_text().splitlines():
if line.startswith('MemAvailable:'):
return int(line.split()[1]) // 1024
except (OSError, ValueError, IndexError) as exc:
raise RuntimeError('Cannot estimate available RAM from /proc/meminfo') from exc
raise RuntimeError('Cannot estimate available RAM: MemAvailable is missing')
def _memory_preflight(memory: int) -> None:
available = available_memory_mib()
if memory + HOST_RESERVE_MIB > available:
raise RuntimeError(f'Not enough available RAM: VM needs {memory} MiB plus a {HOST_RESERVE_MIB} MiB host reserve; '
f'{available} MiB available. Choose less --memory or stop another VM')
def session_manifest(runtime: Runtime, manifest: Path | None = None) -> Path | None:
"""Use an inherited session only when it belongs to these runtime roots."""
explicit = manifest is not None
inherited = os.environ.get('HYDRA_SESSION_MANIFEST')
if not explicit and not inherited:
return None
path = Path(manifest if explicit else inherited).expanduser().absolute()
try:
data = json.loads(path.read_text(encoding='utf-8'))
if (not isinstance(data, dict) or
any(not isinstance(data.get(key), str) or not Path(data[key]).is_absolute()
or Path(data[key]) != getattr(runtime, key) for key in ('config', 'state', 'runtime')) or
not isinstance(data.get('names'), list) or
any(not isinstance(name, str) for name in data['names']) or
not isinstance(data.get('terminal'), str) or not Path(data['terminal']).is_absolute() or
not isinstance(data.get('directory'), str) or Path(data['directory']) != path.parent):
raise ValueError('Invalid session manifest or different runtime roots')
for name in data['names']:
validate_name(name)
return path
except (OSError, ValueError) as exc:
if explicit:
raise session.SessionError('Invalid session manifest; start a fresh Hydra OS session') from exc
return None
def _view_preflight(runtime: Runtime, *, no_view: bool, manifest: Path | None) -> Path | None:
if no_view:
return None
selected = session_manifest(runtime, manifest)
session._display()
session._viewer()
return selected
def _show(runtime: Runtime, name: str, *, no_view: bool, windowed: bool,
manifest: Path | None) -> int:
if no_view:
return 0
if manifest is not None:
return session.request_refresh(manifest, selected=name)
return session.view(runtime, name, windowed=windowed)
def open_vm(runtime: Runtime, name: str, *, windowed=False, no_view=False,
manifest: Path | None = None) -> int:
profile = runtime.profile(name)
current = runtime.status(name)
status = current['status']
if status == 'unknown':
raise RuntimeError(f'Cannot open {name}: unknown VM identity; {current.get("error", "inspect its QMP identity before retrying")}')
manifest = _view_preflight(runtime, no_view=no_view, manifest=manifest)
if status == 'stopped':
_memory_preflight(profile.memory)
runtime.start(name)
# Other verified QEMU states (for example paused) are viewable without a
# new process. Runtime.start owns the lock and final identity recheck.
return _show(runtime, name, no_view=no_view, windowed=windowed, manifest=manifest)
def run_source(runtime: Runtime, source: str | Path, *, name: str, memory=4096,
cpus=2, disk_size='40G', sha256: str | None = None, windowed=False,
no_view=False, manifest: Path | None = None, defaults: Path | None = None,
progress=None) -> int:
validate_name(name)
profile_path = runtime.config / f'{name}.toml'
if profile_path.exists() or profile_path.is_symlink():
raise RuntimeError(f'VM {name} already exists; use hydra open {name}')
paths = runtime.paths(name)
if paths.disk.parent.exists() or paths.disk.parent.is_symlink():
raise RuntimeError(f'Existing VM state/disk for {name}; choose a new name and preserve the old disk')
if runtime.status(name)['status'] != 'stopped':
raise RuntimeError(f'Cannot create {name}: existing or unknown VM identity')
preferences = load_defaults(defaults) if defaults is not None else {}
# Validate requested allocations before downloading or creating anything.
Profile(name, memory=memory, cpus=cpus, disk_size=disk_size, **preferences)
manifest = _view_preflight(runtime, no_view=no_view, manifest=manifest)
_memory_preflight(memory)
source = str(source)
if '://' in source:
iso = media.download_iso(source, sha256=sha256, progress=progress)
else:
iso = media.validate_iso(Path(source).expanduser().resolve(), sha256=sha256)
# Recheck after a potentially lengthy transfer. Profile publication itself
# is exclusive ('x') and Runtime never replaces an existing disk.
if paths.disk.parent.exists() or paths.disk.parent.is_symlink():
raise RuntimeError(f'Existing VM state/disk for {name}; choose a new name')
_memory_preflight(memory)
runtime.add(Profile(name, iso, memory, cpus, disk_size, **preferences))
runtime.start(name)
return _show(runtime, name, no_view=no_view, windowed=windowed, manifest=manifest)