# 06 — Stockage objet S3 (Hetzner Object Storage) --- ## Migration MinIO → Hetzner Object Storage Bonne nouvelle : **aucune modification de code nécessaire.** Le code Xpeditis utilise déjà le AWS SDK v3 avec `forcePathStyle: true` et un endpoint configurable dans `apps/backend/src/infrastructure/storage/s3-storage.adapter.ts` : ```typescript // Ce code existant fonctionne avec Hetzner Object Storage this.s3Client = new S3Client({ region, endpoint, // ← Changer vers Hetzner credentials: { accessKeyId, secretAccessKey }, forcePathStyle: !!endpoint, // ← true pour Hetzner (path-style S3) }); ``` Il suffit de **changer 4 variables d'environnement** : ```bash # AVANT (MinIO local) AWS_S3_ENDPOINT=http://localhost:9000 AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin AWS_REGION=us-east-1 # APRÈS (Hetzner Object Storage) AWS_S3_ENDPOINT=https://fsn1.your-objectstorage.com AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION=eu-central-1 AWS_S3_BUCKET=xpeditis-prod ``` --- ## Configuration détaillée ### Variables d'environnement backend (.env.production) ```bash # S3 / Hetzner Object Storage AWS_S3_ENDPOINT=https://fsn1.your-objectstorage.com AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION=eu-central-1 AWS_S3_BUCKET=xpeditis-prod ``` > **Endpoint par région :** > - Falkenstein : `https://fsn1.your-objectstorage.com` > - Nuremberg : `https://nbg1.your-objectstorage.com` > - Helsinki : `https://hel1.your-objectstorage.com` > > Utilisez la même région que vos serveurs pour éviter des frais de transfert inter-région. --- ## Tester la connexion depuis le code ```bash # 1. Build l'image Docker backend avec les vars de test # 2. Ou tester directement avec AWS CLI # Test avec AWS CLI (profil configuré dans doc 03) aws s3 ls s3://xpeditis-prod/ \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com # Uploader un fichier test echo "test" | aws s3 cp - s3://xpeditis-prod/test/health.txt \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com # Générer une URL signée (1h) aws s3 presign s3://xpeditis-prod/test/health.txt \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com \ --expires-in 3600 # Nettoyage aws s3 rm s3://xpeditis-prod/test/health.txt \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com ``` --- ## Structure du bucket Créez les "dossiers" initiaux (S3 utilise des préfixes, pas de vrais dossiers) : ```bash #!/bin/bash PROFILE="hetzner" ENDPOINT="https://fsn1.your-objectstorage.com" BUCKET="xpeditis-prod" for PREFIX in documents pdfs exports logos backups/postgres; do aws s3api put-object \ --bucket "$BUCKET" \ --key "$PREFIX/" \ --profile "$PROFILE" \ --endpoint-url "$ENDPOINT" \ --content-length 0 echo "✅ Créé: $PREFIX/" done ``` --- ## Lifecycle policies (économies de stockage) Hetzner Object Storage supporte les lifecycle rules S3 pour archiver automatiquement les anciens fichiers. ```bash # Créer le fichier de lifecycle cat > /tmp/lifecycle.json << 'EOF' { "Rules": [ { "ID": "archive-old-pdfs", "Status": "Enabled", "Filter": { "Prefix": "pdfs/" }, "Transitions": [ { "Days": 90, "StorageClass": "GLACIER" } ] }, { "ID": "archive-old-exports", "Status": "Enabled", "Filter": { "Prefix": "exports/" }, "Expiration": { "Days": 365 } }, { "ID": "cleanup-old-backups", "Status": "Enabled", "Filter": { "Prefix": "backups/" }, "Expiration": { "Days": 30 } } ] } EOF # Appliquer le lifecycle aws s3api put-bucket-lifecycle-configuration \ --bucket xpeditis-prod \ --lifecycle-configuration file:///tmp/lifecycle.json \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com # Vérifier aws s3api get-bucket-lifecycle-configuration \ --bucket xpeditis-prod \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com ``` --- ## CORS (pour upload direct depuis le navigateur) Si vous implémentez des uploads directs depuis le browser (carrier portal) : ```bash cat > /tmp/cors.json << 'EOF' { "CORSRules": [ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"], "AllowedOrigins": [ "https://app.xpeditis.com", "https://xpeditis.com" ], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3000 } ] } EOF aws s3api put-bucket-cors \ --bucket xpeditis-prod \ --cors-configuration file:///tmp/cors.json \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com ``` --- ## Intégration dans le Secret Kubernetes Le Secret Kubernetes `backend-secrets` contiendra les credentials S3. Voir le doc 09 pour les manifests complets, mais voici la section S3 : ```yaml apiVersion: v1 kind: Secret metadata: name: backend-secrets namespace: xpeditis-prod type: Opaque stringData: AWS_S3_ENDPOINT: "https://fsn1.your-objectstorage.com" AWS_ACCESS_KEY_ID: "" AWS_SECRET_ACCESS_KEY: "" AWS_REGION: "eu-central-1" AWS_S3_BUCKET: "xpeditis-prod" ``` --- ## Monitoring du stockage ```bash # Voir la taille totale du bucket aws s3 ls s3://xpeditis-prod/ \ --recursive \ --human-readable \ --summarize \ --profile hetzner \ --endpoint-url https://fsn1.your-objectstorage.com \ | tail -3 # Output : # Total Objects: 1234 # Total Size: 4.5 GiB ``` --- ## Tarifs Hetzner Object Storage | Ressource | Prix | |---|---| | Stockage | Inclus dans le pack €4.99/mois (1 TB) | | Trafic sortant (internet) | Inclus dans le pack (1 TB) | | Requêtes | Incluses | | Stockage > 1 TB | €0.0067/TB/heure (~€4.90/TB/mois) | | Trafic > 1 TB | ~€1/TB | Pour Xpeditis à 1 000 users (~200 GB de fichiers), le coût est de **€4.99/mois fixe**.