Entrega read-only de apontamentos DNS (Cloudflare, OpenPanel BIND, público) no Desk e Console /admin/dominio, com spec, scripts de rollback e patches VM112 para painel lateral no passo DNS do onboarding (deploy wizard pendente). Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
5.2 KiB
Python
119 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Spec 037b — provision-zone cria zona do cliente na CF Ligbox."""
|
|
from pathlib import Path
|
|
|
|
ROUTER = Path("/opt/ligbox-wizard/backend/app/routers/onboarding.py")
|
|
REGISTRY = Path("/opt/ligbox-wizard/backend/app/services/dns_account_registry.py")
|
|
CF = Path("/opt/ligbox-wizard/backend/app/services/cloudflare.py")
|
|
|
|
# --- cloudflare ensure_zone account_id ---
|
|
cf_text = CF.read_text(encoding="utf-8")
|
|
OLD_ENSURE = """ def ensure_zone(self, domain: str) -> dict:
|
|
\"\"\"Garante que a zona existe na conta Ibytera; cria se necessário.\"\"\"
|
|
zone = self.get_zone_by_name(domain)
|
|
if zone:
|
|
return {"zone": zone, "created": False}
|
|
zone = self.create_zone(domain)
|
|
return {"zone": zone, "created": True}"""
|
|
|
|
NEW_ENSURE = """ def ensure_zone(self, domain: str, account_id: str | None = None) -> dict:
|
|
\"\"\"Garante que a zona existe na conta CF; cria se necessário.\"\"\"
|
|
zone = self.get_zone_by_name(domain)
|
|
if zone:
|
|
return {"zone": zone, "created": False}
|
|
zone = self.create_zone(domain, account_id=account_id)
|
|
return {"zone": zone, "created": True}"""
|
|
|
|
if "account_id: str | None = None" not in cf_text:
|
|
cf_text = cf_text.replace(OLD_ENSURE, NEW_ENSURE)
|
|
CF.write_text(cf_text, encoding="utf-8")
|
|
print("cloudflare.py patched")
|
|
|
|
# --- registry: ensure pick_provision_account exists (copy from monorepo if needed) ---
|
|
# handled via scp separately
|
|
|
|
# --- onboarding import ---
|
|
text = ROUTER.read_text(encoding="utf-8")
|
|
if "provision_ligbox_zone" not in text:
|
|
text = text.replace(
|
|
"from app.services.dns_account_registry import resolve_ligbox_zone, resolve_payload",
|
|
"from app.services.dns_account_registry import (\n pick_provision_account,\n provision_ligbox_zone,\n resolve_ligbox_zone,\n resolve_payload,\n)",
|
|
)
|
|
|
|
OLD_BLOCK = ''' match = resolve_ligbox_zone(domain)
|
|
if match:
|
|
activity_log.info(
|
|
f"Zona {domain} já na conta {match.account.id} — sem create",
|
|
source="cloudflare",
|
|
)
|
|
payload = _portal_onboarding_payload(domain, zone_created=False)
|
|
payload["zone_id"] = match.zone_id
|
|
payload["ligbox_account_id"] = match.account.id
|
|
payload["dns_mode"] = match.dns_mode
|
|
payload["message"] = (
|
|
f"Domínio {domain} já está na Cloudflare Ligbox ({match.account.label})."
|
|
)
|
|
if nameservers := wizard_nameservers(match.zone):
|
|
payload["nameservers"] = nameservers
|
|
payload["status"]["nameservers"] = nameservers
|
|
return payload
|
|
|
|
raise HTTPException(
|
|
status_code=422,
|
|
detail={
|
|
"code": "zone_not_in_ligbox_accounts",
|
|
"domain": domain,
|
|
"message": (
|
|
"Domínio não está nas contas Ligbox (ligit, itecnologys, ibytera). "
|
|
"Use API Token da sua Cloudflare ou apontamentos no registrador."
|
|
),
|
|
"paths_available": ["byo", "external"],
|
|
},
|
|
)
|
|
|
|
cf = CloudflareDNS()
|
|
try:
|
|
activity_log.info(f"Criar zona Cloudflare: {domain}", source="cloudflare")
|
|
cf.verify_token()
|
|
result = cf.ensure_zone(domain)'''
|
|
|
|
NEW_BLOCK = ''' provision_acct = pick_provision_account()
|
|
if not provision_acct:
|
|
raise HTTPException(
|
|
400,
|
|
"Nenhuma conta Cloudflare Ligbox configurada. Ver dns-accounts.yaml e secrets/.",
|
|
)
|
|
|
|
try:
|
|
activity_log.info(
|
|
f"Provision zona cliente {domain} (conta Ligbox {provision_acct.id})",
|
|
source="cloudflare",
|
|
)
|
|
provision_acct.client().verify_token()
|
|
result = provision_ligbox_zone(domain, account=provision_acct)
|
|
match = result["account"]
|
|
cf = match.client()
|
|
result = {"zone": result["zone"], "created": result["created"]}'''
|
|
|
|
if "provision_ligbox_zone(domain" not in text:
|
|
text = text.replace(OLD_BLOCK, NEW_BLOCK)
|
|
# fix variable shadowing - after replace, need to fix zone/created extraction
|
|
text = text.replace(
|
|
" match = result["account"]\n cf = match.client()\n result = {"zone": result["zone"], "created": result["created"]}",
|
|
" ligbox_acct = result["account"]\n cf = ligbox_acct.client()\n zone = result["zone"]\n created = result["created"]",
|
|
)
|
|
# Remove duplicate zone = result["zone"] if the old code still has it - read and fix
|
|
text = text.replace(
|
|
" zone = result[\"zone\"]\n created = result[\"created\"]\n zone = result[\"zone\"]\n created = result[\"created\"]",
|
|
" zone = result[\"zone\"]\n created = result[\"created\"]",
|
|
)
|
|
|
|
# Add ligbox_account_id to payload after zone created
|
|
if 'payload["ligbox_account_id"]' not in text.split("provision_cloudflare_zone")[1][:2500]:
|
|
text = text.replace(
|
|
' payload["zone_id"] = zone.get("id")\n payload["sandbox"] = sandbox',
|
|
' payload["zone_id"] = zone.get("id")\n payload["ligbox_account_id"] = ligbox_acct.id\n payload["dns_mode"] = f"ligbox_cf_{ligbox_acct.id}"\n payload["sandbox"] = sandbox',
|
|
)
|
|
|
|
ROUTER.write_text(text, encoding="utf-8")
|
|
print("onboarding.py provision-zone patched OK")
|