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>
102 lines
3.4 KiB
Python
102 lines
3.4 KiB
Python
"""Conta Cloudflare dedicada por cliente — Spec 037 Fase D."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from app.services.cloudflare import CloudflareDNS, CloudflareError
|
|
from app.services.dns_account_registry import pick_provision_account, provision_ligbox_zone
|
|
|
|
_CF_CLIENT_DIR = Path("/var/lib/ligbox-wizard/cf_client_accounts")
|
|
|
|
|
|
def _path(domain: str) -> Path:
|
|
safe = domain.lower().strip().replace("/", "_")
|
|
return _CF_CLIENT_DIR / f"{safe}.json"
|
|
|
|
|
|
def save_record(domain: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
domain = domain.lower().strip().rstrip(".")
|
|
_CF_CLIENT_DIR.mkdir(parents=True, exist_ok=True)
|
|
existing = load_record(domain) or {}
|
|
merged = {**existing, **data, "domain": domain}
|
|
merged["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
if not merged.get("created_at"):
|
|
merged["created_at"] = merged["updated_at"]
|
|
_path(domain).write_text(json.dumps(merged, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
return merged
|
|
|
|
|
|
def load_record(domain: str) -> dict[str, Any] | None:
|
|
p = _path(domain)
|
|
if not p.is_file():
|
|
return None
|
|
try:
|
|
return json.loads(p.read_text(encoding="utf-8"))
|
|
except json.JSONDecodeError:
|
|
return None
|
|
|
|
|
|
def provision_client_account(
|
|
domain: str,
|
|
*,
|
|
ligbox_project_email: str | None = None,
|
|
account_name: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Fase D: cria conta CF dedicada (Tenant) ou fallback zona na conta provision.
|
|
Hoje: fallback = provision_ligbox_zone até Tenant activo.
|
|
"""
|
|
domain = domain.lower().strip().rstrip(".")
|
|
acct = pick_provision_account()
|
|
if not acct:
|
|
raise CloudflareError("Nenhuma conta Ligbox configurada.")
|
|
prov = provision_ligbox_zone(domain, account=acct)
|
|
zone = prov["zone"]
|
|
record = save_record(
|
|
domain,
|
|
{
|
|
"ligbox_project_email": ligbox_project_email,
|
|
"cloudflare_account_id": acct.cloudflare_account_id,
|
|
"cloudflare_account_name": account_name or f"{domain}",
|
|
"zone_id": zone.get("id"),
|
|
"provision_mode": "shared_account_fallback",
|
|
"handoff_status": "pending",
|
|
"tenant_dedicated": False,
|
|
},
|
|
)
|
|
return {
|
|
"domain": domain,
|
|
"zone": zone,
|
|
"created": prov["created"],
|
|
"account": acct.id,
|
|
"record": record,
|
|
"message": (
|
|
"Zona provisionada (modo fallback conta partilhada). "
|
|
"Tenant dedicado pendente activação Cloudflare."
|
|
),
|
|
}
|
|
|
|
|
|
def handoff_manager(domain: str, manager_email: str) -> dict[str, Any]:
|
|
"""Fase D: convite gestor CF — stub até Tenant + members API."""
|
|
domain = domain.lower().strip().rstrip(".")
|
|
manager_email = manager_email.lower().strip()
|
|
rec = load_record(domain) or {}
|
|
rec = save_record(
|
|
domain,
|
|
{
|
|
**rec,
|
|
"client_manager_email": manager_email,
|
|
"handoff_status": "invited_stub",
|
|
"handoff_note": "Implementar POST /accounts/{id}/members quando Tenant activo.",
|
|
},
|
|
)
|
|
return {
|
|
"domain": domain,
|
|
"manager_email": manager_email,
|
|
"handoff_status": rec["handoff_status"],
|
|
"message": "Handoff registado (stub). Convite CF manual até Tenant API.",
|
|
}
|