Stockage et base de données
Stockage des blobs
Les messages bruts, les pièces jointes et les fichiers vivent dans un stockage d'objets. Postgres garde leurs métadonnées et qui les utilise. Chaque fichier est stocké une fois par espace de travail, sous son SHA-256, quel que soit le nombre de messages qui le portent. Un fichier que plus rien n'utilise est supprimé par un nettoyage horaire une fois CAMPBOOKS_BLOBS__SWEEP_GRACE_HOURS (24 par défaut) écoulées. Les fichiers plus grands que CAMPBOOKS_BLOBS__MAX_OBJECT_BYTES (50 Mio par défaut) sont refusés.
Disque local (par défaut)
CAMPBOOKS_BLOBS__BACKEND=fs stocke les fichiers sous CAMPBOOKS_BLOBS__PATH (/data/blobs, le volume blobs-data). Les écritures sont atomiques : un fichier temporaire, écrit sur le disque, puis renommé à sa place.
Stockage compatible S3
N'importe quel stockage qui parle l'API S3 convient (AWS S3, Garage, MinIO, Ceph, Cloudflare R2). Créez un bucket privé et une clé d'accès limitée à lui, puis définissez ceci dans .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
Le serveur vérifie au démarrage qu'il peut joindre le bucket, et refuse de démarrer sinon. Les gros envois se font par parties. Ajoutez au bucket une règle de cycle de vie qui abandonne les envois multipart incomplets après un jour, pour qu'un plantage en plein envoi ne laisse rien derrière lui. Le volume blobs-data est inutilisé avec ce backend.
Chiffrement au repos
Campbooks ne chiffre pas encore les fichiers lui-même. Utilisez le chiffrement de votre stockage : chiffrement côté serveur sur le bucket, ou un disque chiffré sous blobs-data pour le backend local.
Passer d'un backend à l'autre
Copiez toute l'arborescence (clés <workspace>/<aa>/<sha256>, à l'exception du préfixe _staging/) vers le nouveau stockage, serveur arrêté, puis changez les réglages. Les clés sont identiques sur disque et dans un bucket.
Comptes de base de données
Postgres ignore la sécurité au niveau des lignes pour les superutilisateurs et les rôles BYPASSRLS. Une requête qui peut faire RESET ROLE peut aussi retrouver les pouvoirs propres du compte. Le serveur utilise donc deux comptes :
| Compte | Réglage | Sert à | Doit être |
|---|---|---|---|
| exécution | CAMPBOOKS_DATABASE_URL (et CAMPBOOKS_DATABASE_PASSWORD_FILE en option) | chaque requête et chaque tâche | LOGIN, pas superutilisateur, sans BYPASSRLS, ne possède rien, membre de campbooks_app |
| migration | CAMPBOOKS_DATABASE_MIGRATION_URL | appliquer les migrations au démarrage, rien d'autre | le propriétaire du schéma (ou un superutilisateur) |
Postgres inclus
Rien à faire. Le service ponctuel db-init crée le compte campbooks_runtime à chaque up (il est idempotent et met aussi à niveau une installation existante). Il génère le mot de passe une seule fois dans le volume db-secrets, et le serveur le lit dans /run/db/runtime-password. Le compte propriétaire reste campbooks avec POSTGRES_PASSWORD, utilisé uniquement pour les migrations.
Pour renouveler le mot de passe d'exécution, supprimez le fichier dans le volume et redémarrez :
docker compose run --rm --entrypoint rm db-init /run/db/runtime-password
docker compose up -d
Votre propre Postgres ou un Postgres géré
Créez le compte une fois, en tant qu'administrateur. Les migrations accordent ensuite à campbooks_app ce dont chaque table a besoin.
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
Définissez ensuite CAMPBOOKS_DATABASE_URL=postgres://campbooks_runtime:<password>@host/db, ou omettez le mot de passe et pointez CAMPBOOKS_DATABASE_PASSWORD_FILE vers un fichier qui le contient. Définissez CAMPBOOKS_DATABASE_MIGRATION_URL sur l'URL du propriétaire. Sur les services gérés, le propriétaire est l'utilisateur qui a créé la base. Ne le réutilisez pas comme compte d'exécution.
Il vous faut aussi pgvector, que l'image incluse (pgvector/pgvector:pg17) fournit.
Isolation des espaces de travail
Les lignes de chaque espace de travail sont protégées par la sécurité au niveau des lignes de Postgres en plus des requêtes. Le serveur interroge la base avec un compte qui ne peut pas contourner les politiques, et refuse de démarrer s'il le pouvait.
Au démarrage, le serveur refuse de démarrer, en disant pourquoi, quand le rôle avec lequel il interroge la base :
- est un superutilisateur,
- a
BYPASSRLS, - est membre d'un tel rôle, ou
- possède des tables avec sécurité au niveau des lignes.
Quand un compte de migration est configuré, il refuse aussi si le compte d'exécution pouvait encore basculer vers un rôle privilégié. Seule l'ancienne configuration à URL unique (un compte privilégié qui exécute SET ROLE campbooks_app) continue de fonctionner, avec un avertissement vous invitant à séparer les comptes. Si vous ne pouvez pas encore les séparer, voir Dépannage.