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
+61 -4
View File
@@ -13,13 +13,30 @@ from .session import launch_session, session_helper, view
def parser() -> argparse.ArgumentParser:
cli = argparse.ArgumentParser(prog='hydra', description='Independent HydraOS VM manager')
cli = argparse.ArgumentParser(prog='hydra', description='Hydra OS independent VM manager')
cli.add_argument('--config', type=Path, default=Path(os.environ.get('XDG_CONFIG_HOME', Path.home() / '.config')) / 'hydra')
cli.add_argument('--state', type=Path, default=Path(os.environ.get('XDG_STATE_HOME', Path.home() / '.local/state')) / 'hydra')
cli.add_argument('--runtime', type=Path, default=Path(os.environ.get('XDG_RUNTIME_DIR', f'/tmp/hydra-{os.getuid()}')) / 'hydra')
cli.add_argument('--defaults', type=Path, default=Path('/etc/hydra/defaults.toml'),
help='Distribution preferences for newly created profiles')
commands = cli.add_subparsers(dest='command', required=True)
commands = cli.add_subparsers(dest='command')
commands.add_parser('menu', help='Open the terminal VM menu')
download = commands.add_parser('download', help='Download and validate a direct ISO URL')
download.add_argument('url')
download.add_argument('--sha256')
run = commands.add_parser('run', help='Create, start and display a VM from a local ISO or HTTP(S) URL')
run.add_argument('source')
run.add_argument('--name', required=True)
run.add_argument('--memory', '--ram', type=int, default=4096, help='VM RAM in MiB')
run.add_argument('--cpus', type=int, default=2)
run.add_argument('--disk-size', default='40G')
run.add_argument('--sha256')
run.add_argument('--no-view', action='store_true')
run.add_argument('--windowed', action='store_true')
opener = commands.add_parser('open', help='Start a stopped VM or reconnect a running VM')
opener.add_argument('name')
opener.add_argument('--no-view', action='store_true')
opener.add_argument('--windowed', action='store_true')
add = commands.add_parser('add', help='Create a VM profile without starting it')
add.add_argument('name')
add.add_argument('--iso', type=Path)
@@ -48,7 +65,7 @@ def parser() -> argparse.ArgumentParser:
viewer = commands.add_parser('view', help='Connect a viewer without changing VM power')
viewer.add_argument('name')
viewer.add_argument('--windowed', action='store_true')
session = commands.add_parser('session', help='Start the minimal HydraOS labwc session')
session = commands.add_parser('session', help='Start the minimal Hydra OS labwc session')
session.add_argument('--nested', action='store_true')
helper = commands.add_parser('_session-helper', help=argparse.SUPPRESS)
helper.add_argument('operation')
@@ -56,10 +73,47 @@ def parser() -> argparse.ArgumentParser:
return cli
def _download_progress():
last = None
interactive = sys.stderr.isatty()
def report(received, total):
nonlocal last
step = received * 100 // total if total else received // (64 * 1024 * 1024)
if last == step:
return
last = step
if not interactive and received and (total is None or received != total):
return
suffix = f' / {total // (1024 * 1024)} MiB ({step}%)' if total else ' MiB'
print(f'Download: {received // (1024 * 1024)}{suffix}', file=sys.stderr)
return report
def main(argv: list[str] | None = None) -> int:
args = parser().parse_args(argv)
cli = parser()
args = cli.parse_args(argv)
runtime = Runtime(args.config, args.state, args.runtime)
try:
if args.command is None and not sys.stdin.isatty():
cli.print_help()
return 0
if args.command in (None, 'menu'):
from .launch import session_manifest
from .menu import run_menu
return run_menu(runtime, manifest=session_manifest(runtime))
if args.command == 'download':
from .media import download_iso
print(download_iso(args.url, sha256=args.sha256, progress=_download_progress()))
return 0
if args.command == 'run':
from .launch import run_source
return run_source(runtime, args.source, name=args.name, memory=args.memory,
cpus=args.cpus, disk_size=args.disk_size, sha256=args.sha256,
no_view=args.no_view, windowed=args.windowed, defaults=args.defaults,
progress=_download_progress())
if args.command == 'open':
from .launch import open_vm
return open_vm(runtime, args.name, no_view=args.no_view, windowed=args.windowed)
if args.command == 'view':
return view(runtime, args.name, windowed=args.windowed)
elif args.command == 'session':
@@ -87,6 +141,9 @@ def main(argv: list[str] | None = None) -> int:
result = getattr(runtime, args.command)(args.name)
print(json.dumps(result, indent=2))
return 0
except KeyboardInterrupt:
print('hydra: Cancelled', file=sys.stderr)
return 130
except (RuntimeError, OSError, ValueError, subprocess.TimeoutExpired) as exc:
print(f'hydra: {exc}', file=sys.stderr)
return 1