# Spec 037 — DNS multi-conta Cloudflare + BYO + Registrador **Criado:** 2026-06-22 **Solicitado por:** Roger **Status:** 🔄 Em implementação (Fase A activa · B/C/D em construção) **Prioridade:** P1 **VM:** 112 (wizard API `:8090`) **Relacionado:** [004 cloudflare-zone-provision](../004-onboard-funnel-events/cloudflare-zone-provision.md) · 025 · 017 · **[dns-viewer.md](./dns-viewer.md)** · [035 §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md) --- ## Resumo O passo DNS do onboarding usa **três contas Cloudflare gerenciadas pela Ligbox** (ligit, itecnologys, ibytera). O wizard: 1. **Se a zona já existe** numa das 3 contas → aplica apontamentos com o token dessa conta. 2. **Se o cliente escolhe «Cloudflare Ligbox»** e a zona **ainda não existe** → o wizard **cria a zona do cliente** na conta Ligbox correcta (`provision-zone`), depois aplica MX/SPF/etc. 3. **Se o cliente não quer DNS na Ligbox** → BYO (token CF dele) ou registrador externo. **Política:** não importar domínios alheios nem criar zonas fora do fluxo wizard; **sim** criar a zona **do cliente** na CF gerenciada pela Ligbox quando esse for o caminho escolhido. **Evolução (Roger 2026-06-22):** cada cliente novo terá **conta Cloudflare dedicada** — e-mail inicial `proj_{id}@ligbox.com.br`, dados do domínio do cliente; após go-live, convidar gestor `admin@dominio`. Ver [client-cf-account-lifecycle.md](./client-cf-account-lifecycle.md). **Identidade projeto (Roger 2026-06-22):** o agente **cria** `proj_{id}@ligbox.com.br` no Carbonio, **entrega senha** ao cliente e referencia em CF, DNS, registry e webhooks. Ver [project-email-identity.md](./project-email-identity.md). --- ## Três contas Ligbox (pré-cadastro) | ID interno | E-mail admin (referência) | Ficheiro token (VM112) | `account_id` CF | |------------|---------------------------|-------------------------|-----------------| | `ligit` | admin@ligit.com.br | `secrets/cloudflare-ligit.token` | _configurar_ | | `itecnologys` | admin@itecnologys.com | `secrets/cloudflare-itecnologys.token` | _configurar_ | | `ibytera` | ibytera@gmail.com | `secrets/cloudflare-ibytera.token` | _configurar_ | Catálogo: `deploy/vm112-wizard/dns-accounts.yaml` (não commitar tokens). Conta **legacy** `cloudflare.token` + `cloudflare_account_id` em `config.py` → migrar para `ibytera` ou deprecar após cutover. --- ## Fluxo decisão (passo DNS) ```mermaid flowchart TD A[POST validate-domain] --> B[GET /dns/resolve/{domain}] B --> C{Zona já existe em conta Ligbox?} C -->|sim| D[apply na conta encontrada] C -->|não| E{Cliente escolhe caminho} E -->|Cloudflare Ligbox| F[provision-zone: cria zona do cliente na conta default] F --> G[apply apontamentos] E -->|BYO| H[connect-custom + apply] E -->|Registrador| I[instructions + verify manual] D --> V[GET dns/verify] G --> V H --> V I --> V V --> J[account/create] ``` ### Regra «zona do cliente na CF Ligbox» - **Existente:** probe `ligit → itecnologys → ibytera`; usa a conta onde a zona já está. - **Nova (wizard):** `POST provision-zone` cria `cliente.com.br` na conta `default_provision_account` (yaml, hoje `ibytera`) — **só** quando o utilizador escolhe Cloudflare Ligbox. - **Nunca:** criar zona sem escolha explícita Ligbox; nunca BYO/registrador criar em conta Ligbox. --- ## Endpoints novos / alterados | Método | Path | Descrição | |--------|------|-----------| | GET | `/api/onboarding/dns/resolve/{domain}` | Procura zona nas 3 contas; devolve `matched_account`, `zone_id`, `paths_available` | | POST | `/api/onboarding/dns/cloudflare/connect-custom` | Valida token BYO + zona; guarda em vault sessão | | POST | `/api/onboarding/dns/cloudflare/apply` | **Alterado:** usa conta resolvida, BYO, ou body `account_id` | | POST | `/api/onboarding/dns/cloudflare/provision-zone` | **Cria zona do cliente** na CF Ligbox (conta default ou existente) — só caminho Ligbox | ### Response `dns/resolve` ```json { "domain": "cliente.com.br", "matched": true, "account_id": "ligit", "account_label": "Ligbox Ligit (admin@ligit.com.br)", "zone_id": "…", "zone_status": "active", "dns_mode": "ligbox_cf_ligit", "paths_available": ["apply_ligbox", "byo", "external"] } ``` Se `matched: false` (zona nova): ```json { "matched": false, "can_provision_ligbox": true, "provision_account_id": "ibytera", "paths_available": ["provision_ligbox", "byo", "external"], "message": "Domínio novo — pode criar zona na Cloudflare gerenciada pela Ligbox ou usar BYO/registrador." } ``` --- ## BYO Cloudflare (cliente) 1. Cliente cola **API Token** (scope: DNS Edit + Zone Read **só na zona**). 2. `verify_token()` + `get_zone_by_name(domain)`. 3. Token em vault (`/var/lib/.../dns_tokens/{session_hash}`), TTL 7 dias pós-onboard. 4. `apply` com `CloudflareDNS(token=customer_token)`. 5. Purge Spec 017 **não** apaga zona BYO — só registos criados por nós (futuro: tracking). --- ## Registrador / DNS externo Sem mudança funcional core: `GET /dns/instructions/{domain}` + verificação `dns_verify`. Fase 2: adapters EPP/API (Gandi, GoDaddy, Registro.br homologado). --- ## DNS Viewer (read-only) — Spec 037-DNS-VIEWER Painel unificado para **exibir** apontamentos (staff Desk + gerente Console) **sem editar** na UI. | Caminho wizard | O viewer mostra | |----------------|-----------------| | **Cloudflare Ligbox** | Registos **planeados** (pré-NS) ou **aplicados** (pós-apply) + NS CF | | **BYO / Registrador / externo** | DNS **actual** (público + instructions) — **não** preview Ligbox | Documento completo: **[dns-viewer.md](./dns-viewer.md)** — endpoints, links editar, fases V0–V4. **Estado deploy (2026-06-25):** V1–V3 produção (VM122/123) · commit `038fb8f` · V4 wizard patches prontos · deploy VM112 pendente. --- ## Código | Caminho monorepo | Deploy VM112 | |------------------|--------------| | `projects/wizard/backend/app/services/dns_account_registry.py` | `/opt/ligbox-wizard/backend/app/services/` | | `deploy/vm112-wizard/dns-accounts.yaml` | `/opt/ligbox-wizard/dns-accounts.yaml` | | `deploy/vm112-wizard/dns-accounts.yaml.example` | exemplo versionado | --- ## Critérios de aceitação (Fase 1) 1. Domínio existente na conta `ibytera` → `resolve` retorna `matched` + `apply` OK. 2. Domínio **novo** + caminho Ligbox → `provision-zone` cria zona na conta default + `apply` OK. 3. BYO com token válido → `apply` na conta do cliente. 4. Caminho registrador → sem `provision-zone` Ligbox. 5. Tokens em `secrets/` — nunca no Git. --- ## Decisões Roger (2026-06-22, corrigido) | # | Regra | |---|--------| | 1 | Não importar domínios alheios nas contas Ligbox | | 2 | **Sim** criar zona **do cliente** na CF gerenciada Ligbox via wizard (escolha explícita) | | 3 | Conta para zona nova: `default_provision_account` no yaml (hoje `ibytera`) | | 4 | BYO / registrador = DNS fora das contas Ligbox | --- ## Fase 2 — Conta CF dedicada por cliente Ver [client-cf-account-lifecycle.md](./client-cf-account-lifecycle.md): | Fase | E-mail Cloudflare | Quando | |------|-------------------|--------| | A — Onboarding | `proj_{id}@ligbox.com.br` | Criação conta + zona | | B — Handoff | `admin@{dominio_cliente}` convidado como gestor | DNS + email + infra OK | **Orquestração agentica (edge):** [cf-agents-sdk-architecture.md](./cf-agents-sdk-architecture.md) — Cloudflare Agents SDK (Durable Objects, multi-agent, human-in-the-loop) + roster Spec 029 A0–A7.