# 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*
