# 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= OPS_WEBHOOK_ENABLED=true ``` VM122 `.env`: ``` WEBHOOK_SECRET= ``` --- ## 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*