Initial commit: Agentic OS troubleshooting platform

Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend +
LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini ->
DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials,
RBAC, audit log, project-memory + Obsidian integrations, and editable
troubleshooting decision rules tuned for the GeneseasX vessel stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 22:11:07 +03:00
commit 6185b9b85a
126 changed files with 14565 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
"""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