| 1 | """SSH/SFTP connection management for wulFTP.""" |
| 2 | |
| 3 | import os |
| 4 | from dataclasses import dataclass |
| 5 | from pathlib import Path |
| 6 | from typing import Dict, Optional |
| 7 | |
| 8 | import paramiko |
| 9 | from paramiko import SSHConfig |
| 10 | from dotenv import load_dotenv |
| 11 | |
| 12 | # Load environment variables |
| 13 | load_dotenv() |
| 14 | |
| 15 | |
| 16 | @dataclass |
| 17 | class SSHHost: |
| 18 | """Represents an SSH host configuration.""" |
| 19 | alias: str |
| 20 | hostname: str |
| 21 | port: int = 22 |
| 22 | user: Optional[str] = None |
| 23 | key_file: Optional[str] = None |
| 24 | |
| 25 | def display_name(self) -> str: |
| 26 | """Get display name for UI.""" |
| 27 | if self.user: |
| 28 | return f"{self.alias} ({self.user}@{self.hostname})" |
| 29 | return f"{self.alias} ({self.hostname})" |
| 30 | |
| 31 | |
| 32 | def load_ssh_config() -> Dict[str, SSHHost]: |
| 33 | """Load SSH hosts from ~/.ssh/config and environment.""" |
| 34 | hosts = {} |
| 35 | config_path = Path.home() / ".ssh" / "config" |
| 36 | |
| 37 | if config_path.exists(): |
| 38 | config = SSHConfig() |
| 39 | with open(config_path) as f: |
| 40 | config.parse(f) |
| 41 | |
| 42 | for host in config.get_hostnames(): |
| 43 | if host == "*": |
| 44 | continue |
| 45 | |
| 46 | cfg = config.lookup(host) |
| 47 | hosts[host] = SSHHost( |
| 48 | alias=host, |
| 49 | hostname=cfg.get("hostname", host), |
| 50 | port=int(cfg.get("port", 22)), |
| 51 | user=cfg.get("user"), |
| 52 | key_file=cfg.get("identityfile", [None])[0], |
| 53 | ) |
| 54 | |
| 55 | # Add custom hosts from environment |
| 56 | if os.getenv("WULFTP_HOST"): |
| 57 | hosts["env_default"] = SSHHost( |
| 58 | alias="Default", |
| 59 | hostname=os.getenv("WULFTP_HOST"), |
| 60 | port=int(os.getenv("WULFTP_PORT", "22")), |
| 61 | user=os.getenv("WULFTP_USER"), |
| 62 | key_file=os.getenv("WULFTP_KEY"), |
| 63 | ) |
| 64 | |
| 65 | return hosts |