# 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 **&lt; 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 **&lt; 2 min** (2×30 s critical).
2. Subir frontend → alerta **RESOLVED** em **&lt; 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 **&lt; 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 &lt; 1 min** (tier critical).
