514 lines
17 KiB
Markdown
514 lines
17 KiB
Markdown
# SPEC — Integração Validação Admin Domínios Virtuais (Wizard VM112 + Desk VM122)
|
||
|
||
**Versão:** 1.0
|
||
**Data:** 2026-06-12
|
||
**Autor:** Roger / Cursor DevOps
|
||
**Estado:** 📋 **PLANEAMENTO** (correcção VM112 ✅ concluída)
|
||
**Depende de:** `SPEC-CORRECAO-ADMIN-DOMINIOS-VIRTUAIS.md`
|
||
|
||
---
|
||
|
||
## 1. Objectivo
|
||
|
||
Integrar a **validação e provisionamento admin Carbonio** (porta `:6071`, redirect `/admin`, certs SSL) no:
|
||
|
||
1. **Wizard VM112** (`/opt/ligbox-wizard/`) — onboarding automático por domínio virtual
|
||
2. **VM122 Ligbox Ops** (`ligbox-ops-platform`) — Desk, tickets, timeline, auditoria
|
||
|
||
**Problema actual:** validação admin existe em scripts offline (`admin-login-check/`) mas **não** no wizard API nem no Desk.
|
||
|
||
---
|
||
|
||
## 2. Contexto — dois conceitos de «admin»
|
||
|
||
| Conceito | URL | Onde vive hoje |
|
||
|----------|-----|----------------|
|
||
| **Painel delegado wizard** | `https://onboard.ligbox.com.br/admin` | SPA `DomainAdmin.jsx` + `/api/domain-admin/*` |
|
||
| **Carbonio Admin Console** | `https://mail.{dominio}/admin` → `:6071/static/login/` | nginx VM112 + Carbonio auth |
|
||
|
||
**Esta spec trata exclusivamente do Carbonio Admin Console por domínio virtual.**
|
||
|
||
---
|
||
|
||
## 3. Estado actual vs alvo
|
||
|
||
### 3.1 Wizard — `infrastructure.py` (6 checks hoje)
|
||
|
||
| ID actual | O que valida | Admin? |
|
||
|-----------|--------------|--------|
|
||
| `carbonio_domain` | zmprov domínio existe | ❌ |
|
||
| `dns_mail` | MX/A públicos | ❌ |
|
||
| `haproxy_sni` | SNI CT114 | ❌ |
|
||
| `traefik_router` | Router Traefik :443 | ❌ |
|
||
| `cert_san` | LE `mail-vm112-multi` (webmail) | ❌ (não :6071) |
|
||
| `webmail_https` | GET :443 | ❌ |
|
||
|
||
### 3.2 Novos checks admin (4 adicionais)
|
||
|
||
| ID novo | Label | O que valida | Severidade |
|
||
|---------|-------|--------------|------------|
|
||
| `admin_redirect_443` | Admin redirect :443 | `GET /admin` → 302 Location `:6071/static/login/` | **P1** |
|
||
| `admin_cert_6071` | Cert SSL admin :6071 | CN/SAN = `mail.{dominio}` no cert nginx :6071 | **P1** |
|
||
| `admin_login_6071` | Admin login :6071 | E4 `/zx/login/supported` JSON 200; E6 config OK | **P1** |
|
||
| `admin_block_nginx` | Bloco nginx admin | server block `:6071` existe (admin ou .custom) | **P2** |
|
||
|
||
### 3.3 Estado alvo — 10 checks infra
|
||
|
||
```
|
||
carbonio_domain → dns_mail → haproxy_sni → traefik_router → cert_san → webmail_https
|
||
→ admin_block_nginx → admin_redirect_443 → admin_cert_6071 → admin_login_6071
|
||
```
|
||
|
||
**`ready: true`** quando todos P1 OK (P2 = warning, não bloqueia onboarding).
|
||
|
||
---
|
||
|
||
## 4. Wizard VM112 — desenho técnico
|
||
|
||
### 4.1 Novo módulo Python
|
||
|
||
**Ficheiro:** `/opt/ligbox-wizard/backend/app/services/admin_domain_validation.py`
|
||
|
||
```python
|
||
# Funções principais (interface proposta)
|
||
def check_admin_redirect(mail_host: str) -> StepResult
|
||
def check_admin_cert_sni(mail_host: str) -> StepResult
|
||
def check_admin_login_flow(mail_host: str) -> StepResult
|
||
def check_admin_nginx_block(mail_host: str) -> StepResult
|
||
def provision_admin_infra(domain: str, mail_aliases: list[str] | None) -> ProvisionResult
|
||
```
|
||
|
||
**Implementação interna:**
|
||
|
||
| Check | Método |
|
||
|-------|--------|
|
||
| `admin_redirect_443` | `httpx` HEAD `https://{mail_host}/admin` → Location contém `:6071/static/login/` |
|
||
| `admin_cert_6071` | `openssl s_client -connect 127.0.0.1:6071 -servername {host}` ou leitura nginx ssl_certificate |
|
||
| `admin_login_6071` | Subprocess `node check-admin-login-flow.mjs --host {mail_host}` ou reimplementar E4/E6 em Python |
|
||
| `admin_block_nginx` | grep server block em `carbonio.admin` + `.custom` |
|
||
|
||
### 4.2 Alterações em `infrastructure.py`
|
||
|
||
**`get_status()`** — adicionar 4 steps após `webmail_https`:
|
||
|
||
```python
|
||
steps.append(check_admin_block_nginx(all_hosts))
|
||
steps.append(check_admin_redirect_443(all_hosts))
|
||
steps.append(check_admin_cert_6071(all_hosts))
|
||
steps.append(check_admin_login_6071(all_hosts))
|
||
```
|
||
|
||
**Payload de resposta (exemplo):**
|
||
|
||
```json
|
||
{
|
||
"domain": "iofficebooks.com",
|
||
"mail_host": "mail.iofficebooks.com",
|
||
"steps": [
|
||
{"id": "admin_redirect_443", "ok": true, "message": "302 → :6071/static/login/"},
|
||
{"id": "admin_cert_6071", "ok": true, "message": "CN=mail.iofficebooks.com"},
|
||
{"id": "admin_login_6071", "ok": true, "message": "E4 JSON 200, E6 domain OK"},
|
||
{"id": "admin_block_nginx", "ok": true, "message": "bloco :6071 presente"}
|
||
],
|
||
"admin_ready": true
|
||
}
|
||
```
|
||
|
||
**`provision()`** — novo step `admin_infra` após `cert_san`:
|
||
|
||
```python
|
||
def do_admin_infra() -> str:
|
||
# 1. sync-traefik-admin-certs.sh (se domínio usa cert Traefik)
|
||
# 2. apply-admin-nginx-overrides.py --reload
|
||
# 3. re-validar admin_redirect + admin_cert + admin_login
|
||
```
|
||
|
||
**Ordem provision completa:**
|
||
|
||
```
|
||
domain_site_layout → haproxy_sni → traefik_router → cert_san → admin_infra → (re-check all)
|
||
```
|
||
|
||
### 4.3 API routes (sem breaking changes)
|
||
|
||
| Método | Route | Alteração |
|
||
|--------|-------|-----------|
|
||
| GET | `/api/onboarding/infrastructure/status/{domain}` | +4 steps admin |
|
||
| POST | `/api/onboarding/infrastructure/provision?step=admin_infra` | Novo step |
|
||
| POST | `/api/onboarding/infrastructure/provision` | Inclui `admin_infra` no fluxo completo |
|
||
|
||
### 4.4 Frontend wizard (`App.jsx` / `WizardProcessHub`)
|
||
|
||
**UI — secção «Infraestrutura admin»** no painel de progresso:
|
||
|
||
| Step UI | Ícone OK | Ícone fail | Acção |
|
||
|---------|----------|------------|-------|
|
||
| Redirect `/admin` | ✅ | ⚠️ | «Abrir admin» link |
|
||
| Certificado :6071 | ✅ | ❌ | «Sincronizar cert» botão |
|
||
| Login admin | ✅ | ❌ | «Diagnosticar» → activity log |
|
||
| Bloco nginx | ✅ | ⚠️ | automático |
|
||
|
||
**Link pós-onboarding (Passo Concluído):**
|
||
|
||
```
|
||
Admin do domínio: https://mail.{dominio}/admin
|
||
(credenciais: admin@{dominio})
|
||
```
|
||
|
||
### 4.5 Activity log — mensagens
|
||
|
||
```
|
||
[infra] admin_redirect_443: OK — 302 → mail.iofficebooks.com:6071/static/login/
|
||
[infra] admin_cert_6071: FAIL — CN=mail.diarissima.com (esperado mail.iofficebooks.com)
|
||
[infra] admin_infra: sync Traefik certs + apply nginx overrides
|
||
[infra] admin_login_6071: OK — E4/E6 validados
|
||
```
|
||
|
||
### 4.6 Configuração (`.env` / `config.py`)
|
||
|
||
```python
|
||
# Novos settings
|
||
admin_check_scripts_dir: str = "/opt/ligbox-deploy/scripts/admin-login-check"
|
||
admin_nginx_apply_script: str = ".../apply-admin-nginx-overrides.py"
|
||
admin_traefik_sync_script: str = ".../sync-traefik-admin-certs.sh"
|
||
admin_canonical_url: str = "https://mail.ligbox.com.br:6071/static/login/"
|
||
```
|
||
|
||
### 4.7 Hook em `POST /account/create`
|
||
|
||
Após criar conta e auto-provision infra existente:
|
||
|
||
```python
|
||
# onboarding.py — após provision infra
|
||
infra = infrastructure.provision(domain, mail_aliases=aliases)
|
||
if not infra["status"].get("admin_ready"):
|
||
activity_log.warn(f"Admin infra incompleto: {domain}", source="infra")
|
||
# Não bloquear cliente — emitir webhook infra.partial
|
||
ops_webhook.emit("infra.synced", domain, session_id, data={
|
||
"steps": infra["status"]["steps"],
|
||
"admin_ready": infra["status"].get("admin_ready"),
|
||
"admin_url": f"https://mail.{domain}/admin",
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 5. VM122 Desk — desenho técnico
|
||
|
||
### 5.1 VM122 actual
|
||
|
||
| Componente | Path | Estado |
|
||
|------------|------|--------|
|
||
| API | `ligbox-ops-platform/api/app/main.py` | ✅ running |
|
||
| Desk UI | `frontend/` → `desk.ligbox.com.br` | ✅ |
|
||
| Webhook ingest | `POST /api/v1/webhooks/onboard` | ✅ |
|
||
| SQLite | tenants, tickets, webhook_events | ✅ |
|
||
|
||
### 5.2 Novos eventos webhook (extend contract 001 + 004)
|
||
|
||
| Event | Emissor | Quando | Cria ticket? |
|
||
|-------|---------|--------|--------------|
|
||
| `infra.synced` | VM112 wizard | Após provision infra completo | Nota em ticket existente |
|
||
| `admin.validation.failed` | VM112 wizard | admin_ready=false pós-provision | **Sim** (prioridade média) |
|
||
| `admin.cert.expiring` | VM122 cron/worker | Cert :6071 < 14 dias | **Sim** (prioridade baixa) |
|
||
|
||
**Payload `infra.synced` (extendido):**
|
||
|
||
```json
|
||
{
|
||
"event": "infra.synced",
|
||
"domain": "iofficebooks.com",
|
||
"session_id": "sess-abc123",
|
||
"data": {
|
||
"mail_host": "mail.iofficebooks.com",
|
||
"admin_ready": true,
|
||
"admin_url": "https://mail.iofficebooks.com/admin",
|
||
"admin_url_canonical": "https://mail.ligbox.com.br:6071/static/login/",
|
||
"steps": [
|
||
{"id": "admin_redirect_443", "ok": true},
|
||
{"id": "admin_cert_6071", "ok": true},
|
||
{"id": "admin_login_6071", "ok": true}
|
||
],
|
||
"provisioned_at": "2026-06-12T18:52:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Payload `admin.validation.failed`:**
|
||
|
||
```json
|
||
{
|
||
"event": "admin.validation.failed",
|
||
"domain": "betinplace.com",
|
||
"session_id": "sess-xyz",
|
||
"data": {
|
||
"failed_steps": ["admin_cert_6071"],
|
||
"messages": ["CN mismatch: expected mail.betinplace.com"],
|
||
"suggested_action": "Run sync-traefik-admin-certs.sh + apply-admin-nginx-overrides.py --reload"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 VM122 — alterações API
|
||
|
||
**Ficheiro:** `api/app/routers/webhooks.py` (ou equivalente)
|
||
|
||
```python
|
||
# Novos handlers
|
||
EVENT_HANDLERS = {
|
||
...
|
||
"infra.synced": handle_infra_synced, # update ticket + timeline
|
||
"admin.validation.failed": handle_admin_fail, # create ticket priority=medium
|
||
}
|
||
```
|
||
|
||
**Ficheiro:** `api/app/routers/desk.py` — extend ticket detail
|
||
|
||
```python
|
||
# GET /api/v1/desk/tickets/{id}
|
||
# + admin_status block quando domain conhecido:
|
||
{
|
||
"admin_status": {
|
||
"ready": true,
|
||
"url": "https://mail.iofficebooks.com/admin",
|
||
"last_check": "2026-06-12T18:52:00Z",
|
||
"failed_steps": []
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.4 VM122 — novo endpoint auditoria admin
|
||
|
||
```
|
||
GET /api/v1/infra/admin-status/{domain}
|
||
```
|
||
|
||
**Resposta:** proxy para VM112 `GET /api/onboarding/infrastructure/status/{domain}` filtrando steps admin, ou VM122 armazena último `infra.synced`.
|
||
|
||
**Uso Desk:** widget «Admin domínio» no detalhe do ticket.
|
||
|
||
### 5.5 VM122 Desk UI — telas
|
||
|
||
#### Tela 1: Ticket detalhe — secção «Admin domínio»
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ Admin Carbonio — iofficebooks.com │
|
||
├─────────────────────────────────────────────────┤
|
||
│ URL: https://mail.iofficebooks.com/admin [↗] │
|
||
│ Redirect :443 ✅ 302 → :6071 │
|
||
│ Cert :6071 ✅ CN=mail.iofficebooks.com │
|
||
│ Login E4/E6 ✅ │
|
||
│ Última validação: 2026-06-12 18:52 UTC │
|
||
│ [Re-validar agora] [Abrir admin] │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### Tela 2: Dashboard Ops — widget «Admin domínios»
|
||
|
||
| Domínio | Admin OK | Cert :6071 | Último check |
|
||
|---------|----------|------------|--------------|
|
||
| iofficebooks.com | ✅ | ✅ | 12/06 18:52 |
|
||
| betinplace.com | ✅ | ✅ | 12/06 18:52 |
|
||
| novo-cliente.com | ❌ | ❌ | — |
|
||
|
||
**Fonte:** agregação `webhook_events` tipo `infra.synced` + `admin.validation.failed`.
|
||
|
||
#### Tela 3: Runbook Desk — «Admin ERR_CERT»
|
||
|
||
Passos automáticos sugeridos (link para runbook):
|
||
|
||
1. Verificar Traefik tem cert para `mail.{dominio}`
|
||
2. Correr `sync-traefik-admin-certs.sh`
|
||
3. Correr `apply-admin-nginx-overrides.py --reload`
|
||
4. Re-validar via wizard ou script E1–E8
|
||
|
||
### 5.6 Processos Desk Support
|
||
|
||
#### Processo 1: Onboarding novo domínio (automático)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as Cliente
|
||
participant W as Wizard VM112
|
||
participant C114 as Traefik CT114
|
||
participant V112 as Carbonio VM112
|
||
participant O as Ops VM122 Desk
|
||
|
||
C->>W: validate-domain + create account
|
||
W->>C114: provision SNI + router + cert :443
|
||
W->>V112: cert_san + admin_infra
|
||
Note over V112: sync Traefik certs + nginx overrides
|
||
W->>W: validate admin_redirect + cert + login
|
||
alt admin_ready=true
|
||
W->>O: webhook infra.synced (admin_ready=true)
|
||
O->>O: timeline + nota ticket
|
||
else admin_ready=false
|
||
W->>O: webhook admin.validation.failed
|
||
O->>O: ticket prioridade média + runbook
|
||
end
|
||
W->>C: onboarding.completed + link /admin
|
||
```
|
||
|
||
#### Processo 2: Ticket manual «Admin não abre»
|
||
|
||
| Passo | Actor | Acção |
|
||
|-------|-------|-------|
|
||
| 1 | NOC | Cliente reporta ERR_CERT ou 504 em `mail.X/admin` |
|
||
| 2 | Desk | Abrir ticket; campo domínio preenchido |
|
||
| 3 | Desk | `GET /api/v1/infra/admin-status/{domain}` |
|
||
| 4 | Técnico | Se cert fail → sync Traefik + apply overrides |
|
||
| 5 | Técnico | Se redirect fail → apply-admin-nginx-overrides.py |
|
||
| 6 | Técnico | Se NAT fail → verificar pfSense :6071 → VM112 |
|
||
| 7 | Desk | Re-validar; fechar ticket com evidência curl |
|
||
|
||
#### Processo 3: Renovação cert (preventivo)
|
||
|
||
| Trigger | Acção |
|
||
|---------|-------|
|
||
| Traefik renova LE (CT114) | Cron VM112: `sync-traefik-admin-certs.sh` semanal |
|
||
| Cert < 14 dias | VM122 emite `admin.cert.expiring` → ticket low priority |
|
||
| zmproxyconfgen executado | **Obrigatório** `zmproxyconfgen-ligbox` |
|
||
|
||
#### Processo 4: Novo domínio virtual (pós-onboarding)
|
||
|
||
Quando ops adiciona domínio manualmente (`zmprov cd`):
|
||
|
||
1. Correr wizard `infrastructure/provision?domain=X` (ou script CLI)
|
||
2. Inclui step `admin_infra` automaticamente
|
||
3. Desk recebe `infra.synced` ou `admin.validation.failed`
|
||
|
||
---
|
||
|
||
## 6. Wire VM112 → VM122 (pré-requisitos)
|
||
|
||
### 6.1 Completar `ops_webhook.py` (VM112)
|
||
|
||
**Ficheiro:** `/opt/ligbox-wizard/backend/app/services/ops_webhook.py`
|
||
|
||
**Adicionar em `config.py`:**
|
||
|
||
```python
|
||
ops_webhook_enabled: bool = False
|
||
ops_webhook_url: str = "http://10.10.10.122:8080/api/v1/webhooks/onboard"
|
||
ops_webhook_secret: str = ""
|
||
```
|
||
|
||
**Montar em `main.py`:** routers `assist` se necessário.
|
||
|
||
### 6.2 Secret partilhado
|
||
|
||
VM112 `.env`:
|
||
```
|
||
OPS_WEBHOOK_SECRET=<same-as-VM122-WEBHOOK_SECRET>
|
||
OPS_WEBHOOK_ENABLED=true
|
||
```
|
||
|
||
VM122 `.env`:
|
||
```
|
||
WEBHOOK_SECRET=<shared>
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Plano de implementação (fases)
|
||
|
||
### Fase 1 — Wizard backend (P1) — ~2-3 dias
|
||
|
||
| Task | Ficheiro | Esforço |
|
||
|------|----------|---------|
|
||
| Criar `admin_domain_validation.py` | services/ | M |
|
||
| Integrar 4 checks em `infrastructure.get_status()` | infrastructure.py | S |
|
||
| Step `admin_infra` em `provision()` | infrastructure.py | M |
|
||
| Config settings | config.py | S |
|
||
| Testes unitários checks | tests/ | M |
|
||
|
||
### Fase 2 — Wizard frontend (P1) — ~1-2 dias
|
||
|
||
| Task | Ficheiro | Esforço |
|
||
|------|----------|---------|
|
||
| UI steps admin no ProcessHub | frontend/ | M |
|
||
| Link admin pós-onboarding | App.jsx | S |
|
||
| Botão «Sincronizar admin» | frontend/ | S |
|
||
|
||
### Fase 3 — Webhooks VM112→VM122 (P1) — ~1-2 dias
|
||
|
||
| Task | Ficheiro | Esforço |
|
||
|------|----------|---------|
|
||
| Settings ops_webhook | config.py, .env | S |
|
||
| Emit `infra.synced` extendido | onboarding.py | S |
|
||
| Emit `admin.validation.failed` | onboarding.py | S |
|
||
| Handlers VM122 | api/app/ | M |
|
||
|
||
### Fase 4 — Desk UI admin widget (P2) — ~2-3 dias
|
||
|
||
| Task | Ficheiro | Esforço |
|
||
|------|----------|---------|
|
||
| Secção admin no ticket detail | frontend-desk/ | M |
|
||
| Widget dashboard domínios | frontend-desk/ | M |
|
||
| Endpoint proxy admin-status | api/app/ | S |
|
||
| Runbook ERR_CERT na UI | docs/ | S |
|
||
|
||
### Fase 5 — Automação ops (P2) — ~1 dia
|
||
|
||
| Task | Esforço |
|
||
|------|---------|
|
||
| Cron sync Traefik certs semanal | S |
|
||
| Alert cert expiring → VM122 | M |
|
||
| Documentar zmproxyconfgen-ligbox em runbook Desk | S |
|
||
|
||
**Total estimado:** 7-11 dias dev.
|
||
|
||
---
|
||
|
||
## 8. Critérios de aceitação integração
|
||
|
||
### Wizard
|
||
|
||
- [ ] `GET infrastructure/status/{domain}` inclui 4 steps admin
|
||
- [ ] `POST infrastructure/provision` executa `admin_infra` automaticamente
|
||
- [ ] UI mostra estado admin no ProcessHub
|
||
- [ ] Link `mail.{dominio}/admin` no passo Concluído
|
||
- [ ] Novo domínio onboarding → admin_ready=true sem intervenção manual
|
||
|
||
### VM122 Desk
|
||
|
||
- [ ] `infra.synced` aparece na timeline do ticket
|
||
- [ ] `admin.validation.failed` cria ticket com runbook
|
||
- [ ] Widget «Admin domínio» no detalhe do ticket
|
||
- [ ] Dashboard lista domínios com status admin
|
||
- [ ] Técnico consegue re-validar via API sem SSH
|
||
|
||
### Ops
|
||
|
||
- [ ] Cron sync certs Traefik documentado e activo
|
||
- [ ] Runbook Desk para ERR_CERT / 504 / redirect
|
||
- [ ] zmproxyconfgen-ligbox referenciado em procedimento padrão
|
||
|
||
---
|
||
|
||
## 9. Referências
|
||
|
||
| Documento | Conteúdo |
|
||
|-----------|----------|
|
||
| `SPEC-CORRECAO-ADMIN-DOMINIOS-VIRTUAIS.md` | Correcção implementada VM112 |
|
||
| `SPEC-ADMIN-DOMINIO-BLINDAGEM-NGINX.md` | Blindagem nginx |
|
||
| `SPEC-CARBONIO-ADMIN-LOGIN-FLOW-VALIDATION.md` | Fluxo E1–E8 |
|
||
| `ligbox-ops-platform/specs/001-webhook-vm112-integration/` | Webhook MVP |
|
||
| `ligbox-ops-platform/specs/004-onboard-funnel-events/` | Funil onboarding |
|
||
| `ligbox-ops-platform/specs/009-ops-audit-overview/` | Audit scorecard |
|
||
|
||
---
|
||
|
||
## 10. Sincronização documentação
|
||
|
||
| Destino | Caminho |
|
||
|---------|---------|
|
||
| VM112 | `/opt/ligbox-deploy/docs/SPEC-INTEGRACAO-ADMIN-WIZARD-VM122-DESK.md` |
|
||
| Obsidian | `/root/obsidian-infra/carbonio/carbonio-server/docs/` |
|
||
| VM122 | `/root/obsidian-infra/ligbox-ops-platform/specs/010-admin-domain-validation/` |
|
||
| GitHub | `itecnologys/ibytera-mail-portal` + `ligbox-ops-platform` |
|
||
|
||
---
|
||
|
||
*Roger — planeamento integração Wizard + Desk — 2026-06-12*
|