runv_landing_sync.py (3195B)
1 """ 2 Sincronização da landing pública após alterações a ``users.json``. 3 4 Invoca ``site/genlanding.py --sync-public-only`` (cópia de ``site/public/`` + 5 ``data/members.json``). Partilhado por create_runv_user, update_user e del-user. 6 """ 7 8 from __future__ import annotations 9 10 import json 11 import logging 12 import subprocess 13 import sys 14 from pathlib import Path 15 16 _SCRIPT_DIR = Path(__file__).resolve().parent 17 _REPO_ROOT = _SCRIPT_DIR.parent.parent 18 19 20 def genlanding_sync_command( 21 *, 22 document_root: Path, 23 users_json: Path, 24 homes_root: Path | None = None, 25 ) -> list[str]: 26 """Comando completo para ``site/genlanding.py --sync-public-only`` (lista para subprocess).""" 27 script = _REPO_ROOT / "site" / "genlanding.py" 28 cmd: list[str] = [ 29 sys.executable, 30 str(script), 31 "--sync-public-only", 32 "--document-root", 33 str(document_root), 34 "--members-users-json", 35 str(users_json), 36 ] 37 if homes_root is not None: 38 cmd.extend(["--members-homes-root", str(homes_root)]) 39 return cmd 40 41 42 def try_sync_landing_via_genlanding( 43 *, 44 document_root: Path, 45 users_json: Path, 46 homes_root: Path | None, 47 log: logging.Logger, 48 ) -> tuple[bool, int | None]: 49 """ 50 Copia site/public → DocumentRoot e regenera data/members.json (genlanding.py --sync-public-only). 51 Falhas são apenas registadas — não aborta o chamador. 52 Devolve (sucesso, número de membros no JSON público ou None se não foi possível contar). 53 """ 54 script = _REPO_ROOT / "site" / "genlanding.py" 55 if not script.is_file(): 56 log.warning( 57 "genlanding.py não encontrado em %s; landing não sincronizada", 58 script, 59 ) 60 return False, None 61 cmd = genlanding_sync_command( 62 document_root=document_root, 63 users_json=users_json, 64 homes_root=homes_root, 65 ) 66 out = document_root / "data" / "members.json" 67 try: 68 r = subprocess.run(cmd, capture_output=True, text=True, timeout=300) 69 combined = ((r.stdout or "") + "\n" + (r.stderr or "")).strip() 70 if r.returncode != 0: 71 log.warning( 72 "genlanding --sync-public-only terminou com código %s: %s", 73 r.returncode, 74 combined[:2000] if combined else "(sem saída)", 75 ) 76 return False, None 77 log.info("landing sincronizada (site/public + members.json) em %s", document_root) 78 if combined: 79 log.debug("genlanding sync: %s", combined[:1500]) 80 n_public: int | None = None 81 try: 82 raw = out.read_text(encoding="utf-8") 83 parsed = json.loads(raw) 84 if isinstance(parsed, list): 85 n_public = len(parsed) 86 log.info("constelação: %s membro(s) no dataset público (%s)", n_public, out) 87 except (OSError, json.JSONDecodeError, TypeError) as ex: 88 log.warning("members.json após sync não foi possível validar: %s", ex) 89 return True, n_public 90 except (OSError, subprocess.TimeoutExpired) as e: 91 log.warning("falha ao executar genlanding --sync-public-only: %s", e) 92 return False, None