151 lines
6.2 KiB
Markdown
151 lines
6.2 KiB
Markdown
# Implementation Plan: Webhook VM112 → Ops Platform
|
||
|
||
**Branch**: `001-webhook-vm112-integration` | **Date**: 2026-06-08 | **Spec**: [spec.md](./spec.md)
|
||
|
||
**Input**: Feature specification from `/specs/001-webhook-vm112-integration/spec.md`
|
||
|
||
## Summary
|
||
|
||
Implementar integração LAN entre o portal de onboarding VM112 (`ibytera-mail-portal`) e a API Ops VM122 (`ligbox-ops-platform`). O portal emite webhooks autenticados após marcos do onboarding; o Ops regista eventos, cria tickets automaticamente e garante idempotência. Falhas de webhook são não-bloqueantes para o cliente.
|
||
|
||
**Abordagem**: módulo `ops_webhook` no portal (httpx + retry), extensão mínima do receptor existente em VM122 (idempotência + índice), secret partilhado via `.env` em ambas as VMs.
|
||
|
||
## Technical Context
|
||
|
||
**Language/Version**: Python 3.11+ (portal VM112 Ubuntu 24.04; Ops VM122 Debian 12)
|
||
|
||
**Primary Dependencies**: FastAPI, httpx, pydantic-settings (portal); FastAPI, httpx, redis, sqlite3 (Ops — já deployados)
|
||
|
||
**Storage**: SQLite `ops.db` (VM122) — tabelas `webhook_events`, `tickets` existentes; sem alteração de schema obrigatória (índice lógico para idempotência)
|
||
|
||
**Testing**: curl manual + script `scripts/verify-webhook.sh`; teste portal com Ops offline
|
||
|
||
**Target Platform**: VM112 `10.10.10.112:8090` → VM122 `10.10.10.122:8080` (LAN only)
|
||
|
||
**Project Type**: Integração cross-VM (dois repositórios/deploy paths)
|
||
|
||
**Performance Goals**: Webhook entrega < 5s p95; não adicionar > 500ms ao tempo de resposta do portal
|
||
|
||
**Constraints**: LAN-only; secret em header `X-Webhook-Secret`; fail2ban inalterado; onboarding nunca bloqueado por falha Ops
|
||
|
||
**Scale/Scope**: ~10–50 onboardings/dia; 4 tipos de evento MVP + 4 fase 2
|
||
|
||
## Constitution Check
|
||
|
||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||
|
||
| Princípio | Status | Notas |
|
||
|-----------|--------|-------|
|
||
| I. vmbr1 / LAN | ✅ PASS | Tráfego 112→122 na LAN `10.10.10.0/24` via vmbr4000 |
|
||
| II. Interfaces Proxmox | ✅ PASS | Nenhuma alteração de rede |
|
||
| III. Anti-scan Hetzner | ✅ PASS | Sem novas regras NAT/iptables |
|
||
| IV. Mail vs Ops separation | ✅ PASS | Portal emite; Ops recebe — sem mail stack em 122 |
|
||
| V. fail2ban | ✅ PASS | Sem alteração SSH |
|
||
| VI. pfSense API | N/A | Não usado nesta feature |
|
||
| VII. Spec-Driven | ✅ PASS | spec → plan em curso |
|
||
| VIII. Documentation | ✅ PASS | Artefactos em `specs/001-*` |
|
||
| IX. YAGNI | ✅ PASS | Sem novos serviços; extensão mínima |
|
||
|
||
**Post-design re-check**: ✅ Nenhuma violação. Sem Complexity Tracking necessário.
|
||
|
||
## Project Structure
|
||
|
||
### Documentation (this feature)
|
||
|
||
```text
|
||
specs/001-webhook-vm112-integration/
|
||
├── spec.md
|
||
├── plan.md # este ficheiro
|
||
├── research.md
|
||
├── data-model.md
|
||
├── quickstart.md
|
||
├── contracts/
|
||
│ └── webhook-onboard.md
|
||
├── checklists/
|
||
│ └── requirements.md
|
||
└── tasks.md # gerado por /speckit-tasks
|
||
```
|
||
|
||
### Source Code (deploy targets)
|
||
|
||
```text
|
||
# VM112 — /opt/ibytera-mail-portal/ (sync desde obsidian-infra/carbonio/ibytera-mail-portal/)
|
||
backend/app/
|
||
├── config.py # + ops_webhook_url, ops_webhook_secret, ops_webhook_enabled
|
||
├── services/
|
||
│ └── ops_webhook.py # NOVO: emit_event(), retry logic
|
||
└── routers/
|
||
└── onboarding.py # chamar ops_webhook após account/create
|
||
|
||
# VM122 — /opt/ligbox-ops-platform/ (já deployado)
|
||
api/app/
|
||
└── main.py # + idempotência, índice dedup, log melhorado
|
||
```
|
||
|
||
**Structure Decision**: alterações mínimas em dois deploy paths existentes; código fonte versionado em `obsidian-infra` (portal) e `workspace/projects/ligbox-ops-platform` (ops).
|
||
|
||
## Phase 0: Research Summary
|
||
|
||
Ver [research.md](./research.md) — conclusões:
|
||
|
||
1. Endpoint receptor MVP já funcional (`POST /api/v1/webhooks/onboard`)
|
||
2. `session_id` disponível via header `X-Onboarding-Session` / `request.state`
|
||
3. Portal não tem cliente webhook — criar `ops_webhook.py`
|
||
4. Idempotência: lookup `event+session_id+domain` antes de INSERT ticket
|
||
5. Secret dev `ligbox-ops-dev-secret` — rotacionar em produção
|
||
|
||
## Phase 1: Design Artifacts
|
||
|
||
| Artefacto | Ficheiro | Conteúdo |
|
||
|-----------|----------|----------|
|
||
| Data model | [data-model.md](./data-model.md) | Payload, entidades, dedup key |
|
||
| API contract | [contracts/webhook-onboard.md](./contracts/webhook-onboard.md) | Request/response, eventos |
|
||
| Quickstart | [quickstart.md](./quickstart.md) | Testes manuais e deploy |
|
||
|
||
## Implementation Phases
|
||
|
||
### Phase A — Ops receptor (VM122) — ~1h
|
||
|
||
1. Adicionar verificação idempotente em `webhook_onboard`
|
||
2. Query `webhook_events` por `(event_type, session_id, domain)` antes de criar ticket
|
||
3. Melhorar subject do ticket: `[account.created] dominio.com — admin@dominio.com`
|
||
4. Log estruturado em falha 401
|
||
|
||
### Phase B — Portal emissor (VM112) — ~2h
|
||
|
||
1. `config.py`: `ops_webhook_url`, `ops_webhook_secret`, `ops_webhook_enabled` (default true)
|
||
2. `services/ops_webhook.py`:
|
||
- `emit_event(event, domain, session_id, data, timeout=5)`
|
||
- Retry 3x: 1s, 3s, 9s backoff
|
||
- Header `X-Webhook-Secret`
|
||
- `activity_log.warn` em falha, nunca raise para o router
|
||
3. `onboarding.py` → `create_account`: após sucesso, chamar:
|
||
```python
|
||
ops_webhook.emit_event("account.created", domain, session_id, {...})
|
||
```
|
||
|
||
### Phase C — Config + validação — ~30min
|
||
|
||
1. `.env` VM112: `OPS_WEBHOOK_URL=http://10.10.10.122:8080/api/v1/webhooks/onboard`
|
||
2. `.env` VM122: confirmar `WEBHOOK_SECRET` igual
|
||
3. Script `scripts/verify-webhook.sh` no repo ops
|
||
4. Teste E2E: criar conta teste → ticket no desk
|
||
|
||
### Phase D — Eventos P3 (opcional, pós-MVP)
|
||
|
||
- `domain.validated` em `/validate-domain`
|
||
- `dns.applied` em `/cloudflare/apply`
|
||
- `onboarding.completed` / `onboarding.failed` nos respectivos pontos
|
||
|
||
## Risk & Mitigation
|
||
|
||
| Risco | Mitigação |
|
||
|-------|-----------|
|
||
| Ops offline durante onboarding | Retry + non-blocking; email admin continua |
|
||
| Secret exposto em log | Nunca logar secret; só "auth failed" |
|
||
| Tickets duplicados | Idempotência no receptor |
|
||
| Latência no portal | Fire-and-forget async (BackgroundTasks FastAPI) |
|
||
|
||
## Complexity Tracking
|
||
|
||
> Nenhuma violação da constitution — tabela vazia.
|