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>
125 lines
5.1 KiB
Python
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")
|