import argparse import logging import sys from pathlib import Path from clan_lib.errors import ClanError from clan_lib.machines.install import BuildOn, InstallOptions, install_machine from clan_lib.machines.machines import Machine from clan_lib.ssh.remote import Remote from clan_cli.completions import ( add_dynamic_completer, complete_machines, complete_target_host, ) from clan_cli.machines.hardware import HardwareConfig from clan_cli.ssh.deploy_info import DeployInfo, find_reachable_host, ssh_command_parse log = logging.getLogger(__name__) def install_command(args: argparse.Namespace) -> None: try: # Only if the caller did not specify a target_host via args.target_host # Find a suitable target_host that is reachable target_host_str = args.target_host deploy_info: DeployInfo | None = ssh_command_parse(args) use_tor = False if deploy_info and not args.target_host: host = find_reachable_host(deploy_info) if host is None: use_tor = True target_host_str = deploy_info.tor.target else: target_host_str = host.target if args.password: password = args.password elif deploy_info and deploy_info.addrs[0].password: password = deploy_info.addrs[0].password else: password = None machine = Machine(name=args.machine, flake=args.flake, nix_options=args.option) host_key_check = args.host_key_check if target_host_str is not None: target_host = Remote.from_ssh_uri( machine_name=machine.name, address=target_host_str ).override(host_key_check=host_key_check) else: target_host = machine.target_host().override(host_key_check=host_key_check) if machine._class_ == "darwin": msg = "Installing macOS machines is not yet supported" raise ClanError(msg) if args.flake is None: msg = "Could not find clan flake toplevel directory" raise ClanError(msg) if not args.yes: ask = input(f"Install {args.machine} to {target_host.target}? [y/N] ") if ask != "y": return None return install_machine( InstallOptions( machine=machine, kexec=args.kexec, phases=args.phases, debug=args.debug, no_reboot=args.no_reboot, nix_options=args.option, build_on=BuildOn(args.build_on) if args.build_on is not None else None, update_hardware_config=HardwareConfig(args.update_hardware_config), password=password, identity_file=args.identity_file, use_tor=use_tor, ), target_host=target_host, ) except KeyboardInterrupt: log.warning("Interrupted by user") sys.exit(1) def register_install_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--kexec", type=str, help="use another kexec tarball to bootstrap NixOS", ) parser.add_argument( "--no-reboot", action="store_true", help="do not reboot after installation (deprecated)", default=False, ) parser.add_argument( "--host-key-check", choices=["strict", "ask", "tofu", "none"], default="ask", help="Host key (.ssh/known_hosts) check mode.", ) parser.add_argument( "--build-on", choices=[x.value for x in BuildOn], default=None, help="where to build the NixOS configuration", ) parser.add_argument( "--yes", action="store_true", help="do not ask for confirmation", default=False, ) parser.add_argument( "--update-hardware-config", type=str, default="none", help="update the hardware configuration", choices=[x.value for x in HardwareConfig], ) parser.add_argument( "--phases", type=str, help="comma separated list of phases to run. Default is: kexec,disko,install,reboot", ) machines_parser = parser.add_argument( "machine", type=str, help="machine to install", ) add_dynamic_completer(machines_parser, complete_machines) group = parser.add_mutually_exclusive_group(required=False) group.add_argument( "-j", "--json", help="specify the json file for ssh data (generated by starting the clan installer)", ) target_host_parser = group.add_argument( "--target-host", help="ssh address to install to in the form of user@host:2222", ) add_dynamic_completer(target_host_parser, complete_target_host) authentication_group = parser.add_mutually_exclusive_group() authentication_group.add_argument( "--password", help="specify the password for the ssh connection (generated by starting the clan installer)", ) authentication_group.add_argument( "-i", dest="identity_file", type=Path, help="specify which SSH private key file to use", ) group.add_argument( "-P", "--png", help="specify the json file for ssh data as the qrcode image (generated by starting the clan installer)", ) parser.set_defaults(func=install_command)