deploy_skill: soporte para Coolify v4.1.2 (API /applications 404) + flujo UI Playwright

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]>
This commit is contained in:
urieljareth
2026-07-08 00:42:12 -06:00
co-authored by Claude Opus 4.8
parent cd998ce6b0
commit e7d1c33cd5
22 changed files with 2935 additions and 0 deletions
@@ -0,0 +1,40 @@
// 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(); }