diff --git a/pkgs/clan-cli/clan_cli/__init__.py b/pkgs/clan-cli/clan_cli/__init__.py index 279451212..22f53b374 100644 --- a/pkgs/clan-cli/clan_cli/__init__.py +++ b/pkgs/clan-cli/clan_cli/__init__.py @@ -214,10 +214,15 @@ For more detailed information, visit: {help_hyperlink("getting-started", "https: description="Ssh to a remote machine", epilog=( f""" -This subcommand allows seamless ssh access to the nixos-image builders. +This subcommand allows seamless ssh access to the nixos-image builders or a machine of your clan. Examples: + $ clan ssh [ssh_args ...] berlin` + + Will ssh in to the machine called `berlin`, using the + `clan.core.networking.targetHost` specified in its configuration + $ clan ssh [ssh_args ...] --json [JSON] Will ssh in to the machine based on the deployment information contained in the json string. [JSON] can either be a json formatted string itself, or point diff --git a/pkgs/clan-cli/clan_cli/ssh/deploy_info.py b/pkgs/clan-cli/clan_cli/ssh/deploy_info.py index b8f19aea2..3c67f27e6 100644 --- a/pkgs/clan-cli/clan_cli/ssh/deploy_info.py +++ b/pkgs/clan-cli/clan_cli/ssh/deploy_info.py @@ -8,7 +8,12 @@ from typing import Any from clan_cli.async_run import AsyncRuntime from clan_cli.cmd import run +from clan_cli.completions import ( + add_dynamic_completer, + complete_machines, +) from clan_cli.errors import ClanError +from clan_cli.machines.machines import Machine from clan_cli.nix import nix_shell from clan_cli.ssh.host import Host, is_ssh_reachable from clan_cli.ssh.host_key import HostKeyCheck @@ -24,6 +29,11 @@ class DeployInfo: tor: str | None = None pwd: str | None = None + @staticmethod + def from_hostname(hostname: str, args: argparse.Namespace) -> "DeployInfo": + m = Machine(hostname, flake=args.flake) + return DeployInfo(addrs=[m.target_host_address]) + @staticmethod def from_json(data: dict[str, Any]) -> "DeployInfo": return DeployInfo( @@ -101,6 +111,8 @@ def ssh_command_parse(args: argparse.Namespace) -> DeployInfo | None: return DeployInfo.from_json(data) if args.png: return parse_qr_code(Path(args.png)) + if hasattr(args, "machines"): + return DeployInfo.from_hostname(args.machines[0], args) return None @@ -108,7 +120,7 @@ def ssh_command(args: argparse.Namespace) -> None: host_key_check = HostKeyCheck.from_str(args.host_key_check) deploy_info = ssh_command_parse(args) if not deploy_info: - msg = "No --json or --png data provided" + msg = "No MACHINE, --json or --png data provided" raise ClanError(msg) with AsyncRuntime() as runtime: @@ -117,6 +129,16 @@ def ssh_command(args: argparse.Namespace) -> None: def register_parser(parser: argparse.ArgumentParser) -> None: group = parser.add_mutually_exclusive_group(required=True) + machines_parser = group.add_argument( + "machines", + type=str, + nargs="*", + default=[], + metavar="MACHINE", + help="Machine to ssh into.", + ) + add_dynamic_completer(machines_parser, complete_machines) + group.add_argument( "-j", "--json",