Almacenamiento y base de datos
Almacenamiento de blobs
Los mensajes en bruto, los adjuntos y los archivos viven en almacenamiento de objetos. Postgres guarda sus metadatos y quién los usa. Cada archivo se guarda una vez por espacio de trabajo, bajo su SHA-256, sin importar cuántos mensajes lo lleven. Un archivo que ya nada usa se borra mediante un barrido cada hora una vez pasadas CAMPBOOKS_BLOBS__SWEEP_GRACE_HOURS horas (24 por defecto). Los archivos mayores que CAMPBOOKS_BLOBS__MAX_OBJECT_BYTES (50 MiB por defecto) se rechazan.
Disco local (por defecto)
CAMPBOOKS_BLOBS__BACKEND=fs guarda los archivos en CAMPBOOKS_BLOBS__PATH (/data/blobs, el volumen blobs-data). Las escrituras son atómicas: un archivo temporal, volcado a disco y luego renombrado a su sitio.
Almacén compatible con S3
Sirve cualquier almacén que hable la API de S3 (AWS S3, Garage, MinIO, Ceph, Cloudflare R2). Crea un bucket privado y una clave de acceso limitada a él, y define esto en .env:
CAMPBOOKS_BLOBS__BACKEND=s3
CAMPBOOKS_BLOBS__S3__BUCKET=campbooks-blobs
CAMPBOOKS_BLOBS__S3__ENDPOINT=https://s3.example.com # unset for AWS
CAMPBOOKS_BLOBS__S3__REGION=us-east-1 # what your store expects
CAMPBOOKS_BLOBS__S3__ACCESS_KEY_ID=...
CAMPBOOKS_BLOBS__S3__SECRET_ACCESS_KEY=...
CAMPBOOKS_BLOBS__S3__PATH_STYLE=true # most self-hosted stores
El servidor comprueba al arrancar que llega al bucket y se niega a arrancar si no. Las subidas grandes se hacen por partes. Añade al bucket una regla de ciclo de vida que aborte las subidas multiparte incompletas tras un día, para que un fallo a mitad de subida no deje nada atrás. El volumen blobs-data no se usa con este backend.
Cifrado en reposo
Campbooks aún no cifra los archivos por sí mismo. Usa el cifrado de tu almacén: cifrado del lado del servidor en el bucket, o un disco cifrado bajo blobs-data para el backend local.
Cambiar de backend
Copia todo el árbol (claves <workspace>/<aa>/<sha256>, salvo el prefijo _staging/) al nuevo almacén con el servidor detenido, y luego cambia los ajustes. Las claves son idénticas en disco y en un bucket.
Credenciales de la base de datos
Postgres omite la seguridad a nivel de fila para los superusuarios y los roles BYPASSRLS. Una consulta que pueda ejecutar RESET ROLE también puede volver a los poderes propios del usuario. Por eso el servidor usa dos credenciales:
| Usuario | Ajuste | Se usa para | Debe ser |
|---|---|---|---|
| ejecución | CAMPBOOKS_DATABASE_URL (y, opcional, CAMPBOOKS_DATABASE_PASSWORD_FILE) | cada petición y tarea | LOGIN, no superusuario, sin BYPASSRLS, no propietario de nada, miembro de campbooks_app |
| migración | CAMPBOOKS_DATABASE_MIGRATION_URL | aplicar migraciones al arrancar, nada más | el propietario del esquema (o un superusuario) |
Postgres incluido
No hay nada que hacer. El servicio puntual db-init crea el usuario campbooks_runtime en cada up (es idempotente y también actualiza una instalación existente). Genera la contraseña una sola vez en el volumen db-secrets, y el servidor la lee de /run/db/runtime-password. El usuario propietario sigue siendo campbooks con POSTGRES_PASSWORD, usado solo para migraciones.
Para rotar la contraseña de ejecución, borra el archivo del volumen y vuelve a iniciar:
docker compose run --rm --entrypoint rm db-init /run/db/runtime-password
docker compose up -d
Tu propio Postgres o uno gestionado
Crea el usuario una vez, como administrador. Las migraciones concederán después a campbooks_app lo que necesite cada tabla.
CREATE ROLE campbooks_app NOLOGIN; -- skip if it exists
CREATE ROLE campbooks_runtime LOGIN PASSWORD '...' NOSUPERUSER NOBYPASSRLS
NOCREATEDB NOCREATEROLE;
GRANT campbooks_app TO campbooks_runtime;
-- Allow the migration user to run the first migration's grants:
GRANT campbooks_app TO <migration user>; -- skip for a superuser
Después define CAMPBOOKS_DATABASE_URL=postgres://campbooks_runtime:<password>@host/db, o prescinde de la contraseña y apunta CAMPBOOKS_DATABASE_PASSWORD_FILE a un archivo que la contenga. Define CAMPBOOKS_DATABASE_MIGRATION_URL con la URL del propietario. En los servicios gestionados, el propietario es el usuario que creó la base de datos. No lo reutilices como usuario de ejecución.
También necesitas pgvector, que la imagen incluida (pgvector/pgvector:pg17) ya trae.
Aislamiento de espacios de trabajo
Las filas de cada espacio de trabajo están protegidas por la seguridad a nivel de fila de Postgres además de por las consultas. El servidor consulta con un usuario que no puede eludir las políticas, y se niega a arrancar si pudiera.
Al arrancar, el servidor se niega a iniciar, y explica por qué, cuando el rol con el que consulta:
- es superusuario,
- tiene
BYPASSRLS, - es miembro de un rol así, o
- es propietario de tablas con seguridad a nivel de fila.
Cuando hay un usuario de migración configurado, también se niega si el usuario de ejecución aún pudiera cambiar a un rol privilegiado. Solo la antigua configuración de una única URL (un usuario privilegiado que ejecuta SET ROLE campbooks_app) sigue funcionando, con una advertencia para separar los usuarios. Si aún no puedes separarlos, consulta Solución de problemas.