#!/usr/bin/env python3 """Dump Traefik gitea.yml + n8n-hardened.yml + middlewares for harden prep.""" from __future__ import annotations import re import sys from pathlib import Path from playwright.sync_api import sync_playwright BPS = Path("/Users/4ventures/Projects/bps-facturacion") OUT = Path("/Users/4ventures/4ventures-client-portal/.ops/traefik-dumps") SHOT = Path("/Users/4ventures/4ventures-client-portal/scripts/dokploy-shots") BASE = "https://dokploy.4ventures.es" FILES = ("middlewares.yml", "middleware-hardening.yml", "gitea.yml", "n8n-hardened.yml") 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 main() -> int: OUT.mkdir(parents=True, exist_ok=True) email = env("DOKPLOY_EMAIL") or "marketing@4ventures.es" password = env("DOKPLOY_PASSWORD") 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/traefik", wait_until="networkidle", timeout=120000) page.wait_for_timeout(1000) page.get_by_text(re.compile(r"^dynamic$", re.I)).first.click() page.wait_for_timeout(800) for name in FILES: page.locator(f"text={name}").first.dblclick() page.wait_for_timeout(1500) # Unlock not needed for read; try extract from body line numbers area body = page.inner_text("body") # Heuristic: capture from 'http:' or 'http\n' yaml start m = re.search(r"(?ms)^(http:|tcp:|udp:|tls:).*(?=Unlock|Skip YAML|Update\n|$)", body) # Better: get cm content text = page.evaluate( """() => { const rows = document.querySelector('.cm-content'); return rows ? rows.innerText : ''; }""" ) if not text or len(text) < 20: # fallback strip UI chrome lines = [] capture = False for ln in body.splitlines(): if ln.strip() in ("http:", "tcp:", "tls:") or ln.startswith("http:"): capture = True if capture: if ln.strip() in ("Unlock", "Update", "Skip YAML validation (for Go templating)"): break lines.append(ln) text = "\n".join(lines) (OUT / name).write_text(text + "\n", encoding="utf-8") print("dumped", name, "chars", len(text), flush=True) page.screenshot(path=str(SHOT / f"dump_{name.replace('.','_')}.png"), full_page=True) browser.close() return 0 if __name__ == "__main__": raise SystemExit(main())