ligbox-ops-platform/deploy/vm112-wizard/deploy-037-vm112.py
Ligbox Spec Hub 038fb8f7ce feat(dns): Spec 037 DNS Viewer — Desk, Console e patches Wizard V4
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>
2026-06-25 16:36:25 +00:00

125 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""Deploy 037 construction on VM112 — run ON the host."""
from pathlib import Path
import ast
WIZARD = Path("/opt/ligbox-wizard")
TMP = Path("/tmp")
for name in ["project_identity.py", "cf_client_accounts.py"]:
dst = WIZARD / "backend/app/services" / name
dst.write_text((TMP / name).read_text(encoding="utf-8"), encoding="utf-8")
print("copied", name)
(WIZARD / "backend/app/routers/project.py").write_text(
(TMP / "project_router.py").read_text(encoding="utf-8"), encoding="utf-8"
)
print("copied project.py")
main = WIZARD / "backend/app/main.py"
text = main.read_text(encoding="utf-8")
old_imp = (
"from app.routers import admin_accounts, admin_domains, assist, corporate, "
"domain_admin, onboarding, portal_auth, telemetry"
)
new_imp = old_imp.replace(", telemetry", ", project, telemetry")
if "project, telemetry" not in text:
text = text.replace(old_imp, new_imp)
if "project.router" not in text:
text = text.replace(
'app.include_router(onboarding.router, prefix="/api")',
'app.include_router(onboarding.router, prefix="/api")\n'
'app.include_router(project.router, prefix="/api")',
)
main.write_text(text, encoding="utf-8")
print("main.py ok")
onb = WIZARD / "backend/app/routers/onboarding.py"
ot = onb.read_text(encoding="utf-8")
if "cf_client_accounts" not in ot:
ot = ot.replace(
"from app.services import dns_byo_vault",
"from app.services import dns_byo_vault, cf_client_accounts, project_identity",
)
PHASE_D = '''
class ProvisionClientAccountRequest(BaseModel):
domain: str = Field(..., min_length=3, max_length=253)
display_name: str | None = None
class HandoffManagerRequest(BaseModel):
domain: str
manager_email: str = Field(..., min_length=5)
def _project_email_for_domain(domain: str) -> str | None:
ident = project_identity.get_project_identity(domain.lower().strip())
return ident.get("ligbox_project_email") if ident else None
@router.post("/dns/cloudflare/provision-client-account")
def provision_client_cf_account(body: ProvisionClientAccountRequest, request: Request):
get_session_from_request(request)
domain = normalize_domain(body.domain)
try:
validate_primary_domain(domain)
except ValueError as e:
raise HTTPException(status_code=400, detail=invalid_domain_http_detail()) from e
try:
ligbox_email = _project_email_for_domain(domain)
result = cf_client_accounts.provision_client_account(
domain,
ligbox_project_email=ligbox_email,
account_name=body.display_name or domain,
)
ops_webhook.emit_event(
"onboard.cf_account.provisioned",
domain=domain,
session_id=get_session_from_request(request),
data={
"ligbox_project_email": ligbox_email,
"zone_id": result.get("zone", {}).get("id"),
"provision_mode": result.get("record", {}).get("provision_mode"),
},
)
return result
except CloudflareError as e:
raise HTTPException(502, str(e)) from e
@router.post("/dns/cloudflare/handoff-manager")
def handoff_cf_manager(body: HandoffManagerRequest, request: Request):
get_session_from_request(request)
domain = normalize_domain(body.domain)
result = cf_client_accounts.handoff_manager(domain, body.manager_email)
ops_webhook.emit_event(
"onboard.cf_handoff.requested",
domain=domain,
session_id=get_session_from_request(request),
data=result,
)
return result
'''
if "/dns/cloudflare/provision-client-account" not in ot:
ot = ot.replace('@router.get("/session/password-status")', PHASE_D + '@router.get("/session/password-status")')
if "onboard.dns.applied" not in ot:
ot = ot.replace(
' verification = dns_verify.verify_mail_dns(domain, settings.mail_public_ip)\n return {\n "domain": domain,',
' verification = dns_verify.verify_mail_dns(domain, settings.mail_public_ip)\n ops_webhook.emit_event(\n "onboard.dns.applied",\n domain=domain,\n session_id=sid,\n data={\n "dns_mode": dns_mode,\n "ligbox_project_email": _project_email_for_domain(domain),\n "ligbox_account_id": ligbox_acct,\n "zone_id": zone_id,\n "verification_ready": verification.get("ready"),\n },\n )\n return {\n "domain": domain,',
)
if "onboard.account.created" not in ot:
ot = ot.replace(
' activity_log.ok("Processo de onboarding finalizado", source="portal")',
' ops_webhook.emit_event(\n "onboard.account.created",\n domain=domain,\n session_id=sid,\n data={\n "email": email,\n "dns_mode": dns_mode,\n "ligbox_project_email": _project_email_for_domain(domain),\n "verified": verified,\n },\n )\n activity_log.ok("Processo de onboarding finalizado", source="portal")',
)
onb.write_text(ot, encoding="utf-8")
ast.parse(main.read_text())
ast.parse(onb.read_text())
print("deploy-037-vm112 OK")