# Spec 035 — Ligbox Watchman (vigilância contínua + alertas) **Criado:** 2026-06-22 **Solicitado por:** Roger **Status:** 📋 Draft — aguarda implementação Fase 1 **Prioridade:** **P0** (incidente Desk 502 sem alerta — 2026-06-22) **Sistema:** VM122 (processo standalone) + monorepo `ligbox-ops-platform` **Relacionado:** Spec **033** (stack health UI) · Spec **002** (Wazuh) · Spec **007** (push) · VM115 ntfy --- ## Resumo **Ligbox Watchman** é um processo **leve, contínuo e independente** do Desk API que monitoriza **todos** os serviços do stack Ligbox (VMs 112, 114, 122, 123, 130 e extensões), regista estado, e **alerta** quando um serviço deixa de responder — **antes** de um utilizador reportar “a página não abre”. **Motivação (incidente real):** `desk.ligbox.com.br` devolveu **HTTP 502** porque `ligbox-ops-platform_frontend_1` e `api_1` estavam **Exited** na VM122 após reboot. O Traefik estava OK; o **INFRA CODE** do Desk não alertou porque depende da própria API. **Princípio:** Watchman **não morre** quando o Desk morre. --- ## Problema | Lacuna actual | Impacto | |---------------|---------| | `stack_health.py` só via API Desk | Sem alerta se API/UI caírem | | Sem processo 24/7 dedicado | Falhas após reboot passam despercebidas | | Docker sem `restart: unless-stopped` | Containers ficam parados (exit 255) | | Wazuh (VM104) | SIEM — não substitui uptime sintético por URL | --- ## Decisões de arquitectura | # | Tema | Decisão | |---|------|---------| | 1 | Onde corre | **VM122** — host com LAN para 112/114/123/130; processo **fora** do container `api` | | 2 | Catálogo | **Fonte única** YAML `contracts/stack-services.yaml`; Desk importa o mesmo catálogo (refactor Spec 033) | | 3 | Intervalo | **Tier crítico** 30 s · **tier standard** 60 s · **tier slow** 120 s — não 1 s global (anti-ruído) | | 4 | Anti-flap | Alerta só após **N falhas consecutivas** (default **2**) | | 5 | Recovery | Notificação **RESOLVED** quando serviço volta OK após alerta | | 6 | Estado | SQLite local `watchman.db` (~MB) — histórico 7 dias | | 7 | Alertas | **ntfy** (primário) + **email** Postfix (secundário) + webhook Desk (opcional) | | 8 | Recursos | 1 processo Python, `asyncio` + pool HTTP, RAM alvo **< 64 MB** | | 9 | Deploy | `systemd` `ligbox-watchman.service` + opcional container Docker dedicado | | 10 | RBAC | Sem UI própria v1 — leitura via ficheiro status JSON + integração futura Desk | --- ## Arquitectura ```mermaid flowchart LR subgraph VM122 W[watchman.py systemd] DB[(watchman.db SQLite)] W --> DB end subgraph LAN VM112[VM112] VM114[CT114] VM123[VM123] VM130[CT130] end W -->|HTTP/TCP probes| VM112 W -->|HTTP/TCP probes| VM114 W -->|HTTP/TCP probes| VM123 W -->|HTTP/TCP probes| VM130 W -->|HTTPS WAN| desk.ligbox.com.br W -->|POST| ntfy[VM115 ntfy] W -->|SMTP| postfix[Postfix Proxmox] W -.->|opcional| desk_hook[Desk webhook] ``` --- ## Catálogo de serviços (v1) Herda Spec 033 (`stack_health.py`) — **~22 probes** em 5 VMs: | VM | Serviços | |----|----------| | **112** | Onboard API, Wizard UI, Carbonio, Domain Admin API | | **114** | Traefik API, Router Desk WAN, Router API Ops WAN | | **122** | Desk API LAN, Desk UI :8091, Redis, integrações locais | | **123** | FOSSBilling, Odoo, OpenPanel, OpenAdmin, Bridge :18087, Ops Console, phpMyAdmin, Ollama | | **130** | Forgejo, Spec Portal LAN, Spec Hub WAN | **Fase 2 (extensão catálogo):** VM104 Wazuh, CT107 Fluxus, VM116 RustDesk, VM124 Nextcloud, pfSense, Proxmox host ping. Schema: [contracts/stack-services.yaml](./contracts/stack-services.yaml). --- ## Tiers e intervalos | Tier | Intervalo | Exemplos | |------|-----------|----------| | `critical` | 30 s | `desk.ligbox.com.br`, `api.ops.ligbox.com.br`, Traefik, Desk API/UI LAN | | `standard` | 60 s | Onboard, OpenPanel, Forgejo, FOSSBilling | | `slow` | 120 s | Ollama, phpMyAdmin, Carbonio HTTPS LAN | Cada ciclo do Watchman executa apenas probes **due** (scheduler por `next_check_at`), não todos de uma vez cada segundo. --- ## Alertas ### Canais (ordem de envio) 1. **ntfy** — `https://ntfy.ligbox.com.br/ligbox-watchman` (tópico configurável; auth opcional) 2. **Email** — `admin@ligbox.com.br` via SMTP LAN (Postfix host ou VM122) 3. **Desk** (opcional) — `POST` evento interno para tab Eventos ### Formato ntfy (DOWN) ``` Title: 🔴 DOWN vm122-desk-ui Body: Desk Frontend — HTTP timeout (3 failures) Tags: warning,skull Priority: high ``` ### Formato ntfy (RESOLVED) ``` Title: 🟢 OK vm122-desk-ui Body: Desk Frontend — recovered after 19m down Tags: white_check_mark Priority: default ``` ### Anti-spam - Máximo **1 alerta DOWN** por serviço por janela de **15 min** (repeat throttle) - RESOLVED sempre enviado uma vez por incidente --- ## Complemento Docker (VM122) Independentemente do Watchman, aplicar na **produção** Desk: ```yaml restart: unless-stopped ``` em `frontend`, `api`, `worker`, `redis` no `docker-compose.mvp.yml` — previne classe de incidente 2026-06-22. --- ## Estrutura no monorepo ``` projects/integrations/watchman/ watchman.py # daemon principal probes.py # HTTP, TCP, Redis, Docker (opcional) catalog.py # load YAML alerts.py # ntfy, email, desk webhook store.py # SQLite state requirements.txt README.md contracts/stack-services.yaml # catálogo partilhado (fonte de verdade) deploy/watchman/ install.sh # systemd + venv ligbox-watchman.service watchman.env.example docker-compose.watchman.yml # alternativa container specs/035-ligbox-watchman/ spec.md tasks.md quickstart.md contracts/ ``` **Refactor Spec 033:** `stack_health.py` passa a ler `contracts/stack-services.yaml` + registry de probe handlers (evita duplicação). --- ## API / ficheiros de status (v1) | Artefacto | Path | Descrição | |-----------|------|-----------| | Status JSON | `/var/lib/ligbox-watchman/status.json` | Snapshot último ciclo (para Desk/nginx) | | SQLite | `/var/lib/ligbox-watchman/watchman.db` | Histórico incidentes | | Log | `journalctl -u ligbox-watchman` | stdout estruturado | Formato `status.json`: ver [contracts/status-snapshot.md](./contracts/status-snapshot.md). **Fase 2:** `GET /api/v1/watchman/status` no Desk (read-only proxy do JSON). --- ## RBAC e segurança | Item | Regra | |------|-------| | Credenciais ntfy/SMTP | `watchman.env` — não commitar | | Probes WAN | só URLs públicas; LAN sem secrets em logs | | OPENPANEL_BRIDGE_TOKEN | herdado de env VM122 (mesmo que Desk) | | Acesso status.json | `root:ligbox-watchman` ou leitura Desk via grupo | --- ## Fases de implementação ### Fase 0 — Mitigação imediata (sem Watchman) - [ ] `restart: unless-stopped` em `docker-compose.mvp.yml` produção VM122 - [ ] Documentar no quickstart ### Fase 1 — Watchman MVP - [ ] `contracts/stack-services.yaml` extraído de `stack_health.py` - [ ] `watchman.py` + systemd na VM122 - [ ] Alertas ntfy - [ ] SQLite + anti-flap - [ ] `status.json` para consulta ### Fase 2 — Integração Desk + email - [ ] Refactor `stack_health.py` → YAML - [ ] Email Postfix - [ ] Card “Watchman” em INFRA CODE (último alerta) - [ ] Catálogo VM104, 107, 116, 124 ### Fase 3 — Auto-remediação (opcional) - [ ] Probe `docker` local: se `frontend`/`api` down → `docker start` (com confirmação Roger) - [ ] Integração Agentic Ops (Spec 029) — ticket automático --- ## Critérios de aceitação (Fase 1) 1. Parar `ligbox-ops-platform_frontend_1` → alerta ntfy em **< 2 min** (2×30 s critical). 2. Subir frontend → alerta **RESOLVED** em **< 2 min**. 3. Parar Desk API → Watchman **continua** a alertar (processo systemd independente). 4. `status.json` actualizado a cada ciclo; `summary.degraded` correcto. 5. RAM do processo **< 64 MB** em steady state (medido 1 h). 6. Catálogo YAML versionado no Forgejo; alteração de URL = PR no repo. --- ## Fora de escopo (v1) - UI web própria do Watchman (usar ntfy + Desk INFRA) - Prometheus/Grafana (peso excessivo para P0) - Monitorização Proxmox host metrics (Fase 2+) - Substituição do Wazuh --- ## Documentos relacionados | Documento | Conteúdo | |-----------|----------| | [quickstart.md](./quickstart.md) | Instalação VM122 | | [tasks.md](./tasks.md) | Checklist | | [contracts/stack-services.yaml](./contracts/stack-services.yaml) | Catálogo probes | | [contracts/status-snapshot.md](./contracts/status-snapshot.md) | Schema JSON | | `specs/033-desk-infra-console-ui/spec.md` | UI stack health | | `projects/ops-desk/api/app/stack_health.py` | Implementação actual (a migrar) | --- ## Incidente de referência **2026-06-22:** `https://desk.ligbox.com.br/` → HTTP **502** (Traefik OK, backend VM122:8091 down). Containers `Exited (255)` ~19 h. Causa provável: reboot VM sem restart Docker. **Watchman teria alertado em < 1 min** (tier critical).