{ "meta": { "title": "Arquitetura do ProxMenux Monitor — AppImage, Flask, SQLite, WebSocket | ProxMenux", "description": "Construção do ProxMenux Monitor: estrutura AppImage, blueprints Flask, tarefas de fundo, dados de psutil, pvesh, smartctl e journalctl, armazenamento SQLite, terminal WebSocket, fornecedores IA, canais de notificação, proxy inverso e Fail2Ban opcional.", "ogTitle": "Arquitetura ProxMenux Monitor", "ogDescription": "Dentro do ProxMenux Monitor: AppImage, blueprints Flask, tarefas de fundo, SQLite, WebSocket, fornecedores IA e canais de notificação.", "twitterTitle": "Arquitetura ProxMenux Monitor", "twitterDescription": "AppImage, Flask, SQLite, WebSocket, fornecedores de IA e canais de notificação — dentro do Monitor." }, "header": { "title": "Arquitetura", "description": "Como se distribui o ProxMenux Monitor, o que contém o AppImage e como os pedidos do navegador passam pelo Flask até às ferramentas do anfitrião e ao armazenamento SQLite.", "section": "ProxMenux Monitor" }, "intro": { "title": "Um processo, muitas responsabilidades", "body": "Um processo Python escuta em TCP 8008. Serve a versão estática Next.js, disponibiliza a API REST, gere o terminal WebSocket, executa Health Monitor e envia notificações. Não exige servidor web separado, intermediário de mensagens ou base de dados externa." }, "requestFlow": { "heading": "Percurso de um pedido", "intro": "Um pedido de dados típico segue este percurso; as preferências do navegador podem ser tratadas localmente:", "diagramCaption": "As rotas protegidas validam JWT com autenticação ativa. Rotas principais e blueprints devolvem dados em cache ou acabados de recolher. A verificação Fail2Ban opcional consulta a lista de bloqueios em cache. Cabeçalhos IP reencaminhados só são aceites com PROXMENUX_TRUST_PROXY=1; caso contrário, usa-se o endereço da ligação. O estado persistente inclui SQLite e ficheiros JSON como auth.json.", "diagramArrowLabel": "HTTP / WS", "nodes": { "clientLabel": "Cliente", "clientDetail": "Navegador ou PWA\n+ proxy opcional\nNginx / Caddy /\nTraefik", "flaskLabel": "Flask :8008", "flaskDetail": "Blueprints\nMiddleware JWT\nVerificação Fail2Ban\n(se instalado)", "hostLabel": "Ferramentas do anfitrião", "hostDetail": "psutil\npvesh\nsmartctl\njournalctl", "stateLabel": "Estado local", "stateDetail": "SQLite DB\n+ auth.json" }, "threadsIntro": "O processo executa também threads em segundo plano. Estes exemplos recolhem dados, atualizam caches e SQLite e enviam notificações. Existem outras tarefas; o envio depende de estar ativado:", "headerThread": "Thread", "headerCadence": "Cadência", "headerJob": "Tarefa agendada", "rows": [ { "thread": "_temperature_collector_loop", "cadence": "60 s", "job": "Guarda temperatura CPU e amostras de latência na base histórica, mantendo dados para os gráficos sem navegador ligado." }, { "thread": "_health_collector_loop", "cadence": "5 min", "job": "Executa o ciclo Health Monitor de dez categorias, guarda erros e observações de discos, mantém as dispensas e passa novos eventos ao motor de notificações." }, { "thread": "_vital_signs_sampler", "cadence": "CPU 5 s; temperatura 15 s; memória 30 s", "job": "Recolhe CPU, memória e temperatura CPU para indicadores e deteção de condições prolongadas, independentemente da análise geral de cinco minutos." }, { "thread": "notification_manager.start()", "cadence": "orientado por eventos", "job": "Inicia os observadores de journal, tarefas e hooks (JournalWatcher, TaskWatcher, ProxmoxHookWatcher) e envia para os canais configurados, com reformulação IA opcional." } ] }, "systemd": { "heading": "Unidade systemd", "intro": "O instalador cria a unidade /etc/systemd/system/proxmenux-monitor.service. Configuração predefinida:", "items": [ "User=root: o serviço usa privilégios root para SMART, pvesh e gestão ZFS. Os processos do terminal web herdam estas permissões.", "Restart=on-failure com espera de dez segundos: o serviço reinicia automaticamente após uma saída com erro.", "After=network.target: ordena o arranque após esse alvo; não garante IP atribuído, DNS ou ligação externa." ], "inspectTitle": "Consultar a unidade atual" }, "appimage": { "heading": "Qual a composição de AppImage", "intro": "O AppImage contém um sistema de ficheiros montado ao executar. AppRun na raiz prepara o ambiente e inicia flask_server.py:", "consequencesIntro": "Duas consequências deste layout:", "consequences": [ "Pacotes Python incluídos. Os pacotes Python vêm no AppImage e correm no interpretador Python do próprio anfitrião, 3.11 (PVE 8) ou 3.13 (PVE 9), sem instalar nada no Python do sistema do anfitrião. A aplicação continua a usar interfaces do kernel e ferramentas externas do anfitrião.", "Clientes de hardware incluídos. Contém ipmitool, lm-sensors e upsc. As leituras dependem do hardware compatível, acesso aos sensores e, para a UPS, de um servidor NUT configurado." ] }, "flask": { "heading": "Estrutura da aplicação Flask", "intro": "flask_server.py cria uma instância Flask(__name__) com origens CORS restritas, regista diretamente dez blueprints e inicia as rotas do terminal. A tabela destaca os módulos principais; também são registados os blueprints de PBS, ligações do Monitor, auditoria e cópias VM. As pré-cargas específicas de PVE só funcionam em anfitriões PVE.", "headerBlueprint": "Blueprint / módulo", "headerPrefix": "Prefixo das rotas", "headerOwns": "Responsabilidade", "rows": [ { "blueprint": "flask_server.py", "prefix": [ "/api/system", "/api/storage", "/api/network", "/api/vms", "/api/hardware", "/api/logs", "/api/prometheus" ], "owns": "Endpoints principais, web estática e verificação Fail2Ban opcional quando o jail proxmenux existe no anfitrião." }, { "blueprint": "flask_auth_routes.py", "prefix": [ "/api/auth/*" ], "owns": "Início de sessão, emissão JWT, configuração e verificação TOTP, alteração de palavra-passe e tokens API." }, { "blueprint": "flask_health_routes.py", "prefix": [ "/api/health/*" ], "owns": "Verificações de estado, detalhes, erros ativos e dispensados e definições de silenciamento. As rotas protegidas exigem autenticação quando ativa." }, { "blueprint": "flask_terminal_routes.py", "prefix": [ "/api/terminal/* + WS" ], "owns": "Atribui um PTY por sessão e liga-o por WebSocket ao xterm.js do navegador." }, { "blueprint": "flask_notification_routes.py", "prefix": [ "/api/notifications/*" ], "owns": "Criar, consultar, editar e remover canais; testes, IA, histórico e envios manuais." }, { "blueprint": "flask_security_routes.py", "prefix": [ "/api/security/*" ], "owns": "Falhas de acesso e, com Fail2Ban instalado, estado do jail, bloqueios e desbloqueio manual." }, { "blueprint": "flask_proxmenux_routes.py", "prefix": [ "/api/proxmenux/*" ], "owns": "Lê as otimizações ProxMenux registadas no anfitrião." }, { "blueprint": "flask_oci_routes.py", "prefix": [ "/api/oci/*" ], "owns": "Funções de implementação de aplicações OCI e contentores no Proxmox VE 9.1+." } ], "endpointsLink": "Os endpoints de integração e os formatos são descritos em Referência da API." }, "dataSources": { "heading": "Fontes de dados", "intro": "Os dados do anfitrião vêm sobretudo de bibliotecas padrão, ficheiros e comandos de administração. Alguns dados dos sistemas convidados dependem também do QEMU Guest Agent ou de comandos no CT:", "headerSource": "Origem", "headerUsedFor": "Usado para", "rows": [ { "source": "psutil", "usedFor": "Carga CPU, memória, swap, ocupação das montagens, contadores de rede e processos." }, { "source": "pvesh / qm / pct", "usedFor": "Informações do nó Proxmox, inventário e configuração VM/CT, armazenamento e histórico de tarefas." }, { "source": "smartctl", "usedFor": "Atributos SATA/NVMe, estado SMART, desgaste, vida útil, modelo e número de série." }, { "source": "zpool / zfs", "usedFor": "Estado do pool (ONLINE / DEGRADED / FAULTED / UNAVAIL), progresso do scrub e utilização dos datasets." }, { "source": "journalctl", "usedFor": "Registos do sistema, processos terminados por OOM, erros ATA/NVMe/dm, segurança e unidades de serviço próprias." }, { "source": "ip / iproute2", "usedFor": "Interfaces, endereços, bridges, bonds e dispositivos geridos por OVS." }, { "source": "nvidia-smi · intel_gpu_top", "usedFor": "Dados GPU disponíveis de utilização, VRAM, temperatura e codificação/descodificação. Os campos dependem do fabricante, ferramenta e controlador." }, { "source": "lspci · lscpu · dmidecode", "usedFor": "Topologia PCIe, modelo e topologia CPU, placa principal e BIOS." }, { "source": "ipmitool · sensors", "usedFor": "Sensores fora de banda, ventoinhas e temperaturas da placa, quando o hardware permite." }, { "source": "upsc (NUT)", "usedFor": "Estado da bateria, carga e autonomia UPS com um servidor NUT configurado." } ], "cacheTitle": "Cache para reduzir consultas ao anfitrião", "cacheBody": "Os resultados dispendiosos de smartctl -a e pvesh get ficam temporariamente no processo Flask para reduzir consultas aos discos e à API do cluster. A duração depende da fonte: segundos para métricas e minutos para SMART." }, "persistence": { "heading": "Persistência", "intro": "Vários ficheiros de estado têm conteúdos e requisitos de acesso diferentes:", "headerPath": "Path", "headerOwner": "Proprietário", "headerContents": "Conteúdo", "rows": [ { "path": "/usr/local/share/proxmenux/health_monitor.db", "owner": "root:root", "contents": "Base SQLite. Tabelas: errors, events, disk_registry, disk_observations, user_settings, notification_history, excluded_storages, excluded_interfaces. Registo em modo WAL." }, { "path": "/usr/local/share/proxmenux/.notification_key", "owner": "root 0600", "contents": "Chave principal de 32 bytes para segredos de notificação ENC2. Usa salt aleatório por valor, chaves derivadas e MAC. Os valores XOR antigos são suportados para migração." }, { "path": "/root/.config/proxmenux-monitor/auth.json", "owner": "root:root", "contents": "Configuração de acesso: ativação, utilizador, hash PBKDF2-HMAC-SHA256 da palavra-passe com salt, segredo TOTP, códigos de recuperação, chave JWT da instalação, metadados dos tokens API e hashes de revogação. Hashes SHA-256 antigos migram após um início de sessão bem-sucedido." }, { "path": "/var/log/proxmenux-auth.log", "owner": "root:root", "contents": "Registo de autenticação em texto simples, independente do Fail2Ban. Se o jail [proxmenux] estiver instalado e ativo, lê o ficheiro para detetar falhas repetidas. Sem esse jail, o registo continua disponível para consulta." } ], "backupTitle": "Guardar auth.json antes de reinstalar", "backupBody": "Substituir o AppImage normalmente preserva /root/.config/proxmenux-monitor/auth.json e /usr/local/share/proxmenux/health_monitor.db. Guarde auth.json com jwt_secret para manter validade e metadados dos tokens. A base guarda separadamente estado e notificações. Guarde .notification_key com ela para restaurar segredos dos canais; a base isolada não restaura a autenticação API." }, "health": { "heading": "Ciclo Health Monitor", "intro": "Aproximadamente a cada cinco minutos, o processo de fundo chama health_monitor.py para verificar as dez categorias do painel:", "items": [ "Serviços PVE críticos (pveproxy, pvedaemon, pvestatd, pve-cluster).", "Armazenamentos Proxmox: pvesh get /storage e disponibilidade de cada um.", "Discos e sistemas de ficheiros: SMART, erros de E/S no dmesg, estado ZFS e capacidade das montagens.", "VM e CT: falhas de arranque, paragens inesperadas, erros QMP e falhas de encerramento.", "Rede: estado de bridges e bonds, ligações e latência até ao gateway.", "Atualizações: pacotes pendentes e correções de segurança.", "Registos: padrões persistentes, picos e cascatas no journal do sistema.", "Memória: intervenções do OOM killer e pressão RAM prolongada.", "CPU: utilização e temperatura da CPU face aos limites configuráveis; os sensores disponíveis dependem do hardware.", "Segurança: falhas de acesso, bloqueios e estado do jail Fail2Ban." ], "afterIntro": "Os resultados usam error_key, categoria e gravidade estáveis. Os registos existentes em errors são atualizados no local (last_seen, gravidade, motivo), sem duplicação. O envio de notificações tem também regras próprias de impressões e intervalos.", "cycleEnd": "A limpeza pode resolver erros antigos com a duração de silenciamento por categoria e regras de estado, retirar registos órfãos de recursos removidos e apagar events com mais de 30 dias. As categorias estão em Painel → Health Monitor." }, "notifications": { "heading": "Motor de notificação", "intro": "notification_manager.py coordena o envio: carrega canais, gere a fila e disponibiliza uma API Python para Flask e Health Monitor e uma entrada CLI para os hooks .sh do ProxMenux.", "items": [ "Os observadores geram eventos: JournalWatcher acompanha o journal, TaskWatcher consulta tarefas Proxmox, ProxmoxHookWatcher recebe hooks de cópias, replicação e snapshots e PollingCollector gere fontes lentas.", "Os modelos convertem eventos em título e texto. O fornecedor IA configurado (OpenAI / Anthropic / Gemini / Groq / Ollama / OpenRouter) pode reformulá-los em linguagem natural. Ambas as versões ficam em notification_history.", "Os canais enviam por serviços como Telegram, Discord, email, Gotify, Pushover e Apprise. As implementações em notification_channels.py partilham create_channel() / send().", "Cifragem. As definições sensíveis (telegram.bot_token, discord.webhook_url, pushover.user_key, pushover.api_token, ai_api_key_*, email.password) são cifradas com a chave de .notification_key antes de guardar e apresentadas de forma mascarada." ], "linksFooter": "Controlos por evento, opções dos canais e IA são descritos em Definições → Notificações e Definições → Assistente de IA." }, "websocket": { "heading": "Terminal WebSocket", "intro": "A aba Terminal liga um cliente leve xterm.js a um PTY do servidor por WebSocket. Existem dois modos:", "items": [ "Modo HTTP, predefinido: o servidor Flask incluído e flask-sock tratam a mudança de protocolo para WebSocket.", "Modo HTTPS / WSS: o servidor TLS usa gevent.pywsgi.WSGIServer com o seu handler predefinido; flask-sock trata as ligações WebSocket sobre TLS." ], "outro": "Os processos filhos do terminal herdam User=root do serviço. Abrir um WebSocket exige um ticket breve de uso único obtido pelo endpoint autenticado de tickets do terminal com permissões administrativas.", "proxyNote": "Num proxy inverso, permita WebSocket e os cabeçalhos Upgrade e Connection. O terminal precisa desse encaminhamento." }, "proxy": { "heading": "Proxy inverso e Fail2Ban", "intro": "A escolha do IP do cliente e o bloqueio opcional na aplicação dependem destas definições e componentes:", "items": [ "Escolha do IP do cliente. A verificação before_request e o registo de acesso só aceitam X-Forwarded-For, depois X-Real-IP, com PROXMENUX_TRUST_PROXY=1. Caso contrário, usam request.remote_addr. Cabeçalhos reencaminhados não são aceites automaticamente.", "Fail2Ban opcional na aplicação. Uma ligação proxy pode apresentar o IP do proxy como origem TCP. Com o IP fiável do cliente e o jail proxmenux ativo, Flask rejeita IP bloqueados com HTTP 403. A lista fica em cache por 30 segundos e renova-se ao processar pedidos após expirar, sem temporizador dedicado." ], "calloutTitle": "Fail2Ban não está incluído", "calloutBody": "Fail2Ban não está incluído nem se instala automaticamente. Pode ser instalado em Segurança do Monitor ou Segurança → Fail2Ban no ProxMenux Scripts. Sem fail2ban-client ou o jail proxmenux, esta verificação não bloqueia pedidos. A autenticação segue a política configurada.", "outro": "Os exemplos Nginx/Caddy/Traefik e o guia do jail estão em Acesso e autenticação e Segurança → Fail2Ban." }, "whereNext": { "heading": "Para continuar", "items": [ { "label": "Acesso e autenticação", "href": "/docs/monitor/access-auth", "tail": " — primeiro arranque, palavra-passe e TOTP 2FA, exemplos de proxy inverso e jail Fail2Ban." }, { "label": "Referência da API", "href": "/docs/monitor/api", "tail": " — endpoints de integração, gestão de tokens e convenções de autenticação." }, { "label": "Configurações → ProxMenux Monitor", "href": "/docs/settings/proxmenux-monitor", "tail": " — controlo do serviço e verificação do estado no menu de texto ProxMenux." } ] } }