# ssh_manager/add_host.py 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 @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/