"""Portable source-checkout command line interface.""" import argparse import json import os import subprocess import sys from pathlib import Path from .profiles import Profile from .runtime import Runtime, RuntimeError from .session import launch_session, session_helper, view def parser() -> argparse.ArgumentParser: cli = argparse.ArgumentParser(prog='hydra', description='Independent HydraOS 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') commands = cli.add_subparsers(dest='command', required=True) add = commands.add_parser('add', help='Create a VM profile without starting it') add.add_argument('name') add.add_argument('--iso', type=Path, required=True) add.add_argument('--memory', type=int, default=4096) add.add_argument('--cpus', type=int, default=2) add.add_argument('--disk-size', default='40G') add.add_argument('--graphics', choices=('auto', 'software'), default='auto') add.add_argument('--network', choices=('user', 'bridge'), default='user') add.add_argument('--bridge', default='hydrabr0') commands.add_parser('list', help='List configured VMs') status = commands.add_parser('status', help='Query VM state through private QMP') status.add_argument('name', nargs='?') for command in ('start', 'shutdown', 'stop'): cmd = commands.add_parser(command) cmd.add_argument('name') if command == 'stop': cmd.add_argument('--force', action='store_true', required=True) 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.add_argument('--nested', action='store_true') helper = commands.add_parser('_session-helper', help=argparse.SUPPRESS) helper.add_argument('operation') helper.add_argument('manifest', type=Path) return cli def main(argv: list[str] | None = None) -> int: args = parser().parse_args(argv) runtime = Runtime(args.config, args.state, args.runtime) try: if args.command == 'view': return view(runtime, args.name, windowed=args.windowed) elif args.command == 'session': return launch_session(runtime, nested=args.nested) elif args.command == '_session-helper': return session_helper(args.operation, args.manifest) elif args.command == 'add': profile = Profile(args.name, args.iso, args.memory, args.cpus, args.disk_size, args.graphics, args.network, args.bridge) runtime.add(profile) result = {'name': profile.name, 'status': 'added'} elif args.command == 'list': result = [{'name': profile.name, 'iso': str(profile.iso)} for profile in runtime.profiles()] elif args.command == 'status': result = runtime.status(args.name) if args.name else [runtime.status(p.name) for p in runtime.profiles()] elif args.command == 'stop': result = runtime.stop(args.name, force=args.force) else: result = getattr(runtime, args.command)(args.name) print(json.dumps(result, indent=2)) return 0 except (RuntimeError, OSError, ValueError, subprocess.TimeoutExpired) as exc: print(f'hydra: {exc}', file=sys.stderr) return 1