diff --git a/.env.example b/.env.example index 2342dbf..67fb668 100644 --- a/.env.example +++ b/.env.example @@ -66,12 +66,12 @@ IAM_REDIS_PASSWORD= CORE_REDIS_PASSWORD= # ============================================================================= -# Contabo Object Storage (Fase 4c) -- expedientes/PDFs/logos cifrados. -# Sin esto, cae a disco local bajo el volumen panel-data (solo dev; en -# producción un volumen local no escala horizontalmente, ver el plan). +# Object storage S3-compatible (Cloudflare R2) -- expedientes/PDFs/logos. +# Sin esto, cae a disco local (solo dev). +# R2: S3_ENDPOINT=https://.r2.cloudflarestorage.com S3_REGION=auto # ============================================================================= S3_ENDPOINT= S3_BUCKET= -S3_REGION= +S3_REGION=auto S3_ACCESS_KEY_ID= S3_SECRET_ACCESS_KEY= diff --git a/api/storage.ts b/api/storage.ts index ccde499..0d13e60 100644 --- a/api/storage.ts +++ b/api/storage.ts @@ -17,11 +17,9 @@ import { config, DATA_DIR } from "./config.ts"; * otra capa. El contenido YA viene cifrado con AES-GCM (docs_crypto.ts) * antes de llegar aquí -- el bucket nunca ve texto plano. * - * Backend real: Contabo Object Storage (S3-compatible). Si no hay - * credenciales S3 configuradas (dev local sin Contabo), cae a disco local - * bajo DATA_DIR -- útil para desarrollar/probar sin depender de Contabo, - * pero NO es la ruta recomendada para staging/producción (ver Fase 4c del - * plan: un volumen local no escala horizontalmente). + * Backend real: S3-compatible (Cloudflare R2). Si no hay credenciales S3 + * (dev local), cae a disco bajo DATA_DIR. En staging/producción el volumen + * local no escala: hay que configurar S3_*. */ let client: S3Client | null = null; @@ -72,8 +70,11 @@ function getClient(): S3Client { if (!client) { client = new S3Client({ endpoint: config.s3Endpoint, - region: config.s3Region, + region: config.s3Region || "auto", forcePathStyle: true, + // R2 no implementa los checksums CRC32 que el SDK v3 manda por defecto. + requestChecksumCalculation: "WHEN_REQUIRED", + responseChecksumValidation: "WHEN_REQUIRED", credentials: { accessKeyId: config.s3AccessKeyId, secretAccessKey: config.s3SecretAccessKey, diff --git a/docs/coolify.md b/docs/coolify.md index 37b228e..5a7d17d 100644 --- a/docs/coolify.md +++ b/docs/coolify.md @@ -6,7 +6,7 @@ |-----|--------| | Postgres gestionado en Coolify | **1 instancia** por ambiente, con **2 bases**: `panels_platform`, `panels_product` (esquemas `iam`/`core`) | | Redis gestionado en Coolify | **1 instancia** por ambiente, con **2 usuarios ACL** (`panels_iam_redis`, `panels_core_redis`) | -| Object storage | Contabo Object Storage (S3-compatible) para expedientes/PDFs/logos cifrados | +| Object storage | Cloudflare R2 (S3-compatible) para expedientes/PDFs/logos cifrados | | Contenedores de la app | **3** (`api`, `web-panel`, `web-saas`) | | Volumen persistente | **1** → `/app/data` en `api` (solo fallback local si no hay Contabo configurado -- no usar así en producción) | @@ -65,7 +65,7 @@ openssl rand -hex 24 # passwords de roles Postgres/Redis | `API_KEY` | Auth alternativa por header `X-API-Key` **+ `X-Tenant-Id` obligatorio** (ya no ve todos los tenants, ver revisión de seguridad) | | `VCARD_BASE` | Prefijo QR/vCard gafetes | | `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` / `SMTP_FROM` | Correo por env (alternativa al panel `/smtp`) | -| `S3_ENDPOINT` / `S3_BUCKET` / `S3_REGION` / `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` | Contabo Object Storage (Fase 4c). Sin esto, cae a disco local -- **no recomendado en producción** | +| `S3_ENDPOINT` / `S3_BUCKET` / `S3_REGION` / `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` | Cloudflare R2. Endpoint `https://.r2.cloudflarestorage.com`, `S3_REGION=auto`. Sin esto, cae a disco local -- **no recomendado en producción** | `web-panel` y `web-saas` **no** necesitan variables de entorno en runtime (estáticos + proxy nginx).