10 KiB
Spec 026 — Purge VM112: validação Traefik pós-remoção (CT114)
Criado: 2026-06-19
Solicitado por: Roger
Prioridade: P0 (incidente produção)
Status: ✅ Implementado (VM112 + CT114, 2026-06-19)
Sistema: Wizard VM112 · Traefik CT114 · Desk VM122
Relacionado: Spec 017 (purge domínio) · Spec 025 (continuidade wizard) · Spec 018 (Serviços / drawer purge)
Incidente que motivou a spec
Data: 2026-06-19 ~02:18 UTC
Sintoma: https://onboard.ligbox.com.br/onboard → 404 page not found (Traefik Go default), afectando todos os domínios onboard — não só o domínio purgado.
Domínios purgados na sessão: iofficebooks.com, exuberanti.com.br.
Causa raiz:
_purge_traefik_routers()em/opt/ligbox-wizard/backend/app/services/domain_orchestration.pyremove routers por corte de texto (Host(...)→ próximo\n).- Isso deixou bloco
mail-mail-exuberanti-com-br-Routersemrule:e chave duplicada nodynamic.yml. - Traefik v3.6 rejeitou o ficheiro inteiro:
yaml: unmarshal errors: mapping key "mail-mail-exuberanti-com-br-Router" already defined - Após restart, só 3 routers internos activos (
acme,api,dashboard) — zero rotas de produção. - O purge reportou
traefik_okporque validou apenas SSH write + restart, não carga efectiva da config.
Correcção manual aplicada (19/06): remoção de routers inválidos/duplicados + restart Traefik → 62 routers activos.
Objetivo
Tornar o purge de domínio seguro para a plataforma inteira: após remover um tenant, o Traefik tem de continuar operacional e o onboard tem de responder 200.
Regra de ouro (nova):
Purge só está concluído quando o domínio sumiu da base e o Traefik tem ≥ N routers e
GET https://onboard.ligbox.com.br/onboard→ 200 com HTML do wizard.
Fora de escopo
- Reescrever Spec 017 (histórico Desk, RBAC, drawer)
- Purge parcial (só DNS, só contas)
- Validação de certificados LE por domínio purgado (opcional futuro)
- Automatizar purge agendado
Problema na implementação actual (VM112)
| Função | Ficheiro | Problema |
|---|---|---|
_purge_traefik_routers |
domain_orchestration.py |
Corte textual frágil; não remove middleware webmail-pending-{slug}; não valida YAML |
_purge_traefik_sni |
idem | OK funcional; falta verificação pós-restart HAProxy |
_execute_purge |
idem | Marca traefik_ok sem smoke test |
| Portal users | _purge_portal_users |
Só /var/lib/ibytera-mail-portal/portal_users/ — falta /var/lib/ligbox-wizard/portal_users/ |
| Nginx Carbonio | — | Não limpa vhosts mail.{domain} em /opt/zextras/conf/nginx/includes/ |
| Branding / scripts deploy | — | Não remove entrada tenant_branding.py nem refs em apply-admin-nginx-overrides.py |
Solução proposta
Fase A — Remoção Traefik robusta (P0)
Substituir corte textual por script Python remoto no CT114 (mesmo padrão de infrastructure.do_traefik()):
- Backup antes de editar:
/root/traefik/dynamic.yml.bak-purge-{domain_slug}-{timestamp} - Parse YAML (
yaml.safe_loadpara validação; edição linha-a-linha — nuncasafe_dumpno ficheiro inteiro). - Remover, por domínio:
- Router
mail-mail-{slug}-Router(e variantes) - Middleware
webmail-pending-{slug}(redirect regex para wizard) - Qualquer router cujo
rulecontenhaHost(\mail.{domain}`)` ou alias mail
- Router
- Validação pré-restart:
- YAML parse OK
- Zero chaves duplicadas em
http.routers - Todo router tem campo
rulenão vazio - Zero ocorrências de
mail.{domain}no texto (sanity grep)
- Restart Traefik só se validação OK.
- Se validação falhar → rollback do backup sem restart.
Slug: {domain} com . → - (ex.: exuberanti.com.br → exuberanti-com-br).
Fase B — Verificação pós-purge (P0)
Novo step _execute_purge: traefik_validate (após traefik_routers).
| # | Check | Comando / origem | Critério |
|---|---|---|---|
| B1 | Routers carregados | curl -s http://127.0.0.1:8080/api/http/routers (CT114) |
count ≥ 10 (alerta se < 10; falha se < 5) |
| B2 | Onboard router activo | JSON routers | existe onboard-ligbox-Router@file enabled |
| B3 | Smoke HTTPS onboard | curl -sf -o /dev/null -w '%{http_code}' https://onboard.ligbox.com.br/onboard |
200 |
| B4 | Smoke API VM112 | curl -sf -o /dev/null -w '%{http_code}' http://10.10.10.112:8090/onboard |
200 |
| B5 | Sem refs domínio no dynamic | grep -i {domain} em dynamic.yml |
0 matches (excepto backup) |
| B6 | Log Traefik limpo | docker logs traefik 2>&1 | tail -20 |
sem unmarshal errors / invalid rule nos últimos 30s |
Falha em B1–B4: rollback dynamic.yml + restart Traefik + step traefik_validate = error + job purge = error (não done).
Timeline Desk: novo passo visível «Validar Traefik / onboard» com detalhe de cada check.
Fase C — Purge VM112 completo (P1)
Expandir _execute_purge com steps adicionais (ou sub-steps documentados):
| Step | Acção |
|---|---|
portal_users_wizard_store |
Apagar JSON em /var/lib/ligbox-wizard/portal_users/ cujo email ∈ domínio |
nginx_vhosts |
Remover server_name mail.{domain} de includes nginx Carbonio + nginx -t + reload |
tenant_branding |
Remover linha em tenant_branding.py |
deploy_scripts |
Remover mail.{domain} de apply-admin-nginx-overrides.py e sync-traefik-admin-certs.sh |
traefik_export_certs |
Apagar mail-{slug}*.pem em /opt/zextras/ssl/letsencrypt/traefik-export/ |
Cada step reporta ok / error na timeline; falha nginx nginx -t → error (não deixa mail quebrado).
Fase D — Desk / histórico (P2)
- Persistir em
vm112_jsondo job: resultado detraefik_validate(checks B1–B6). - Badge error no histórico se rollback Traefik ocorreu.
- Alerta ops (ntfy / webhook) quando purge falha em
traefik_validate.
Alterações de API / timeline
VM112 — novos steps em POST /api/admin/domains/{domain}/purge
Ordem actualizada (trecho Traefik):
…
traefik_sni → running → done|error
traefik_routers → running → done|error (Fase A — lógica nova)
traefik_validate → running → done|error (Fase B — NOVO)
…
Resposta result (campos novos):
{
"traefik_validate": {
"ok": true,
"router_count": 62,
"onboard_http": 200,
"rollback": false
},
"traefik_rollback": null
}
Em falha:
{
"traefik_validate": { "ok": false, "router_count": 3, "onboard_http": 404, "rollback": true },
"traefik_rollback": "dynamic.yml.bak-purge-exuberanti-com-br-20260619T021800Z"
}
Ficheiros a alterar
| VM | Ficheiro | Fase |
|---|---|---|
| 112 | backend/app/services/domain_orchestration.py |
A, B, C |
| 112 | backend/app/services/infrastructure.py |
A (reutilizar _router_key_for_host, SSH helpers) |
| 114 | /root/traefik/dynamic.yml |
(runtime — só via purge script) |
| 122 | api/app/vm112_domains_routes.py |
D (opcional — repassar novos campos) |
| 122 | frontend/assets/app.js |
D (render checks no modal histórico) |
Deploy: VM112 systemctl restart ligbox-wizard após merge.
Critérios de aceitação
- Purge de domínio teste remove router/middleware sem duplicar chaves YAML.
- Após purge, Traefik API reporta ≥ 10 routers HTTP.
curl -sf https://onboard.ligbox.com.br/onboard→ 200 imediatamente após purge.- Purge com YAML inválido simulado → rollback automático + job status error (não
done). - Portal users removidos de ambas as pastas (
ibytera-mail-portal+ligbox-wizard). - Histórico Desk (Spec 017 v2) mostra step
traefik_validatecom detalhe. - Regressão: purge de domínio inexistente (
no_zone,domínio já ausente) continua idempotente.
Test plan (E2E)
# Pré: criar domínio teste via wizard (zona CF pending OK)
DOMAIN=teste-purge-$(date +%s).example.com # ou domínio real de lab
# Executar purge
curl -s -X POST "http://10.10.10.112:8090/api/admin/domains/${DOMAIN}/purge?sync=true" \
-H "X-Api-Key: $ADMIN_API_KEY" | jq '.result.traefik_validate'
# Validar plataforma
curl -sf -o /dev/null -w "onboard:%{http_code}\n" https://onboard.ligbox.com.br/onboard
ssh root@10.10.10.114 'curl -s http://127.0.0.1:8080/api/http/routers | python3 -c "import sys,json; print(len(json.load(sys.stdin)),\"routers\")"'
Teste de regressão (incidente 19/06): purge exuberanti.com.br duas vezes seguidas → segunda execução idempotente, Traefik estável.
Riscos e mitigação
| Risco | Mitigação |
|---|---|
| Rollback falha | Manter últimos 5 backups dynamic.yml.bak-purge-* |
| Traefik API :8080 fechado externamente | Checks só via SSH CT114 localhost |
| Purge longo (>60s) | Jobs async Spec 017 já existem; validate no final |
| Race: dois purges simultâneos | Lock file CT114 /tmp/traefik-dynamic.lock |
Prioridade no backlog
| Fase | Prioridade | Motivo |
|---|---|---|
| A + B | P0 | Evita outage total do onboard |
| C | P1 | Limpeza completa tenant (nginx, branding) |
| D | P2 | Observabilidade Desk |
Referências
- Incidente: purge
exuberanti.com.br2026-06-19 — Traefik 3 routers only - Spec 017 — ordem purge VM112 + histórico Desk
- Spec 025 — item backlog «Traefik YAML validation» (consolidar implementação aqui)
- Log Traefik:
mapping key "mail-mail-exuberanti-com-br-Router" already defined at line 475 - Fix manual:
dynamic.yml.bak-fix-dup-exuberanti-20260619
Conclusão (estado actual)
| Fase | Entrega | Estado |
|---|---|---|
| A | Remoção YAML estruturada + backup/rollback | ✅ |
| B | traefik_validate + smoke onboard |
✅ |
| C | Purge nginx / branding / wizard store | ✅ (parcial — VM112) |
| D | Histórico Desk + alerta ops | 📋 |
Implementação pendente em VM112 — esta spec documenta o backlog acordado com Roger (2026-06-19).