"""Predefined device/VM slots available when configuring a vessel. Each catalog entry defines defaults (port, MCP, type). Per-vessel overrides are stored on the Device row when the operator enables that slot. """ from __future__ import annotations from dataclasses import dataclass from app.models.enums import DeviceType from app.agent.asterisk_profiles import asterisk_container_name @dataclass(frozen=True) class CatalogEntry: key: str label: str description: str device_type: DeviceType mcp_server: str | None default_port: int default_username: str | None = None category: str = "core" # core | network | optional # GeneseasX + common homelab/vessel gear VESSEL_DEVICE_CATALOG: tuple[CatalogEntry, ...] = ( CatalogEntry( key="proxmox", label="Proxmox host", description="Proxmox VE — API token (proxmox-mcp) with SSH fallback", device_type=DeviceType.proxmox, mcp_server="proxmox-mcp", default_port=22, default_username="root", category="core", ), CatalogEntry( key="pfsense", label="pfSense firewall", description="pfSense REST API (pfSense package)", device_type=DeviceType.pfsense, mcp_server="pfsense-mcp", default_port=40443, category="core", ), CatalogEntry( key="docker_vm", label="Docker VM (GeneseasX)", description="Debian Docker VM hosting GeneseasX services", device_type=DeviceType.geneseasx, mcp_server="ssh-generic-mcp", default_port=22, default_username="root", category="core", ), CatalogEntry( key="asterisk_geneseasx", label="Asterisk VoIP (GeneseasX)", description="Asterisk in Docker container `voip` on the GeneseasX Docker VM (SSH + docker exec)", device_type=DeviceType.asterisk, mcp_server="ssh-generic-mcp", default_port=5022, default_username="root", category="core", ), CatalogEntry( key="asterisk_satbox", label="Asterisk VoIP (TMGeneseas / satbox)", description="Asterisk installed natively on Debian 9/10 host (SSH + asterisk CLI)", device_type=DeviceType.asterisk, mcp_server="ssh-generic-mcp", default_port=22, default_username="root", category="core", ), CatalogEntry( key="debian_host", label="Plain Debian server", description="Standalone Debian 9/10 host (SSH)", device_type=DeviceType.debian, mcp_server="ssh-generic-mcp", default_port=22, default_username="root", category="optional", ), CatalogEntry( key="fortigate", label="FortiGate", description="Standalone FortiGate (FortiOS REST API)", device_type=DeviceType.fortigate, mcp_server="fortigate-mcp", default_port=443, category="network", ), CatalogEntry( key="fortiswitch", label="FortiSwitch", description="FortiSwitch management API", device_type=DeviceType.fortiswitch, mcp_server="fortiswitch-mcp", default_port=443, category="network", ), CatalogEntry( key="tplink", label="TP-Link device", description="TP-Link switch/AP (SSH; MCP pending)", device_type=DeviceType.tplink, mcp_server=None, default_port=22, category="optional", ), ) def catalog_by_key() -> dict[str, CatalogEntry]: return {e.key: e for e in VESSEL_DEVICE_CATALOG} def catalog_as_dicts() -> list[dict]: from app.services.device_defaults import proxmox_api_configured, resolve_proxmox_api_defaults, resolve_slot_defaults rows: list[dict] = [] for e in VESSEL_DEVICE_CATALOG: env = resolve_slot_defaults(e.key, e) row = { "key": e.key, "label": e.label, "description": e.description, "device_type": e.device_type.value, "mcp_server": e.mcp_server, "default_port": env.port if env.port is not None else e.default_port, "default_username": env.username or e.default_username, "has_default_secret": bool(env.secret), "secret_kind": env.secret_kind, "env_defaults_configured": env.from_env, "asterisk_container": asterisk_container_name(e.key) or None, "category": e.category, } if e.key == "proxmox": api = resolve_proxmox_api_defaults("") row["has_default_api"] = proxmox_api_configured(api) rows.append(row) return rows