Files
Hydra-OS/hydra/cli.py
T

93 lines
4.7 KiB
Python

"""Portable source-checkout command line interface."""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from .profiles import Profile, load_defaults
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')
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)
add = commands.add_parser('add', help='Create a VM profile without starting it')
add.add_argument('name')
add.add_argument('--iso', type=Path)
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'))
add.add_argument('--bridge')
add.add_argument('--width', type=int)
add.add_argument('--height', type=int)
add.add_argument('--refresh-millihz', type=int)
media = commands.add_parser('media', help='Attach or detach installation media while stopped')
media.add_argument('name')
selection = media.add_mutually_exclusive_group(required=True)
selection.add_argument('--iso', type=Path)
selection.add_argument('--detach', action='store_true')
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':
preferences = load_defaults(args.defaults)
preferences.update({key: getattr(args, key) for key in
('network', 'bridge', 'width', 'height', 'refresh_millihz')
if getattr(args, key) is not None})
profile = Profile(args.name, args.iso, args.memory, args.cpus, args.disk_size,
args.graphics, **preferences)
runtime.add(profile)
result = {'name': profile.name, 'status': 'added'}
elif args.command == 'media':
result = runtime.media(args.name, None if args.detach else args.iso)
elif args.command == 'list':
result = [{'name': profile.name, 'iso': str(profile.iso) if profile.iso else None} 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