""" Server store — CRUD + JSON persistence + observer pattern. """ import json import os import shutil from typing import Callable, Optional # Shared config — same file used by ssh.py and Claude Code /ssh skill SHARED_DIR = os.path.expanduser("~/.server-connections") SERVERS_FILE = os.path.join(SHARED_DIR, "servers.json") # Fallback: local config dir (for example file) LOCAL_CONFIG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config") EXAMPLE_FILE = os.path.join(LOCAL_CONFIG_DIR, "servers.example.json") SERVER_TYPES = ["ssh", "telnet", "rdp", "mariadb", "mssql", "postgresql"] DEFAULT_PORTS = { "ssh": 22, "telnet": 23, "rdp": 3389, "mariadb": 3306, "mssql": 1433, "postgresql": 5432, } class ServerStore: def __init__(self): self._data: dict = {"servers": [], "ssh_key": {"type": "ed25519", "path": "~/.ssh/id_ed25519"}} self._observers: list[Callable] = [] self._statuses: dict[str, str] = {} # alias -> "online" | "offline" | "unknown" self._load() def _load(self): if os.path.exists(SERVERS_FILE): with open(SERVERS_FILE, "r", encoding="utf-8") as f: self._data = json.load(f) elif os.path.exists(EXAMPLE_FILE): with open(EXAMPLE_FILE, "r", encoding="utf-8") as f: self._data = json.load(f) self._save() def _save(self): os.makedirs(SHARED_DIR, exist_ok=True) with open(SERVERS_FILE, "w", encoding="utf-8") as f: json.dump(self._data, f, indent=2, ensure_ascii=False) def _notify(self): for cb in self._observers: try: cb() except Exception: pass def subscribe(self, callback: Callable): self._observers.append(callback) def get_all(self) -> list[dict]: return list(self._data.get("servers", [])) def get_server(self, alias: str) -> Optional[dict]: for s in self._data.get("servers", []): if s["alias"] == alias: return dict(s) return None def add_server(self, server: dict): if self.get_server(server["alias"]): raise ValueError(f"Server '{server['alias']}' already exists") self._data.setdefault("servers", []).append(server) self._save() self._notify() def update_server(self, alias: str, updated: dict): servers = self._data.get("servers", []) for i, s in enumerate(servers): if s["alias"] == alias: servers[i] = updated self._save() self._notify() return raise ValueError(f"Server '{alias}' not found") def remove_server(self, alias: str): self._data["servers"] = [s for s in self._data.get("servers", []) if s["alias"] != alias] self._statuses.pop(alias, None) self._save() self._notify() def get_ssh_key_path(self) -> str: path = self._data.get("ssh_key", {}).get("path", "~/.ssh/id_ed25519") return os.path.expanduser(path) # Status management def set_status(self, alias: str, status: str): self._statuses[alias] = status def get_status(self, alias: str) -> str: return self._statuses.get(alias, "unknown")