From ccf86c5b2e966d125f94649efab7cfc915fecab3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 3 Sep 2026 05:32:06 +0000 Subject: [PATCH] api: etiquetar fallos de arranque (postgres, redis, s3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tras Liquibase, Coolify marca api unhealthy si el fail-fast tira antes de Deno.serve. Los logs ahora dicen qué ping falló. Co-authored-by: alberto.martinez --- api/main.ts | 30 ++++++++++++++++++++++-------- docs/coolify.md | 14 ++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/api/main.ts b/api/main.ts index 614a157..002dfa9 100644 --- a/api/main.ts +++ b/api/main.ts @@ -1252,22 +1252,36 @@ registerPayrollRoutes(app); const port = config.port; +function startup(name: string, p: Promise): Promise { + return p.then(() => { + console.log(`[startup] ok ${name}`); + }).catch((e: unknown) => { + const msg = e instanceof Error ? e.message : String(e); + console.error(`[startup] FAIL ${name}: ${msg}`); + throw e; + }); +} + // Fail-fast (Fase 7): antes con SQLite la app "siempre arrancaba" (creaba // el archivo si no existía). Con Postgres/Redis, si alguna de las 5 // conexiones no responde al arrancar, es mejor fallar ruidosamente que // dejar que el primer request autenticado descubra el problema. await Promise.all([ - pingCoreDb(), - pingPlatformDb(), - pingRedis().then((r) => { - if (!r.iam || !r.core) throw new Error("Redis (iam/core) no responde"); - }), - pingStorage().then((s) => { - if (!s.ok) throw new Error("Storage (Contabo o disco local) no responde"); + startup("postgres core (DATABASE_URL_CORE)", pingCoreDb()), + startup("postgres platform (DATABASE_URL_PLATFORM)", pingPlatformDb()), + startup("redis iam+core", pingRedis().then((r) => { + if (!r.iam || !r.core) { + throw new Error( + `Redis no responde (iam=${r.iam} core=${r.core}). REDIS_URL_* debe usar el hostname de Redis URL (internal), no el UUID de Postgres.`, + ); + } + })), + startup("storage S3/R2", pingStorage().then((s) => { + if (!s.ok) throw new Error("Storage no responde (HeadBucket R2 o disco local)"); if (!config.isDev && !s.configured) { throw new Error("S3_ENDPOINT/S3_BUCKET/S3_* son obligatorios fuera de desarrollo"); } - }), + })), ]); Deno.serve({ port, hostname: "0.0.0.0" }, app.fetch); diff --git a/docs/coolify.md b/docs/coolify.md index f8c94a1..e844f4c 100644 --- a/docs/coolify.md +++ b/docs/coolify.md @@ -120,6 +120,20 @@ Ejemplo: `sudo docker logs migrate-pdyrt8ccp804tfmutefau0k6-052025710094`. `Unkn El `docker stop … No such container` del helper de Coolify es ruido de limpieza, no la causa. El build de imágenes puede ser OK y el deploy igual falla en `migrate`. +### `dependency api failed to start` / `api is unhealthy` + +Liquibase ya corrió si ves `migrate … Exited` y acto seguido `api … Starting`. La API **no llega a escuchar** si Redis, S3 o los roles `_app` fallan (fail-fast antes de `Deno.serve`). Coolify entonces marca unhealthy en 1–2 s. + +```bash +sudo docker logs api-- +``` + +Busca `[startup] FAIL …`. Causas típicas: + +- **Redis:** `REDIS_URL_IAM` / `REDIS_URL_CORE` con el UUID de **Postgres**. Redis tiene el suyo (Redis URL internal). +- **S3/R2:** faltan `S3_*` o HeadBucket falla (token Account API, Object Read & Write, bucket correcto, `S3_REGION=auto`). +- **Postgres `_app`:** Liquibase usa `*_OWNER`; el runtime usa `DATABASE_URL_PLATFORM`, `DATABASE_URL_IAM`, `DATABASE_URL_CORE` (passwords distintos). + ### Coolify pide `PLATFORM_OWNER_PASSWORD`, `IAM_APP_PASSWORD`, `*_REDIS_PASSWORD`… Eso sale de **Reload Compose** mezclando el overlay de desarrollo. En producción **no las rellenes**. En el recurso: Compose file = `docker-compose.yml` únicamente. Borra esas 8 variables si Coolify las marcó Required. Siguen haciendo falta las URLs completas (`DATABASE_URL_*`, `REDIS_URL_*`).