From ffb1f7e20407cce006649c83810dea6694134a51 Mon Sep 17 00:00:00 2001 From: Arctic Date: Sat, 8 Mar 2025 00:43:48 -0600 Subject: [PATCH] refactor --- add_host.py | 252 ++++++++++++++++++++++---------- cli.py | 121 ++++++++++------ config.py | 46 +++++- list_hosts.py | 141 ++++++++++-------- regen_key.py | 388 +++++++++++++++++++++++++++++++++----------------- 5 files changed, 636 insertions(+), 312 deletions(-) diff --git a/add_host.py b/add_host.py index 38e25eb..0b7ef3c 100644 --- a/add_host.py +++ b/add_host.py @@ -2,109 +2,211 @@ import os import subprocess +import glob +from pathlib import Path +from typing import Optional, List, Dict, Set, Tuple +from dataclasses import dataclass from .utils import print_error, print_warning, print_info, safe_input +from .config import CONF_DIR -def add_host(conf_dir): +@dataclass +class HostConfig: + label: str + hostname: str + user: str = "root" + port: str = "22" + identity_file: str = "" + + def to_config_lines(self) -> List[str]: + """Convert host configuration to SSH config file lines.""" + lines = [ + f"Host {self.label}", + f" HostName {self.hostname}", + f" User {self.user}", + f" Port {self.port}" + ] + if self.identity_file: + lines.append(f" IdentityFile {self.identity_file}") + return lines + +def get_existing_hosts(conf_dir: str) -> Tuple[Set[str], Dict[str, str]]: + """ + Get existing host labels and hostnames from config files. + Returns (host_labels, hostname_to_label) where: + - host_labels is a set of existing host labels + - hostname_to_label maps hostnames to their labels + """ + host_labels = set() + hostname_to_label = {} + + pattern = os.path.join(conf_dir, "*", "config") + for config_file in glob.glob(pattern): + try: + with open(config_file, 'r') as f: + lines = f.readlines() + + current_label = None + for line in lines: + line = line.strip() + if not line or line.startswith('#'): + continue + + if line.lower().startswith('host '): + labels = line.split()[1:] + for label in labels: + if '*' not in label: + current_label = label + host_labels.add(label) + break + elif current_label and line.lower().startswith('hostname '): + hostname = line.split(None, 1)[1].strip() + hostname_to_label[hostname] = current_label + + except Exception as e: + print_warning(f"Error reading config file {config_file}: {e}") + continue + + return host_labels, hostname_to_label + +def generate_ssh_key(key_path: Path) -> bool: + """Generate a new ED25519 SSH key pair.""" + try: + subprocess.check_call([ + "ssh-keygen", + "-q", + "-t", "ed25519", + "-N", "", + "-f", str(key_path) + ]) + print_info(f"Generated new SSH key at {key_path}") + return True + except subprocess.CalledProcessError as e: + print_error(f"Error generating SSH key: {e}") + return False + +def copy_ssh_key(key_path: Path, user: str, hostname: str, port: str) -> bool: + """Copy SSH public key to remote server.""" + try: + cmd = ["ssh-copy-id", "-i", str(key_path)] + if port != "22": + cmd.extend(["-p", port]) + cmd.append(f"{user}@{hostname}") + + subprocess.check_call(cmd) + print_info("Key successfully copied to remote server.") + return True + except subprocess.CalledProcessError as e: + print_error(f"Error copying key to server: {e}") + return False + +def write_config_file(config_path: Path, config_lines: List[str]) -> bool: + """Write SSH config lines to file.""" + try: + config_path.write_text("\n".join(config_lines) + "\n") + print_info(f"Created/updated config at: {config_path}") + return True + except Exception as e: + print_error(f"Failed to write config to {config_path}: {e}") + return False + +def add_host(conf_dir: str) -> bool: """ Interactive prompt to create a new SSH host in ~/.ssh/conf/