ligbox-ops-platform/specs/004-onboard-funnel-events/cloudflare-zone-provision.md
Ligbox Spec Hub c1881f58e6 chore: sync Console SSO, DNS viewer, specs e infra docs pendentes
Inclui console handoff Desk↔Console (Spec 019), melhorias DNS Viewer (037),
OpenPanel/Nextcloud/VM116 deploy notes, contracts stack e sidebar actualizado.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 20:10:17 +00:00

13 KiB

Anexo 004 — Cloudflare: inserir domínio na conta Ligbox (wizard VM112)

Parent: spec.md · Spec 004 onboard-funnel-events
Criado: 2026-06-22 · Roger
VM: 112 (10.10.10.112) · API wizard :8090
URL pública: https://onboard.ligbox.com.br/api/onboarding/

Este anexo documenta onde vive o código, funções, endpoints, fluxo UI e specs relacionadas para criar/inserir um domínio na conta Cloudflare Ligbox/Ibytera durante o onboarding (opção «Trazer DNS para o portal»).


Resumo operacional

Acção Quem Quando no wizard
Validar domínio (formato + Carbonio) POST /validate-domain Passo 0 — antes do CF
Criar zona na conta CF POST /dns/cloudflare/provision-zone Passo DNS — «Trazer para o portal»
Aplicar MX/SPF/DKIM/… POST /dns/cloudflare/apply Após NS no registrador (ou auto sandbox)
Verificar propagação GET /dns/verify/{domain} Antes de criar conta
Emitir dns.applied (funil Desk) apply_cloudflare_dns router Após apply OK — ver research.md

Nota: provision-zone cria a zona; apply escreve registos DNS dentro da zona. São passos distintos.

Spec 037 (2026-06-22): três contas Ligbox (ligit, itecnologys, ibytera). GET /dns/resolve/{domain} descobre a conta; domínio fora das três → BYO (connect-custom) ou registrador. Ver 037-dns-multi-cloudflare-orchestration.


Onde vive o código (VM112)

Caminho Papel
/opt/ligbox-wizard/backend/app/services/cloudflare.py Cliente API Cloudflare (CloudflareDNS)
/opt/ligbox-wizard/backend/app/routers/onboarding.py Rotas HTTP onboarding + hooks webhook funil
/opt/ligbox-wizard/backend/app/services/dns_verify.py Verificação MX/A após apply
/opt/ligbox-wizard/backend/app/config.py cloudflare_account_id, load_cloudflare_token_from_secrets()
/opt/ligbox-wizard/secrets/cloudflare.token Token API (não commitar)
/opt/ligbox-wizard/secrets/README.txt Instruções token
Frontend: frontend/src/App.jsx (ou deploy equivalente) choosePortalDns(), applyPortalDnsRecords()
Serviço: systemctl status ligbox-wizard API :8090

Repo espelho (dev): ibytera-mail-portal / ligbox-wizard no monorepo ou LAPTOP — deploy para VM112 via scp + restart.

Desk VM122 (só leitura): projects/ops-desk/api/app/cloudflare_dns.pyGET /api/v1/dns/cloudflare/recordsnão cria zona.


Configuração e credenciais

Variável / ficheiro Descrição
secrets/cloudflare.token API Token Cloudflare (conta Ligbox/Ibytera)
settings.cloudflare_account_id Account ID onde zonas são criadas (POST /zones body account.id)
settings.mail_public_ip IP para registos A/MX em mail_dns_records()
settings.onboard_sandbox Se true → modo sandbox (sem CF real)
Token prefix sandbox_ Também activa is_cloudflare_sandbox()

Constitution — segurança token

Fonte: .specify/memory/constitution.md

Cloudflare API: Tokens com scope mínimo (zone DNS, não account-wide).

Scopes mínimos para provision-zone (produção):

Scope Motivo
Account - Zone - Edit Criar zona na conta
Zone - Create POST /zones
Zone - DNS - Edit upsert_record (MX, SPF, TXT, A)
Zone - Read get_zone_by_name, list records

Mensagem de erro no código se faltar permissão:

Token sem permissão para criar zonas.
Adicione «Account - Zone - Edit» e «Zone - Create» ao API token Ibytera.

Funções que criam/inserem a zona (conta CF)

