mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 12:43:18 +00:00
ops: generar accesos por ambiente y verificar Postgres, Redis y Contabo
Scripts create-accesses / verify-connectivity para un Postgres+Redis+bucket por ambiente. /v1/health y el arranque de la API incluyen sonda de storage. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
This commit is contained in:
parent
d36c287a82
commit
1ac04996b6
8 changed files with 351 additions and 12 deletions
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
|
|
@ -83,6 +83,18 @@ jobs:
|
|||
REDIS_URL_CORE: redis://panels_core_redis:ci-core-redis@localhost:6379
|
||||
run: ./db/provision/verify-isolation.sh
|
||||
|
||||
- name: Verificar conectividad Postgres/Redis (S3 opcional en CI)
|
||||
env:
|
||||
DATABASE_URL_PLATFORM: postgresql://panels_platform_app:ci-platform-app@localhost:5432/panels_platform
|
||||
DATABASE_URL_PLATFORM_OWNER: postgresql://panels_platform_owner:ci-platform-owner@localhost:5432/panels_platform
|
||||
DATABASE_URL_IAM: postgresql://panels_iam_app:ci-iam-app@localhost:5432/panels_product
|
||||
DATABASE_URL_IAM_OWNER: postgresql://panels_iam_owner:ci-iam-owner@localhost:5432/panels_product
|
||||
DATABASE_URL_CORE: postgresql://panels_core_app:ci-core-app@localhost:5432/panels_product
|
||||
DATABASE_URL_CORE_OWNER: postgresql://panels_core_owner:ci-core-owner@localhost:5432/panels_product
|
||||
REDIS_URL_IAM: redis://panels_iam_redis:ci-iam-redis@localhost:6379
|
||||
REDIS_URL_CORE: redis://panels_core_redis:ci-core-redis@localhost:6379
|
||||
run: ./db/provision/verify-connectivity.sh
|
||||
|
||||
- name: deno check
|
||||
working-directory: api
|
||||
run: deno check main.ts
|
||||
|
|
|
|||
16
api/main.ts
16
api/main.ts
|
|
@ -61,7 +61,7 @@ import {
|
|||
lineAmount,
|
||||
listBudget,
|
||||
} from "./budget.ts";
|
||||
import { companyDocKey, getObject, projectDocKey, workerDocKey } from "./storage.ts";
|
||||
import { companyDocKey, getObject, pingStorage, projectDocKey, workerDocKey } from "./storage.ts";
|
||||
import { cacheCore, cacheKeyCore } from "./cache.ts";
|
||||
import { pingRedis } from "./redis.ts";
|
||||
|
||||
|
|
@ -105,13 +105,15 @@ app.use(
|
|||
// Postgres/Redis, /v1/health de verdad toca las tres conexiones Postgres y
|
||||
// las dos de Redis, no solo responde estático.
|
||||
app.get("/v1/health", async (c) => {
|
||||
const [core, platform, redis] = await Promise.all([
|
||||
const [core, platform, redis, storage] = await Promise.all([
|
||||
pingCoreDb().then(() => true).catch(() => false),
|
||||
pingPlatformDb().then(() => true).catch(() => false),
|
||||
pingRedis(),
|
||||
pingStorage(),
|
||||
]);
|
||||
const ok = core && platform && redis.iam && redis.core;
|
||||
return c.json({ ok, core, platform, redis }, ok ? 200 : 503);
|
||||
const storageOk = storage.ok && (storage.backend === "local" || storage.configured);
|
||||
const ok = core && platform && redis.iam && redis.core && storageOk;
|
||||
return c.json({ ok, core, platform, redis, storage }, ok ? 200 : 503);
|
||||
});
|
||||
|
||||
app.post("/v1/auth/login", async (c) => {
|
||||
|
|
@ -1260,6 +1262,12 @@ await Promise.all([
|
|||
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");
|
||||
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);
|
||||
|
|
|
|||
35
api/scripts/verify-storage.ts
Normal file
35
api/scripts/verify-storage.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Sonda de Contabo (S3): HeadBucket + put/get/delete.
|
||||
*
|
||||
* Desde api/:
|
||||
* deno run --allow-net --allow-env --allow-read --allow-write scripts/verify-storage.ts
|
||||
*
|
||||
* Requiere S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY.
|
||||
* Si REQUIRE_S3=1 y no hay credenciales, sale con error (staging/prod).
|
||||
* Si no están, sale 0 y avisa (dev local con disco).
|
||||
*/
|
||||
import { config } from "../config.ts";
|
||||
import { pingStorage, probeStorageReadWrite, s3Configured } from "../storage.ts";
|
||||
|
||||
const requireS3 = ["1", "true", "yes"].includes((Deno.env.get("REQUIRE_S3") ?? "").toLowerCase());
|
||||
|
||||
if (!s3Configured()) {
|
||||
const msg =
|
||||
"S3 no configurado (S3_ENDPOINT / S3_BUCKET / S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY).";
|
||||
if (requireS3) {
|
||||
console.error(`FAIL - ${msg} Obligatorio en staging/producción.`);
|
||||
Deno.exit(1);
|
||||
}
|
||||
console.log(`SKIP - ${msg} En este ambiente se usará disco local.`);
|
||||
Deno.exit(0);
|
||||
}
|
||||
|
||||
console.log(`S3 endpoint=${config.s3Endpoint} bucket=${config.s3Bucket} region=${config.s3Region}`);
|
||||
const ping = await pingStorage();
|
||||
if (!ping.ok) {
|
||||
console.error("FAIL - HeadBucket: no se pudo alcanzar el bucket (credenciales, red o nombre).");
|
||||
Deno.exit(1);
|
||||
}
|
||||
console.log("OK - HeadBucket");
|
||||
await probeStorageReadWrite();
|
||||
console.log("OK - put/get/delete de objeto sonda");
|
||||
|
|
@ -1,4 +1,10 @@
|
|||
import { S3Client, PutObjectCommand, GetObjectCommand } from "npm:@aws-sdk/client-s3@3";
|
||||
import {
|
||||
S3Client,
|
||||
PutObjectCommand,
|
||||
GetObjectCommand,
|
||||
HeadBucketCommand,
|
||||
DeleteObjectCommand,
|
||||
} from "npm:@aws-sdk/client-s3@3";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { config, DATA_DIR } from "./config.ts";
|
||||
|
|
@ -20,10 +26,48 @@ import { config, DATA_DIR } from "./config.ts";
|
|||
|
||||
let client: S3Client | null = null;
|
||||
|
||||
function s3Configured(): boolean {
|
||||
export function s3Configured(): boolean {
|
||||
return !!(config.s3Endpoint && config.s3Bucket && config.s3AccessKeyId && config.s3SecretAccessKey);
|
||||
}
|
||||
|
||||
export type StoragePing = {
|
||||
configured: boolean;
|
||||
ok: boolean;
|
||||
backend: "s3" | "local";
|
||||
};
|
||||
|
||||
/** HeadBucket (S3) o escritura de prueba en disco local. */
|
||||
export async function pingStorage(): Promise<StoragePing> {
|
||||
if (!s3Configured()) {
|
||||
try {
|
||||
await mkdir(join(DATA_DIR, "local-objects"), { recursive: true });
|
||||
return { configured: false, ok: true, backend: "local" };
|
||||
} catch {
|
||||
return { configured: false, ok: false, backend: "local" };
|
||||
}
|
||||
}
|
||||
try {
|
||||
await getClient().send(new HeadBucketCommand({ Bucket: config.s3Bucket }));
|
||||
return { configured: true, ok: true, backend: "s3" };
|
||||
} catch {
|
||||
return { configured: true, ok: false, backend: "s3" };
|
||||
}
|
||||
}
|
||||
|
||||
/** Put + get + delete de un objeto sonda. No dejar basura en el bucket. */
|
||||
export async function probeStorageReadWrite(): Promise<void> {
|
||||
const key = `_panels/connectivity-probe-${crypto.randomUUID()}`;
|
||||
const payload = new TextEncoder().encode("panels-connectivity-probe");
|
||||
await putObject(key, payload);
|
||||
const got = await getObject(key);
|
||||
if (new TextDecoder().decode(got) !== "panels-connectivity-probe") {
|
||||
throw new Error("El objeto sonda no coincide con lo escrito");
|
||||
}
|
||||
if (s3Configured()) {
|
||||
await getClient().send(new DeleteObjectCommand({ Bucket: config.s3Bucket, Key: key }));
|
||||
}
|
||||
}
|
||||
|
||||
function getClient(): S3Client {
|
||||
if (!client) {
|
||||
client = new S3Client({
|
||||
|
|
|
|||
|
|
@ -60,6 +60,35 @@ El script verifica automáticamente que cruzar de módulo devuelva `NOPERM`
|
|||
(ver "Riesgos y mitigaciones" del plan: un prefijo de llave sin ACL detrás
|
||||
no aísla nada).
|
||||
|
||||
## Accesos de un ambiente (Coolify + Contabo)
|
||||
|
||||
**1 Postgres + 1 Redis + 1 bucket por ambiente.** El servidor lo crea
|
||||
Coolify/Contabo; este repo solo genera roles y valida que contesten.
|
||||
|
||||
1. En Coolify: recurso Postgres y recurso Redis de **ese** ambiente.
|
||||
En Contabo: bucket (ej. `panels-prod` / `panels-staging`) + access key.
|
||||
2. Generar secretos y crear roles/ACLs contra esos hosts:
|
||||
|
||||
```bash
|
||||
export PGHOST=... PGUSER=postgres PGPASSWORD=... # admin que da Coolify
|
||||
export REDIS_ADMIN_URL="redis://:...@host:6379"
|
||||
export S3_ENDPOINT=https://usc1.contabostorage.com
|
||||
export S3_BUCKET=panels-prod
|
||||
export S3_ACCESS_KEY_ID=... S3_SECRET_ACCESS_KEY=...
|
||||
./db/provision/create-accesses.sh --apply --verify --out .env.prod.local
|
||||
```
|
||||
|
||||
3. Pegar el contenido de `.env.prod.local` (gitignored) en las env del `api`.
|
||||
4. Sin `--apply`, el script solo imprime el bloque; no toca servidores.
|
||||
|
||||
Comprobar conectividad después, sin reprovisionar:
|
||||
|
||||
```bash
|
||||
set -a && source .env.prod.local && set +a
|
||||
REQUIRE_S3=1 ./db/provision/verify-connectivity.sh # staging/prod
|
||||
./db/provision/verify-isolation.sh
|
||||
```
|
||||
|
||||
## Qué falta hacer manualmente en Coolify (staging/producción)
|
||||
|
||||
Estos scripts asumen que ya existe un servidor Postgres y un servidor Redis
|
||||
|
|
@ -68,9 +97,10 @@ tiene acceso a la cuenta de Coolify del usuario, así que:
|
|||
|
||||
1. Crear el recurso Postgres gestionado en Coolify para el ambiente.
|
||||
2. Crear el recurso Redis gestionado en Coolify para el ambiente.
|
||||
3. Correr estos scripts contra ambos usando las credenciales admin que da Coolify.
|
||||
4. Cargar los secrets resultantes (`DATABASE_URL_*`, `REDIS_URL_*`) en la
|
||||
configuración del servicio `api` de ese ambiente.
|
||||
3. Crear el bucket Contabo de ese ambiente.
|
||||
4. Correr `create-accesses.sh --apply --verify` (o los SQL 01-04 + `05-redis-acl.sh`).
|
||||
5. Cargar los secrets resultantes (`DATABASE_URL_*`, `REDIS_URL_*`, `S3_*`)
|
||||
en la configuración del servicio `api` de ese ambiente.
|
||||
|
||||
## Desarrollo local
|
||||
|
||||
|
|
|
|||
131
db/provision/create-accesses.sh
Executable file
131
db/provision/create-accesses.sh
Executable file
|
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env bash
|
||||
# PANELS · genera secretos de UN ambiente y, si hay superusuario, crea
|
||||
# roles/bases/ACLs. No crea el servidor Postgres, Redis ni el bucket:
|
||||
# esos se levantan en Coolify / Contabo; este script solo deja los accesos.
|
||||
#
|
||||
# Uso (solo imprimir bloque .env, sin tocar servidores):
|
||||
# PGHOST=pg.interno REDIS_HOST=redis.interno S3_BUCKET=panels-prod \
|
||||
# ./db/provision/create-accesses.sh
|
||||
#
|
||||
# Uso (crear roles en un Postgres/Redis ya levantados):
|
||||
# PGHOST=... PGUSER=postgres PGPASSWORD=... REDIS_ADMIN_URL=redis://:...@host:6379 \
|
||||
# ./db/provision/create-accesses.sh --apply --verify
|
||||
#
|
||||
# Flags:
|
||||
# --apply corre 01-04 SQL + 05 Redis ACL con las claves generadas
|
||||
# --verify corre verify-connectivity.sh al final (implica tener URLs)
|
||||
# --out FILE escribe el bloque .env (FILE debe estar gitignored, ej. .env.prod.local)
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
APPLY=0
|
||||
VERIFY=0
|
||||
OUT=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--apply) APPLY=1; shift ;;
|
||||
--verify) VERIFY=1; shift ;;
|
||||
--out) OUT="$2"; shift 2 ;;
|
||||
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||||
*) echo "Flag desconocido: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
rand24() { openssl rand -hex 24; }
|
||||
rand32() { openssl rand -hex 32; }
|
||||
|
||||
PGHOST="${PGHOST:-127.0.0.1}"
|
||||
PGPORT="${PGPORT:-5432}"
|
||||
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
PGUSER="${PGUSER:-postgres}"
|
||||
|
||||
PLATFORM_OWNER_PASSWORD="${PLATFORM_OWNER_PASSWORD:-$(rand24)}"
|
||||
PLATFORM_APP_PASSWORD="${PLATFORM_APP_PASSWORD:-$(rand24)}"
|
||||
IAM_OWNER_PASSWORD="${IAM_OWNER_PASSWORD:-$(rand24)}"
|
||||
IAM_APP_PASSWORD="${IAM_APP_PASSWORD:-$(rand24)}"
|
||||
CORE_OWNER_PASSWORD="${CORE_OWNER_PASSWORD:-$(rand24)}"
|
||||
CORE_APP_PASSWORD="${CORE_APP_PASSWORD:-$(rand24)}"
|
||||
IAM_REDIS_PASSWORD="${IAM_REDIS_PASSWORD:-$(rand24)}"
|
||||
CORE_REDIS_PASSWORD="${CORE_REDIS_PASSWORD:-$(rand24)}"
|
||||
SESSION_SECRET="${SESSION_SECRET:-$(rand32)}"
|
||||
DOCS_KEY="${DOCS_KEY:-$(rand32)}"
|
||||
|
||||
block() {
|
||||
cat <<EOF
|
||||
# PANELS · secretos de un solo ambiente (no mezclar prod/staging/dev)
|
||||
SESSION_SECRET=${SESSION_SECRET}
|
||||
DOCS_KEY=${DOCS_KEY}
|
||||
|
||||
DATABASE_URL_PLATFORM=postgresql://panels_platform_app:${PLATFORM_APP_PASSWORD}@${PGHOST}:${PGPORT}/panels_platform
|
||||
DATABASE_URL_PLATFORM_OWNER=postgresql://panels_platform_owner:${PLATFORM_OWNER_PASSWORD}@${PGHOST}:${PGPORT}/panels_platform
|
||||
DATABASE_URL_IAM=postgresql://panels_iam_app:${IAM_APP_PASSWORD}@${PGHOST}:${PGPORT}/panels_product
|
||||
DATABASE_URL_IAM_OWNER=postgresql://panels_iam_owner:${IAM_OWNER_PASSWORD}@${PGHOST}:${PGPORT}/panels_product
|
||||
DATABASE_URL_CORE=postgresql://panels_core_app:${CORE_APP_PASSWORD}@${PGHOST}:${PGPORT}/panels_product
|
||||
DATABASE_URL_CORE_OWNER=postgresql://panels_core_owner:${CORE_OWNER_PASSWORD}@${PGHOST}:${PGPORT}/panels_product
|
||||
|
||||
REDIS_URL_IAM=redis://panels_iam_redis:${IAM_REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}
|
||||
REDIS_URL_CORE=redis://panels_core_redis:${CORE_REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}
|
||||
|
||||
# Contabo -- rellenar endpoint/keys del Object Storage de ESTE ambiente
|
||||
S3_ENDPOINT=${S3_ENDPOINT:-}
|
||||
S3_BUCKET=${S3_BUCKET:-}
|
||||
S3_REGION=${S3_REGION:-usc1}
|
||||
S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID:-}
|
||||
S3_SECRET_ACCESS_KEY=${S3_SECRET_ACCESS_KEY:-}
|
||||
|
||||
# Passwords sueltos (provision / compose local)
|
||||
POSTGRES_SUPERUSER_PASSWORD=${PGPASSWORD:-}
|
||||
PLATFORM_OWNER_PASSWORD=${PLATFORM_OWNER_PASSWORD}
|
||||
PLATFORM_APP_PASSWORD=${PLATFORM_APP_PASSWORD}
|
||||
IAM_OWNER_PASSWORD=${IAM_OWNER_PASSWORD}
|
||||
IAM_APP_PASSWORD=${IAM_APP_PASSWORD}
|
||||
CORE_OWNER_PASSWORD=${CORE_OWNER_PASSWORD}
|
||||
CORE_APP_PASSWORD=${CORE_APP_PASSWORD}
|
||||
IAM_REDIS_PASSWORD=${IAM_REDIS_PASSWORD}
|
||||
CORE_REDIS_PASSWORD=${CORE_REDIS_PASSWORD}
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "$APPLY" == "1" ]]; then
|
||||
: "${PGPASSWORD:?PGPASSWORD del superusuario es obligatorio con --apply}"
|
||||
SUPERUSER_URL="postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/postgres"
|
||||
echo "== Aplicando roles/bases/esquemas en ${PGHOST}:${PGPORT} =="
|
||||
psql "$SUPERUSER_URL" \
|
||||
-v platform_owner_pw="$PLATFORM_OWNER_PASSWORD" -v platform_app_pw="$PLATFORM_APP_PASSWORD" \
|
||||
-v iam_owner_pw="$IAM_OWNER_PASSWORD" -v iam_app_pw="$IAM_APP_PASSWORD" \
|
||||
-v core_owner_pw="$CORE_OWNER_PASSWORD" -v core_app_pw="$CORE_APP_PASSWORD" \
|
||||
-f "$HERE/01-roles.sql"
|
||||
psql "$SUPERUSER_URL" -f "$HERE/02-databases.sql"
|
||||
psql "postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/panels_platform" -f "$HERE/03-platform-database.sql"
|
||||
psql "postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/panels_product" -f "$HERE/04-product-database.sql"
|
||||
if [[ -n "${REDIS_ADMIN_URL:-}" ]]; then
|
||||
echo "== Aplicando ACLs Redis =="
|
||||
REDIS_ADMIN_URL="$REDIS_ADMIN_URL" \
|
||||
IAM_REDIS_PASSWORD="$IAM_REDIS_PASSWORD" \
|
||||
CORE_REDIS_PASSWORD="$CORE_REDIS_PASSWORD" \
|
||||
"$HERE/05-redis-acl.sh"
|
||||
else
|
||||
echo "SKIP - REDIS_ADMIN_URL no definido; no se crearon usuarios ACL"
|
||||
fi
|
||||
fi
|
||||
|
||||
ENV_TEXT="$(block)"
|
||||
echo "$ENV_TEXT"
|
||||
if [[ -n "$OUT" ]]; then
|
||||
umask 077
|
||||
printf '%s\n' "$ENV_TEXT" > "$OUT"
|
||||
echo "Escrito $OUT (permisos 600). No lo subas a git." >&2
|
||||
fi
|
||||
|
||||
if [[ "$VERIFY" == "1" ]]; then
|
||||
echo "== Verificando conectividad ==" >&2
|
||||
tmp="$(mktemp)"
|
||||
umask 077
|
||||
printf '%s\n' "$ENV_TEXT" > "$tmp"
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$tmp"
|
||||
set +a
|
||||
rm -f "$tmp"
|
||||
"$HERE/verify-connectivity.sh"
|
||||
fi
|
||||
79
db/provision/verify-connectivity.sh
Executable file
79
db/provision/verify-connectivity.sh
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env bash
|
||||
# PANELS · comprueba que ESTE ambiente responde: Postgres (6 roles),
|
||||
# Redis (2 ACL) y Contabo (opcional; obligatorio si REQUIRE_S3=1).
|
||||
#
|
||||
# No crea recursos. Carga URLs desde el entorno (source .env.dev-local o
|
||||
# las vars de Coolify).
|
||||
#
|
||||
# Uso:
|
||||
# set -a && source .env.dev-local && set +a
|
||||
# ./db/provision/verify-connectivity.sh
|
||||
# REQUIRE_S3=1 ./db/provision/verify-connectivity.sh # staging/prod
|
||||
set -uo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
fail=0
|
||||
check() {
|
||||
local desc="$1"; shift
|
||||
if "$@" >/tmp/panels-conn-out.$$ 2>&1; then
|
||||
echo "OK - $desc"
|
||||
else
|
||||
echo "FAIL - $desc"
|
||||
cat /tmp/panels-conn-out.$$
|
||||
fail=1
|
||||
fi
|
||||
rm -f /tmp/panels-conn-out.$$
|
||||
}
|
||||
|
||||
need() {
|
||||
local name="$1"
|
||||
if [[ -z "${!name:-}" ]]; then
|
||||
echo "FAIL - falta $name"
|
||||
fail=1
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "== Postgres =="
|
||||
need DATABASE_URL_PLATFORM && check "platform_app SELECT 1" psql "$DATABASE_URL_PLATFORM" -c "SELECT 1;"
|
||||
need DATABASE_URL_IAM && check "iam_app SELECT 1" psql "$DATABASE_URL_IAM" -c "SELECT 1;"
|
||||
need DATABASE_URL_CORE && check "core_app SELECT 1" psql "$DATABASE_URL_CORE" -c "SELECT 1;"
|
||||
if [[ -n "${DATABASE_URL_PLATFORM_OWNER:-}" ]]; then
|
||||
check "platform_owner SELECT 1" psql "$DATABASE_URL_PLATFORM_OWNER" -c "SELECT 1;"
|
||||
else
|
||||
echo "SKIP - DATABASE_URL_PLATFORM_OWNER (solo hace falta para Liquibase)"
|
||||
fi
|
||||
if [[ -n "${DATABASE_URL_IAM_OWNER:-}" ]]; then
|
||||
check "iam_owner SELECT 1" psql "$DATABASE_URL_IAM_OWNER" -c "SELECT 1;"
|
||||
else
|
||||
echo "SKIP - DATABASE_URL_IAM_OWNER"
|
||||
fi
|
||||
if [[ -n "${DATABASE_URL_CORE_OWNER:-}" ]]; then
|
||||
check "core_owner SELECT 1" psql "$DATABASE_URL_CORE_OWNER" -c "SELECT 1;"
|
||||
else
|
||||
echo "SKIP - DATABASE_URL_CORE_OWNER"
|
||||
fi
|
||||
|
||||
echo "== Redis =="
|
||||
need REDIS_URL_IAM && check "iam redis PING" redis-cli -u "$REDIS_URL_IAM" PING
|
||||
need REDIS_URL_CORE && check "core redis PING" redis-cli -u "$REDIS_URL_CORE" PING
|
||||
|
||||
echo "== Contabo (S3) =="
|
||||
if command -v deno >/dev/null 2>&1; then
|
||||
if (cd "$ROOT/api" && REQUIRE_S3="${REQUIRE_S3:-}" deno run --allow-net --allow-env --allow-read --allow-write scripts/verify-storage.ts); then
|
||||
:
|
||||
else
|
||||
fail=1
|
||||
fi
|
||||
else
|
||||
echo "FAIL - deno no está en PATH; no se pudo probar el bucket"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ "$fail" == "0" ]]; then
|
||||
echo "Conectividad de este ambiente: OK"
|
||||
else
|
||||
echo "Hay fallos de conectividad -- no desplegar la API contra este ambiente todavía."
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -81,8 +81,8 @@ En Coolify el servicio de PANELS se llama **`web-panel`** (FQDN ej. `panels.mrde
|
|||
|
||||
## Paso a paso en Coolify
|
||||
|
||||
1. **Provisionar Postgres y Redis** como recursos gestionados de Coolify para el ambiente (uno de cada, no por módulo).
|
||||
2. **Aprovisionar roles/esquemas/ACLs**: correr los scripts de [`db/provision/`](../db/provision/README.md) contra ese Postgres/Redis (una vez, desde tu máquina o un job manual -- Coolify no lo hace por ti).
|
||||
1. **Provisionar Postgres, Redis y bucket** — 1 de cada **por ambiente** (no por módulo; no compartir prod con staging).
|
||||
2. **Crear accesos y validar**: `./db/provision/create-accesses.sh --apply --verify --out .env.<env>.local` contra esos hosts (ver [`db/provision/README.md`](../db/provision/README.md)). Coolify no crea los roles `panels_*` ni los ACL de Redis solo.
|
||||
3. **Aplicar Liquibase** (paso explícito, NO ocurre al arrancar la app): `./db/update.sh all --context-filter='!dev'` con las credenciales `_owner`. En staging/producción, **nunca** olvidar el `--context-filter` -- sin él, Liquibase corre TAMBIÉN los changesets de demo (`context=dev`).
|
||||
4. **Bootstrap del primer admin**: `deno run ... api/scripts/bootstrap-admin.ts platform` y `... tenant --tenant-id=... --company-code=...` (ver `db/README.md`).
|
||||
5. **Push** este repo (sin `.env` ni `data/`).
|
||||
|
|
@ -91,7 +91,7 @@ En Coolify el servicio de PANELS se llama **`web-panel`** (FQDN ej. `panels.mrde
|
|||
8. **Dominios:** `web-panel` → panels; `web-saas` → saas; `api` sin FQDN (proxy `/v1`).
|
||||
9. Cargar en Coolify todas las variables **obligatorias** de la tabla de arriba + `COOKIE_SECURE=true`.
|
||||
10. Deploy.
|
||||
11. Verificar `https://app…/v1/health` → debe responder `{"ok":true,"core":true,"platform":true,"redis":{"iam":true,"core":true}}`. Si algo es `false`, la app ni siquiera debería haber arrancado (fail-fast, Fase 7).
|
||||
11. Verificar `https://app…/v1/health` → `ok`, `core`, `platform`, `redis.iam`, `redis.core` y `storage` (en prod `backend:"s3"`). Si algo falla, la API no arranca.
|
||||
12. Login SaaS `admin` / tu `SEED_PASSWORD`.
|
||||
13. SMTP en `/smtp` o por `SMTP_*`.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue