Aprendido desplegando cotizador (Next.js+FastAPI+Postgres, Docker Compose, repo privado) en coolify.urieljareth.org (v4.1.2): - Documenta que /applications/* devuelve 404 en esta instancia y que configurar apps git build-from-source solo es posible por la UI web. - Nuevos scripts scripts/coolify-ui/: coolify-login.mjs (guarda storageState) y Configure-CoolifyComposeApp.mjs (build pack -> Docker Compose, Reload Compose File, env vars por Developer view, dominios por servicio). - references/coolify-4.1.2-notes.md: mapa de API funcional/404, gotchas (BOM UTF-8 rompe el parser YAML de Coolify; Reload Compose File obligatorio; pin de dominios por servicio o 503; no encolar deploys concurrentes; PowerShell [IO.File]::ReadAllText para payloads de llaves). - env.local.template.ps1: COOLIFY_EMAIL/COOLIFY_PASSWORD para la UI. - SKILL.md/TOOLS.md: seccion "instance reality" y flujo UI paso a paso. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
41 lines
2.6 KiB
JavaScript
41 lines
2.6 KiB
JavaScript
// coolify-login.mjs — log into the Coolify web UI and save a Playwright storageState.
|
|
// Needed because this Coolify instance (v4.1.2) does NOT expose the /applications/*
|
|
// REST API (all 404), so application config must be driven through the UI.
|
|
//
|
|
// Usage (from the manager repo root, env loaded via .env.local.ps1):
|
|
// node deploy_skill/scripts/coolify-ui/coolify-login.mjs <out-state.json> [shot.png]
|
|
// Env:
|
|
// CF_EMAIL / CF_PASS credentials (map from $env:COOLIFY_EMAIL / $env:COOLIFY_PASSWORD)
|
|
// CF_BASE base URL, default https://coolify.urieljareth.org
|
|
// CF_PW_BASE path to a package.json whose node_modules has playwright
|
|
// (default: this manager repo)
|
|
import { createRequire } from 'module';
|
|
const require = createRequire(process.env.CF_PW_BASE || 'H:/MegaSync/Proyectos/Proxmox & Coolify Manager/package.json');
|
|
const { chromium } = require('playwright');
|
|
|
|
const BASE = (process.env.CF_BASE || 'https://coolify.urieljareth.org').replace(/\/$/, '');
|
|
const EMAIL = process.env.CF_EMAIL, PASS = process.env.CF_PASS;
|
|
const OUT = process.argv[2] || 'coolify_state.json';
|
|
const SHOT = process.argv[3] || 'coolify_login.png';
|
|
if (!EMAIL || !PASS) { console.error('Missing CF_EMAIL/CF_PASS'); process.exit(2); }
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const ctx = await browser.newContext({ viewport: { width: 1600, height: 1000 }, ignoreHTTPSErrors: true });
|
|
const page = await ctx.newPage();
|
|
try {
|
|
await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded', timeout: 45000 });
|
|
await page.waitForTimeout(1500);
|
|
for (const s of ['input[type="email"]','input[name="email"]','#email']) { const el = await page.$(s); if (el) { await el.fill(EMAIL); break; } }
|
|
for (const s of ['input[type="password"]','input[name="password"]','#password']) { const el = await page.$(s); if (el) { await el.fill(PASS); break; } }
|
|
const btn = await page.$('button[type="submit"]') || await page.$('button:has-text("Login")');
|
|
if (btn) await btn.click(); else await page.keyboard.press('Enter');
|
|
await page.waitForTimeout(4000);
|
|
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(()=>{});
|
|
const ok = !/\/login/.test(page.url());
|
|
console.log('LOGIN_OK:', ok, 'url:', page.url());
|
|
await page.screenshot({ path: SHOT }).catch(()=>{});
|
|
if (ok) { await ctx.storageState({ path: OUT }); console.log('storageState ->', OUT); process.exitCode = 0; }
|
|
else { console.log('login failed'); process.exitCode = 1; }
|
|
} catch (e) { console.error('ERR', e.message); process.exitCode = 1; }
|
|
finally { await browser.close(); }
|