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>
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.py — GET /api/v1/dns/cloudflare/records — nã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_zone — funçã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_validated → dns_applied → account_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.pyou rotas emonboarding.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).