#!/usr/bin/env python3 """Verify agent_runs RLS; apply sql/agent_runs_rls.sql only if still disabled.""" from __future__ import annotations import base64 import re import sys from pathlib import Path from playwright.sync_api import sync_playwright ROOT = Path("/Users/4ventures/4ventures-client-portal") BPS = Path("/Users/4ventures/Projects/bps-facturacion") SHOT = ROOT / "scripts/dokploy-shots" OUT = ROOT / ".ops" / "rls_inventory.txt" BASE = "https://dokploy.4ventures.es" SQL_FILE = ROOT / "sql/agent_runs_rls.sql" INVENTORY = ( "SELECT c.relname AS table_name, c.relrowsecurity AS rls_enabled " "FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace " "WHERE n.nspname = 'public' AND c.relkind = 'r' ORDER BY 1;" ) def env(key: str) -> str: import os v = os.environ.get(key, "").strip() if v: return v p = BPS / ".env" if not p.is_file(): return "" for line in p.read_text(encoding="utf-8").splitlines(): if line.startswith(f"{key}="): return line.split("=", 1)[1].strip().strip('"').strip("'") return "" def term_run(page, cmd: str, wait_ms: int = 4000) -> None: helper = page.locator("textarea.xterm-helper-textarea") if helper.count(): helper.first.click() page.keyboard.type(cmd, delay=2) page.keyboard.press("Enter") page.wait_for_timeout(wait_ms) def term_text(page) -> str: return page.evaluate( """() => { const rows = document.querySelector('.xterm-rows'); return rows ? rows.innerText : ''; }""" ) def main() -> int: email = env("DOKPLOY_EMAIL") or "marketing@4ventures.es" password = env("DOKPLOY_PASSWORD") if not password: print("NO_DOKPLOY_PASSWORD", flush=True) return 2 SHOT.mkdir(parents=True, exist_ok=True) OUT.parent.mkdir(parents=True, exist_ok=True) with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page(viewport={"width": 1600, "height": 1200}) page.goto(BASE + "/", wait_until="domcontentloaded", timeout=90000) page.wait_for_timeout(700) if "/dashboard" not in page.url: page.locator('input[type="email"], input[name="email"]').first.fill(email) page.locator('input[type="password"]').first.fill(password) page.locator('button[type="submit"]').first.click() page.wait_for_url(re.compile(r"/dashboard"), timeout=90000) print("login ok", flush=True) page.goto(f"{BASE}/dashboard/docker", wait_until="networkidle", timeout=120000) page.wait_for_timeout(1000) page.locator("input[placeholder*='ilter' i], input[placeholder*='earch' i]").first.fill( "supabase-db" ) page.wait_for_timeout(1200) page.keyboard.press("Escape") page.wait_for_timeout(300) page.get_by_role("button", name=re.compile(r"Open menu", re.I)).first.click() page.wait_for_timeout(600) page.get_by_text(re.compile(r"^Terminal$", re.I)).first.click() page.wait_for_timeout(3500) term_run(page, f'psql -U postgres -d postgres -c "{INVENTORY}" ; echo RLS_INV', 7000) text = term_text(page) OUT.write_text(text[-4000:], encoding="utf-8") print("inventory_written", flush=True) agent_on = bool(re.search(r"agent_runs\s+\|\s+t\b", text)) agent_off = bool(re.search(r"agent_runs\s+\|\s+f\b", text)) print("agent_runs_rls", "on" if agent_on else ("off" if agent_off else "unknown"), flush=True) if agent_on: print("OK_ALREADY", flush=True) browser.close() return 0 if not agent_off: print("PARSE_FAIL — see .ops/rls_inventory.txt", flush=True) page.screenshot(path=str(SHOT / "rls_parse_fail.png"), full_page=True) browser.close() return 4 # Apply SQL via base64 chunks b64 = base64.b64encode(SQL_FILE.read_text(encoding="utf-8").encode()).decode() chunks = [b64[i : i + 200] for i in range(0, len(b64), 200)] term_run(page, "rm -f /tmp/agent_rls.b64 /tmp/agent_rls.sql; echo READY", 2000) for i, ch in enumerate(chunks): op = ">" if i == 0 else ">>" term_run(page, f"printf '%s' '{ch}' {op} /tmp/agent_rls.b64", 800) term_run( page, "base64 -d /tmp/agent_rls.b64 > /tmp/agent_rls.sql && " "psql -U postgres -d postgres -v ON_ERROR_STOP=1 -f /tmp/agent_rls.sql; " "echo EXIT:$?", 8000, ) term_run(page, f'psql -U postgres -d postgres -c "{INVENTORY}" ; echo RLS_INV2', 7000) text2 = term_text(page) OUT.write_text(text2[-4000:], encoding="utf-8") page.screenshot(path=str(SHOT / "rls_after.png"), full_page=True) ok = bool(re.search(r"agent_runs\s+\|\s+t\b", text2)) print("agent_runs_after", "on" if ok else "still_off", flush=True) browser.close() return 0 if ok else 6 if __name__ == "__main__": raise SystemExit(main())