Ficheiro: backend/app/services/cloudflare.py

Helpers

Função Descrição
is_cloudflare_sandbox(token?) True se onboard_sandbox ou token sandbox_*
sandbox_zone(domain) Zona fictícia para testes (sem API CF)
wizard_nameservers(zone) Extrai NS da zona para UI
mail_dns_records(domain, mail_ip, aliases?) Lista dicts MX/SPF/DKIM/A para apply

Classe CloudflareDNS

Método API CF Descrição
verify_token() GET /user/tokens/verify ou equivalente Valida token antes de operações
get_zone_by_name(domain) GET /zones?name= Procura zona na conta
create_zone(domain, account_id?) POST /zones Cria zona type: full na conta Ibytera; se já existe (1061/1093) devolve existente
ensure_zone(domain) composto get_zone_by_name → se null → create_zonefunção principal de inserção
list_zone_names() GET /zones paginado Lista domínios na conta
list_records(zone_id) GET /zones/{id}/dns_records Registos actuais
upsert_record(zone_id, type, name, content, …) POST/PUT dns_records Idempotente (81058 = já existe)
fqdn(name, zone_name) Normaliza @ e subdomínios

Corpo create_zone (produção)

{
  "name": "exemplo.com.br",
  "account": { "id": "<cloudflare_account_id>" },
  "type": "full",
  "jump_start": false
}

Endpoints API (wizard — passo DNS onboard)

Base: /api/onboarding · Header obrigatório: X-Onboarding-Session: <session_id>

Método Path Handler Função
POST /validate-domain validate_domain Valida domínio; emite onboarding.started + domain.validated
GET /dns/cloudflare/status/{domain} cloudflare_zone_status Estado zona (configurado?, NS, pending)
POST /dns/cloudflare/provision-zone provision_cloudflare_zone ensure_zone(domain) — insere na conta CF
POST /dns/cloudflare/apply apply_cloudflare_dns Aplica registos mail; emite dns.applied
GET /dns/verify/{domain} verify_dns Verifica propagação DNS pública
GET /dns/portal-onboarding/{domain} portal_dns_onboarding Payload UI passos NS
GET /dns/instructions/{domain} dns_instructions Instruções alternativas DNS
POST /account/create create_account Conta Carbonio + infra (após DNS OK)

provision-zone — request/response

Request:

{ "domain": "exemplo.com.br" }

Response (campos principais):

{
  "domain": "exemplo.com.br",
  "zone_id": "…",
  "nameservers": ["…ns.cloudflare.com", "…"],
  "message": "Domínio … criado na Cloudflare Ligbox",
  "sandbox": false,
  "status": { "nameservers": ["…"] },
  "steps": [  ],
  "verification": {  }
}

Em sandbox, provision-zone pode também aplicar registos simulados e preencher verification.

apply — request

{
  "domain": "exemplo.com.br",
  "zone_id": "opcional-se-ja-conhecido",
  "mail_aliases": ["suporte", "vendas"]
}

Se zona não existir na conta → HTTP 422 zone_not_in_account + use_portal_onboarding: true.


Fluxo no wizard (UI)

sequenceDiagram
  participant U as Cliente
  participant UI as Wizard App.jsx
  participant API as VM112 :8090
  participant CF as Cloudflare API
  participant OPS as Desk VM122

  U->>UI: Passo 0 — domínio
  UI->>API: POST /validate-domain
  API-->>OPS: webhook domain.validated (+ started 1x)

  U->>UI: Escolhe «DNS no portal Ligbox»
  UI->>API: POST /dns/cloudflare/provision-zone
  API->>CF: ensure_zone (create se necessário)
  CF-->>API: zone_id + nameservers
  API-->>UI: nameservers + passos

  U->>U: Altera NS no registrador
  UI->>API: POST /dns/cloudflare/apply
  API->>CF: upsert_record (MX, SPF, …)
  API-->>OPS: webhook dns.applied
  UI->>API: GET /dns/verify/{domain}

  U->>UI: Passo conta admin
  UI->>API: POST /account/create

Funções frontend (referência)

Função JS Endpoint
choosePortalDns() POST /onboarding/dns/cloudflare/provision-zone
applyPortalDnsRecords() / applyPortalDns() POST /onboarding/dns/cloudflare/apply
refreshCfStatus() GET /onboarding/dns/cloudflare/status/{domain}

