mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-06-01 13:04:42 +00:00
5ca3463bf6
Full rewrite of the docs site under app/[locale]/ with next-intl in localePrefix:"always" mode. Every page now exists at both /en/<path> and /es/<path>; the root / shows a meta-refresh + JS redirect to /<defaultLocale>/ so GitHub Pages serves something on the apex URL. Highlights: - 107 doc pages migrated to file-per-page JSON namespaces under messages/en/ and messages/es/. Spanish content is fully translated (no copy-of-English placeholders). - New documentation for the Active Suppressions section in the Settings tab and the per-event Dismiss dropdown in the Health Monitor modal. - New screenshots: dismiss-duration-dropdown.png and an updated health-suppression-settings.png. - Pagefind integrated for client-side search; index is built on every CI deploy (not committed). - RSS feeds: per-locale at /<locale>/rss.xml plus root /rss.xml for backward compat. - Removed the dead app/[locale]/guides/[slug]/ route — every guide now has its own static page and no markdown source remains. - Fixed orphan link /guides/nvidia -> /guides/nvidia-manual in docs/hardware/nvidia-host. - Removed obsolete components (footer2, calendar, drawer). Verified locally with `npm ci && npm run build`: 2804 files in out/, 231 pages indexed by pagefind, root redirect intact, both locale roots and the new Active Suppressions docs render OK.
304 lines
20 KiB
JSON
304 lines
20 KiB
JSON
{
|
|
"meta": {
|
|
"title": "Arquitectura de ProxMenux Monitor — AppImage, Flask, SQLite, WebSocket | ProxMenux",
|
|
"description": "Cómo está construido ProxMenux Monitor: estructura del AppImage, blueprints de Flask, hilos de fondo, fuentes de datos (psutil, pvesh, smartctl, journalctl), persistencia en SQLite, terminal WebSocket, proveedores de IA, canales de notificación, reverse proxy e integración opcional con Fail2Ban.",
|
|
"ogTitle": "Arquitectura de ProxMenux Monitor",
|
|
"ogDescription": "Dentro de ProxMenux Monitor — estructura del AppImage, blueprints de Flask, hilos de fondo, SQLite, WebSocket, proveedores de IA, canales de notificación.",
|
|
"twitterTitle": "Arquitectura de ProxMenux Monitor",
|
|
"twitterDescription": "AppImage, Flask, SQLite, WebSocket, proveedores de IA y canales de notificación — por dentro del Monitor."
|
|
},
|
|
"header": {
|
|
"title": "Arquitectura",
|
|
"description": "Cómo se empaqueta ProxMenux Monitor, qué corre dentro del AppImage y cómo fluyen las peticiones desde el navegador a través del backend Flask hasta las herramientas del host y el almacén SQLite.",
|
|
"section": "ProxMenux Monitor"
|
|
},
|
|
"intro": {
|
|
"title": "Un proceso, muchas responsabilidades",
|
|
"body": "Un único proceso Python escucha en el puerto TCP 8008. Sirve el build estático de Next.js, expone la API REST, gestiona el terminal WebSocket, ejecuta el Monitor de salud periódico y despacha notificaciones. No hay un servidor web aparte, ni un broker de mensajes, ni una base de datos externa."
|
|
},
|
|
"requestFlow": {
|
|
"heading": "Flujo de petición",
|
|
"intro": "Del navegador al kernel, cada vista del panel sigue el mismo camino:",
|
|
"diagramCaption": "Cada petición se autentica por JWT (cuando la autenticación está activada), se enruta a un blueprint y se responde con datos recogidos bajo demanda desde las herramientas del host. Si Fail2Ban está instalado y el jail proxmenux está activo, el middleware también comprueba la petición contra la lista de IPs baneadas del jail. El reverse proxy opcional es transparente para Flask — reenvía las cabeceras X-Forwarded-* y la app recupera la IP real del cliente a partir de ellas. El estado que necesita sobrevivir a una petición vive en SQLite.",
|
|
"diagramArrowLabel": "HTTP / WS",
|
|
"nodes": {
|
|
"clientLabel": "Cliente",
|
|
"clientDetail": "Navegador o PWA\n+ proxy opcional\nNginx / Caddy /\nTraefik",
|
|
"flaskLabel": "Flask :8008",
|
|
"flaskDetail": "Blueprints\nMiddleware JWT\nHook Fail2Ban\n(si está instalado)",
|
|
"hostLabel": "Herramientas del host",
|
|
"hostDetail": "psutil\npvesh\nsmartctl\njournalctl",
|
|
"stateLabel": "Estado local",
|
|
"stateDetail": "DB SQLite\n+ auth.json"
|
|
},
|
|
"threadsIntro": "El mismo proceso también ejecuta cuatro <strong>hilos de fondo</strong> arrancados al boot — no sirven HTTP, empujan estado a SQLite o a la cola de notificaciones mientras el host está activo:",
|
|
"headerThread": "Hilo",
|
|
"headerCadence": "Cadencia",
|
|
"headerJob": "Tarea",
|
|
"rows": [
|
|
{
|
|
"thread": "_temperature_collector_loop",
|
|
"cadence": "60 s",
|
|
"job": "Registra la temperatura de la CPU y una muestra de latencia de red en la DB de historial para que las gráficas del panel tengan datos incluso cuando ningún cliente está conectado."
|
|
},
|
|
{
|
|
"thread": "_health_collector_loop",
|
|
"cadence": "5 min",
|
|
"job": "Ejecuta el ciclo completo del Monitor de salud (10 categorías), persiste errores activos, dismissals y observaciones de disco, y alimenta nuevos eventos al motor de notificaciones."
|
|
},
|
|
{
|
|
"thread": "_vital_signs_sampler",
|
|
"cadence": "~1 s",
|
|
"job": "Muestreador de alta frecuencia de CPU + temperatura usado por los widgets en vivo del panel Overview."
|
|
},
|
|
{
|
|
"thread": "notification_manager.start()",
|
|
"cadence": "dirigido por eventos",
|
|
"job": "Lanza los watchers de journal / task / hook (<code>JournalWatcher</code>, <code>TaskWatcher</code>, <code>ProxmoxHookWatcher</code>) y despacha a los canales configurados con reescritura opcional con IA."
|
|
}
|
|
]
|
|
},
|
|
"systemd": {
|
|
"heading": "Unidad systemd",
|
|
"intro": "El instalador deja una unidad en <code>/etc/systemd/system/proxmenux-monitor.service</code>. Contenido por defecto:",
|
|
"items": [
|
|
"<strong><code>User=root</code></strong> — obligatorio: SMART, <code>pvesh</code>, scopes de journal, comandos ZFS y el terminal web requieren todos root.",
|
|
"<strong><code>Restart=on-failure</code></strong> con back-off de 10 segundos — las salidas con código distinto de cero relanzan automáticamente.",
|
|
"<strong><code>After=network.target</code></strong> — espera a que el stack de red del host esté online."
|
|
],
|
|
"inspectTitle": "Inspeccionar la unidad en vivo"
|
|
},
|
|
"appimage": {
|
|
"heading": "Qué contiene el AppImage",
|
|
"intro": "El AppImage es un sistema de archivos automontable. <code>AppRun</code> en la raíz prepara el entorno y ejecuta <code>flask_server.py</code>:",
|
|
"consequencesIntro": "Dos consecuencias de esta estructura:",
|
|
"consequences": [
|
|
"<strong>Sin polución del Python del host.</strong> El intérprete y los paquetes empaquetados están aislados dentro del AppImage — actualizar el Python del sistema host no afecta al Monitor y viceversa.",
|
|
"<strong>Las herramientas de hardware también vienen incluidas.</strong> <code>ipmitool</code>, <code>lm-sensors</code> y <code>upsc</code> viajan dentro del AppImage para que el panel pueda leer sensores fuera de banda y el estado de la UPS sin forzar al usuario a instalar paquetes Debian."
|
|
]
|
|
},
|
|
"flask": {
|
|
"heading": "Estructura de la app Flask",
|
|
"intro": "<code>flask_server.py</code> crea una única instancia <code>Flask(__name__)</code>, habilita CORS y registra seis blueprints más un inicializador de WebSocket:",
|
|
"headerBlueprint": "Blueprint / módulo",
|
|
"headerPrefix": "Prefijo de rutas",
|
|
"headerOwns": "Responsable de",
|
|
"rows": [
|
|
{
|
|
"blueprint": "flask_server.py",
|
|
"prefix": [
|
|
"/api/system",
|
|
"/api/storage",
|
|
"/api/network",
|
|
"/api/vms",
|
|
"/api/hardware",
|
|
"/api/logs",
|
|
"/api/prometheus"
|
|
],
|
|
"owns": "Endpoints de datos principales + servir el panel estático + comprobación opcional de Fail2Ban a nivel de aplicación (activa solo cuando Fail2Ban está instalado en el host con el jail <code>proxmenux</code>)."
|
|
},
|
|
{
|
|
"blueprint": "flask_auth_routes.py",
|
|
"prefix": [
|
|
"/api/auth/*"
|
|
],
|
|
"owns": "Login, emisión de JWT, configuración / verificación de TOTP, cambio de contraseña, generación de API tokens."
|
|
},
|
|
{
|
|
"blueprint": "flask_health_routes.py",
|
|
"prefix": [
|
|
"/api/health/*"
|
|
],
|
|
"owns": "Probe de salud público, estado detallado, errores activos / dismissed, ajustes de supresión."
|
|
},
|
|
{
|
|
"blueprint": "flask_terminal_routes.py",
|
|
"prefix": [
|
|
"/api/terminal/* + WS"
|
|
],
|
|
"owns": "Asignación de PTY por sesión y pipe WebSocket hacia <code>xterm.js</code> en el navegador."
|
|
},
|
|
{
|
|
"blueprint": "flask_notification_routes.py",
|
|
"prefix": [
|
|
"/api/notifications/*"
|
|
],
|
|
"owns": "CRUD de canales, envío de prueba, configuración del proveedor de IA, historial, envíos manuales."
|
|
},
|
|
{
|
|
"blueprint": "flask_security_routes.py",
|
|
"prefix": [
|
|
"/api/security/*"
|
|
],
|
|
"owns": "Fallos de autenticación y, cuando Fail2Ban está instalado, estado del jail, eventos de ban y desbaneo manual."
|
|
},
|
|
{
|
|
"blueprint": "flask_proxmenux_routes.py",
|
|
"prefix": [
|
|
"/api/proxmenux/*"
|
|
],
|
|
"owns": "Lee qué optimizaciones post-instalación de ProxMenux están instaladas en el host."
|
|
},
|
|
{
|
|
"blueprint": "flask_oci_routes.py",
|
|
"prefix": [
|
|
"/api/oci/*"
|
|
],
|
|
"owns": "Ayudantes de despliegue de apps OCI / container (Proxmox VE 9.1+)."
|
|
}
|
|
],
|
|
"endpointsLink": "La lista completa de endpoints con la forma de petición / respuesta está en <link>API Reference</link>."
|
|
},
|
|
"dataSources": {
|
|
"heading": "Fuentes de datos",
|
|
"intro": "Nada se recoge a través de un agente propio — el Monitor lee los mismos archivos y ejecuta los mismos comandos que un administrador humano:",
|
|
"headerSource": "Fuente",
|
|
"headerUsedFor": "Usado para",
|
|
"rows": [
|
|
{
|
|
"source": "psutil",
|
|
"usedFor": "Carga de CPU, memoria, swap, uso de puntos de montaje, contadores de NIC, lista de procesos."
|
|
},
|
|
{
|
|
"source": "pvesh / qm / pct",
|
|
"usedFor": "Información del nodo Proxmox, inventario y configuración de VMs y CTs, pools de almacenamiento, historial de tareas."
|
|
},
|
|
{
|
|
"source": "smartctl",
|
|
"usedFor": "Atributos SATA / NVMe, salud SMART, desgaste / vida útil, modelo y número de serie."
|
|
},
|
|
{
|
|
"source": "zpool / zfs",
|
|
"usedFor": "Estado del pool (ONLINE / DEGRADED / FAULTED / UNAVAIL), progreso de scrub, uso de datasets."
|
|
},
|
|
{
|
|
"source": "journalctl",
|
|
"usedFor": "Logs del sistema, OOM kills, errores ATA / NVMe / dm, eventos de seguridad, unidades de servicio propias."
|
|
},
|
|
{
|
|
"source": "ip / iproute2",
|
|
"usedFor": "Interfaces, direcciones, bridges, bonds, dispositivos gestionados por OVS."
|
|
},
|
|
{
|
|
"source": "nvidia-smi · intel_gpu_top",
|
|
"usedFor": "Utilización de GPU, VRAM, temperatura, carga de encoder / decoder."
|
|
},
|
|
{
|
|
"source": "lspci · lscpu · dmidecode",
|
|
"usedFor": "Topología PCIe, modelo y topología de CPU, información de placa y BIOS."
|
|
},
|
|
{
|
|
"source": "ipmitool · sensors",
|
|
"usedFor": "Sensores fuera de banda, velocidades de ventilador, temperaturas de placa (cuando son compatibles)."
|
|
},
|
|
{
|
|
"source": "upsc (NUT)",
|
|
"usedFor": "Estado de batería de UPS, carga, autonomía — cuando hay un servidor NUT configurado en el host."
|
|
}
|
|
],
|
|
"cacheTitle": "La salida se cachea — no toda petición llega al host",
|
|
"cacheBody": "Las fuentes costosas (<code>smartctl -a</code>, <code>pvesh get</code>) se envuelven en cachés temporizadas dentro del proceso Flask para que una pestaña ocupada del panel no martillee el disco o la API del cluster. Los TTLs de la caché están ajustados por fuente (unos pocos segundos para métricas en vivo, varios minutos para SMART)."
|
|
},
|
|
"persistence": {
|
|
"heading": "Persistencia",
|
|
"intro": "Dos ubicaciones del sistema de archivos separan el estado por sensibilidad:",
|
|
"headerPath": "Ruta",
|
|
"headerOwner": "Propietario",
|
|
"headerContents": "Contenido",
|
|
"rows": [
|
|
{
|
|
"path": "/usr/local/share/proxmenux/health_monitor.db",
|
|
"owner": "root:root",
|
|
"contents": "DB SQLite. Tablas: <code>errors</code>, <code>events</code>, <code>disk_registry</code>, <code>disk_observations</code>, <code>user_settings</code>, <code>notification_history</code>, <code>excluded_storages</code>, <code>excluded_interfaces</code>. Modo journal WAL."
|
|
},
|
|
{
|
|
"path": "/usr/local/share/proxmenux/.notification_key",
|
|
"owner": "root <code>0600</code>",
|
|
"contents": "Clave XOR de 32 bytes usada para cifrar ajustes sensibles de notificaciones antes de guardarlos en la DB (tokens de Telegram, API keys de IA, etc.)."
|
|
},
|
|
{
|
|
"path": "/root/.config/proxmenux-monitor/auth.json",
|
|
"owner": "root:root",
|
|
"contents": "Estado de autenticación: flag de activación, nombre de usuario, hash SHA-256 de la contraseña, secret TOTP, códigos de backup, lista de API tokens emitidos, lista de hashes de tokens revocados."
|
|
},
|
|
{
|
|
"path": "/var/log/proxmenux-auth.log",
|
|
"owner": "root:root",
|
|
"contents": "Log de eventos de autenticación en texto plano. Siempre se escribe. Si Fail2Ban está instalado con el jail <code>[proxmenux]</code>, el jail lee este archivo para banear intentos de fuerza bruta; si no, el archivo simplemente acumula las entradas del log."
|
|
}
|
|
],
|
|
"backupTitle": "Haz backup de auth.json antes de reinstalar",
|
|
"backupBody": "Reinstalar el AppImage reemplaza el binario pero deja <code>/root/.config/proxmenux-monitor/auth.json</code> y <code>/usr/local/share/proxmenux/health_monitor.db</code> intactos. Si restauras desde un backup del host, mantén ambos archivos juntos — los API tokens guardados en <code>auth.json</code> se validan contra <code>JWT_SECRET</code>; si la DB y auth.json se desincronizan, los errores dismissed y los tokens guardados pueden comportarse mal."
|
|
},
|
|
"health": {
|
|
"heading": "Ciclo del Monitor de salud",
|
|
"intro": "Cada 5 minutos <code>health_monitor.py</code> ejecuta un ciclo determinista a través de las diez categorías mostradas en el panel:",
|
|
"items": [
|
|
"Servicios críticos de PVE (<code>pveproxy</code>, <code>pvedaemon</code>, <code>pvestatd</code>, <code>pve-cluster</code>).",
|
|
"Pools de almacenamiento Proxmox (<code>pvesh get /storage</code> + disponibilidad por almacenamiento).",
|
|
"Discos y sistemas de archivos: SMART, errores de I/O en dmesg, salud de pool ZFS, capacidad de puntos de montaje.",
|
|
"VMs y CTs: arranques fallidos, guests caídos, errores QMP, fallos de apagado.",
|
|
"Red: estado de bridge / bond, estado de enlace, latencia al gateway.",
|
|
"Actualizaciones: actualizaciones de paquetes pendientes y parches de seguridad.",
|
|
"Logs: detección de patrones persistentes / spike / cascada en el journal del sistema.",
|
|
"Memoria: actividad del OOM killer, presión alta sostenida.",
|
|
"Temperatura: sensores CPU / chasis contra umbrales del fabricante.",
|
|
"Seguridad: fallos de autenticación, eventos de ban, estado del jail fail2ban."
|
|
],
|
|
"afterIntro": "Cada hallazgo se normaliza a un <code>error_key</code> + categoría + severidad estable. La capa de persistencia deduplica contra la tabla <code>errors</code> existente — los eventos repetidos actualizan <code>last_seen</code> y el contador de ocurrencias sin spamear notificaciones.",
|
|
"cycleEnd": "El ciclo también auto-resuelve errores obsoletos usando el ajuste de <em>Suppression Duration</em> por categoría, limpia errores de recursos que ya no existen (VMs eliminadas / discos retirados / almacenamientos desmontados) y poda el log <code>events</code> con más de 30 días. El catálogo completo de categorías y la vista del panel que las expone está documentado en <link>Dashboard → Monitor de salud</link>."
|
|
},
|
|
"notifications": {
|
|
"heading": "Motor de notificaciones",
|
|
"intro": "<code>notification_manager.py</code> es el orquestador. Carga los canales configurados, posee la cola de entrega y expone tanto una API Python (para rutas Flask y el ciclo del Monitor de salud) como un punto de entrada CLI (para los scripts <code>.sh</code> de hooks que vienen con ProxMenux).",
|
|
"items": [
|
|
"<strong>Los watchers</strong> empujan eventos: <code>JournalWatcher</code> sigue el journal del sistema, <code>TaskWatcher</code> hace polling de la lista de tareas Proxmox, <code>ProxmoxHookWatcher</code> reacciona a hooks de backup / replicación / snapshot y <code>PollingCollector</code> gestiona fuentes de datos lentas.",
|
|
"<strong>Las templates</strong> convierten un evento en un par (título, cuerpo). La misma template puede pasar por el proveedor de IA configurado (OpenAI / Anthropic / Gemini / Groq / Ollama / OpenRouter) para producir una reescritura en lenguaje natural; ambas versiones se guardan en <code>notification_history</code>.",
|
|
"<strong>Los canales</strong> entregan los mensajes: Telegram, Discord, Email, Gotify y Apprise (multicanal). Cada uno está implementado en <code>notification_channels.py</code> detrás de la misma interfaz <code>create_channel()</code> / <code>send()</code>, así que añadir un canal nuevo es una sola clase.",
|
|
"<strong>Cifrado.</strong> Los ajustes sensibles (<code>telegram.token</code>, <code>discord.webhook_url</code>, <code>ai_api_key_*</code>, <code>email.password</code>) se cifran con XOR usando la clave en <code>.notification_key</code> antes de escribirse en la DB. El texto plano nunca toca disco."
|
|
],
|
|
"linksFooter": "Los toggles por evento, los overrides por canal y la configuración de IA se exponen en <notifLink>Settings → Notifications</notifLink> y <aiLink>Settings → AI Assistant</aiLink>."
|
|
},
|
|
"websocket": {
|
|
"heading": "Terminal WebSocket",
|
|
"intro": "La pestaña <em>Terminal</em> del panel es un cliente fino <code>xterm.js</code> cableado a un PTY del lado servidor a través de un WebSocket. Dos modos de transporte:",
|
|
"items": [
|
|
"<strong>Modo HTTP (por defecto):</strong> el servidor de desarrollo de Flask con <code>flask-sock</code> gestiona las peticiones de upgrade. Suficiente para LAN / acceso directo.",
|
|
"<strong>Modo HTTPS / WSS:</strong> cuando hay un certificado SSL configurado, el proceso cambia a <code>gevent.pywsgi.WSGIServer</code> con <code>geventwebsocket.handler.WebSocketHandler</code>, para que los WebSockets funcionen sobre TLS sin polyfills."
|
|
],
|
|
"outro": "El PTY es un hijo del proceso Flask, así que hereda <code>User=root</code> de la unidad. Cada petición de terminal pasa por auth JWT; el usuario debe estar ya logueado en el panel antes de que se asigne un PTY.",
|
|
"proxyNote": "Si accedes al Monitor a través de un reverse proxy, asegúrate de habilitar el reenvío de WebSocket (cabeceras <code>Upgrade</code> y <code>Connection</code>). Sin eso, el terminal no funcionará."
|
|
},
|
|
"proxy": {
|
|
"heading": "Reverse proxy y Fail2Ban",
|
|
"intro": "Dos salvaguardas se aseguran de que la seguridad funcione igual tanto si el panel se accede directamente como a través de un reverse proxy:",
|
|
"items": [
|
|
"<strong>Recuperación de la IP real del cliente.</strong> Un hook <code>before_request</code> lee <code>X-Forwarded-For</code> y <code>X-Real-IP</code> en ese orden, cayendo a <code>request.remote_addr</code>. La dirección recuperada es la que ven el log de autenticación y el rate limiting. Esto está siempre activo.",
|
|
"<strong>Comprobación de Fail2Ban a nivel de aplicación (opcional).</strong> Cuando el panel está detrás de un proxy, el firewall del kernel no puede bloquear la IP real del atacante — la conexión siempre viene del proxy. Para tapar ese hueco, el mismo hook de arriba consulta el jail <code>proxmenux</code> de Fail2Ban cada 30 segundos, cachea el conjunto de IPs baneadas y corta las peticiones desde esas IPs con HTTP 403 dentro de Flask."
|
|
],
|
|
"calloutTitle": "Fail2Ban no viene incluido",
|
|
"calloutBody": "Fail2Ban <strong>no</strong> lo instala ProxMenux Monitor por sí mismo. La comprobación a nivel de aplicación es un no-op hasta que instales Fail2Ban en el host (p. ej. vía <link>Seguridad → Fail2Ban</link> en el menú de ProxMenux). Cuando el binario <code>fail2ban-client</code> o el jail <code>proxmenux</code> está ausente, la llamada falla silenciosamente y las peticiones no se filtran — la autenticación sigue aplicándose, pero no hay baneo a nivel de IP.",
|
|
"outro": "Los snippets de reverse proxy (Nginx / Caddy / Traefik) y el walkthrough del jail Fail2Ban están en <accessLink>Access & Authentication</accessLink> y <fail2banLink>Seguridad → Fail2Ban</fail2banLink>."
|
|
},
|
|
"whereNext": {
|
|
"heading": "Por dónde seguir",
|
|
"items": [
|
|
{
|
|
"label": "Access & Authentication",
|
|
"href": "/docs/monitor/access-auth",
|
|
"tail": " — configuración de primer arranque, contraseña + TOTP 2FA, snippets de reverse proxy, jail Fail2Ban."
|
|
},
|
|
{
|
|
"label": "API Reference",
|
|
"href": "/docs/monitor/api",
|
|
"tail": " — cada endpoint, gestión de tokens, mejores prácticas de seguridad."
|
|
},
|
|
{
|
|
"label": "Settings → ProxMenux Monitor",
|
|
"href": "/docs/settings/proxmenux-monitor",
|
|
"tail": " — el toggle del servicio dentro del menú y el flujo de verificación del estado dentro de la TUI de ProxMenux."
|
|
}
|
|
]
|
|
}
|
|
}
|