#!/usr/bin/env python3 """Ensure / refresh Dokploy schedule for pipeline Meta publish (every 5 min).""" from __future__ import annotations import json 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") BASE = "https://dokploy.4ventures.es" APP_ID = "NZS797t9Rnq9Pj4mHiii3" SCHEDULE_NAME = "pipeline-publish" CRON_EXPR = "*/5 * * * *" # Public URL + env expansion via bash -lc so CRON_SECRET from app env is used. CMD = ( "bash -lc " "'curl -sS -H \"Authorization: Bearer $CRON_SECRET\" " "\"https://app.4ventures.es/api/cron/pipeline-publish?limit=20\"'" ) def cred(key: str) -> str: import os v = os.environ.get(key, "").strip() if v: return v for p in (BPS / ".env", ROOT / ".env.local"): if not p.is_file(): continue 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: email = cred("DOKPLOY_EMAIL") or "marketing@4ventures.es" password = cred("DOKPLOY_PASSWORD") if not password: print("missing DOKPLOY_PASSWORD", flush=True) return 2 with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page(viewport={"width": 1500, "height": 1200}) page.goto(BASE + "/", wait_until="domcontentloaded", timeout=90000) page.wait_for_timeout(600) 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) result = page.evaluate( """async ({ appId, name, cron, command }) => { const post = async (path, body) => { const r = await fetch(path, { method: 'POST', credentials: 'include', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), }); const text = await r.text(); let json = null; try { json = JSON.parse(text); } catch {} return { ok: r.ok, status: r.status, text: text.slice(0, 1200), json }; }; // Delete existing schedules with same name for (const listPath of [ '/api/schedule.all', '/api/schedule.byApplicationId', ]) { const listed = await post(listPath, { applicationId: appId }); const rows = Array.isArray(listed.json) ? listed.json : listed.json?.data || listed.json?.schedules || []; if (Array.isArray(rows)) { for (const row of rows) { const n = row?.name || row?.scheduleName; const id = row?.scheduleId || row?.id; if (n === name && id) { await post('/api/schedule.delete', { scheduleId: id }); await post('/api/schedule.remove', { scheduleId: id }); } } } } const created = await post('/api/schedule.create', { applicationId: appId, name, cron, cronExpression: cron, command, shellType: 'bash', enabled: true, scheduleType: 'application', }); return { created }; }""", { "appId": APP_ID, "name": SCHEDULE_NAME, "cron": CRON_EXPR, "command": CMD, }, ) print(json.dumps(result, indent=2)[:4000], flush=True) ok = bool(result.get("created", {}).get("ok")) browser.close() return 0 if ok else 1 if __name__ == "__main__": raise SystemExit(main())