Ficheiro típico: frontend/src/App.jsx (passo DNS do onboard).


Integração funil Desk (Spec 004)

Marco wizard Evento webhook Hook em onboarding.py
Primeiro validate-domain OK onboarding.started validate_domain
validate-domain OK domain.validated validate_domain
apply CF OK dns.applied apply_cloudflare_dns (~L592)
Infra pós-conta infra.synced create_account
Fim OK onboarding.completed create_account
Erro crítico onboarding.failed create_account except

Fase funil: domain_validateddns_appliedaccount_created → …

Ver research.md tabela hook points.

Gap conhecido: provision-zone não emite evento próprio — só cria zona. O funil avança para dns_applied no apply.


Specs e documentos relacionados

# Documento Relação com Cloudflare onboard
004 spec.md Funil + dns.applied
004 research.md Hook apply_cloudflare_dns
004 contracts/webhook-funnel-events.md Payload eventos funil
001 specs/001-webhook-vm112-integration/spec.md Webhook base + domain.validated
012 specs/012-abandoned-onboarding-lead/spec.md onboarding.started em account/create (não validate)
016 specs/016-onboard-self-service-prefill/spec.md Pré-preenche domínio passo 0
025 specs/025-wizard-onboarding-continuity/spec.md Fluxo domínio/DNS idempotente
017 specs/017-vm112-domain-orchestration/spec.md Purge — apaga zona CF (passo 5)
010 specs/010-desk-assist-takeover/spec.md dns.revalidate / assist passo DNS
010 specs/010-admin-domain-validation/spec.md Checks infra (não cria zona)
019 specs/019-email-migration-vm122-execution/spec.md MX CF bloqueado até gate DNS
035 specs/035-ligbox-mail-bundles-foss-openpanel/spec.md Wizard: MX, SPF, DMARC records
027 specs/027-desk-rbac-function-matrix/spec.md RBAC cloudflare_dns.read Desk
constitution.md Token CF scope mínimo
chat-bruto/CHAT_BRUTO_WIZARD_VM112_20260619.txt E2E provision-zone, sandbox, fixes

Testes rápidos (curl)

SESSION="test-$(date +%s)"
BASE="https://onboard.ligbox.com.br/api/onboarding"
HDR=(-H "X-Onboarding-Session: $SESSION" -H "Content-Type: application/json")

# 1. Validar domínio
curl -s -X POST "$BASE/validate-domain" "${HDR[@]}" \
  -d '{"domain":"exemplo.com.br"}' | jq .

# 2. Estado CF
curl -s "$BASE/dns/cloudflare/status/exemplo.com.br" "${HDR[@]}" | jq .

# 3. Inserir zona na conta Ligbox
curl -s -X POST "$BASE/dns/cloudflare/provision-zone" "${HDR[@]}" \
  -d '{"domain":"exemplo.com.br"}' | jq .

# 4. Aplicar registos mail (após NS)
curl -s -X POST "$BASE/dns/cloudflare/apply" "${HDR[@]}" \
  -d '{"domain":"exemplo.com.br"}' | jq .

# 5. Verificar DNS
curl -s "$BASE/dns/verify/exemplo.com.br" "${HDR[@]}" | jq .

LAN directo: substituir host por http://10.10.10.112:8090/api/onboarding.


Erros comuns

Sintoma Causa provável Acção
Token Cloudflare não configurado Falta secrets/cloudflare.token Criar token + restart wizard
Token sem permissão para criar zonas Scopes insuficientes Account Zone Edit + Zone Create
zone_not_in_account no apply provision-zone não foi corrido Correr provision primeiro
Zona noutra conta CF Domínio já em CF cliente Opção «manter no provedor» ou transfer
Sandbox sem CF real Token sandbox_ ou onboard_sandbox Esperado em dev

Manutenção deste anexo

Actualizar quando:

  • Alterar cloudflare.py ou rotas em onboarding.py
  • Novos eventos funil ligados a CF
  • Mudança de conta Cloudflare ou scopes token
  • Refactor frontend passo DNS

Após editar: refresh-spec-driver.sh no CT130 (regra spec-driver-sync).