chore: package de deploiement prod (Dockerfile, compose, backup/restore, guide)

- Dockerfile multi-stage (build Astro -> runtime node standalone)
- docker-compose.prod.yml : Astro builde, ports bindes 127.0.0.1, secrets requis
- .env.prod.example : template de prod avec generation des secrets
- scripts/backup.sh + restore.sh : migration base Directus + photos
- DEPLOY.md : guide pas a pas
- .dockerignore : exclusion du .env (anti-fuite de secrets)
- untrack du tool-log BYAN (churn)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Corentin Joguet 2026-06-15 11:00:52 +02:00
parent bff653acd6
commit b0030246e4
9 changed files with 454 additions and 0 deletions

View file

@ -1,6 +1,10 @@
node_modules
dist
.astro
.env
.env.*
!.env.example
.byan-strict
.git
.github
.claude

31
.env.prod.example Normal file
View file

@ -0,0 +1,31 @@
# ============================================================
# Variables de PRODUCTION — a copier en `.env` SUR LE SERVEUR
# cp .env.prod.example .env puis remplir chaque valeur
# Ne JAMAIS commiter le .env rempli (il est gitignore).
# ============================================================
# --- Domaine public ---
# L'URL finale du site (avec https). Sert au SSR + au CORS Directus.
PUBLIC_SITE_URL=https://mostuki.fr
# URL publique de Directus (sous-domaine admin, derriere ton reverse-proxy).
DIRECTUS_PUBLIC_URL=https://admin.mostuki.fr
# --- Secrets Directus (GENERER des valeurs aleatoires) ---
# Genere chacune avec : openssl rand -hex 32
DIRECTUS_KEY=
DIRECTUS_SECRET=
# --- Compte admin Directus ---
# NE PAS reutiliser changeme-please. Mot de passe long et unique.
DIRECTUS_ADMIN_EMAIL=corentin.jog@gmail.com
DIRECTUS_ADMIN_PASSWORD=
# --- Token statique d'API Directus (lecture des contenus par Astro) ---
# A generer APRES le premier demarrage (voir DEPLOY.md, etape 5).
DIRECTUS_TOKEN=
# --- Postgres ---
POSTGRES_USER=directus
# Mot de passe DB long et unique : openssl rand -hex 24
POSTGRES_PASSWORD=
POSTGRES_DB=directus

4
.gitignore vendored
View file

@ -5,9 +5,13 @@ dist
.env
.env.*
!.env.example
!.env.prod.example
# Docker volumes
postgres_data/
directus_uploads/
directus_extensions/
# BYAN strict-mode local state
.byan-strict/
# BYAN tool log (churn)
_byan-output/tool-log.jsonl

164
DEPLOY.md Normal file
View file

@ -0,0 +1,164 @@
# Deploiement Mostuki Photo — guide pas a pas
Ce guide part du principe que tu as deja un serveur avec Docker + un
reverse-proxy (Traefik/nginx) qui gere le TLS et route ton domaine.
Le principe a retenir : **git porte le code, pas les donnees.** Tes textes,
tes series et tes photos vivent dans la base Directus + le volume uploads. Il
faut donc les EXPORTER de ta machine et les RESTAURER sur le serveur.
---
## Vue d'ensemble
```
[Ta machine] [Serveur]
1. backup.sh --> db.sql.gz --scp--> 6. restore.sh
--> uploads.tar.gz |
2. git clone
3. .env de prod
4. up -d --build
5. token Directus
7. reverse-proxy
```
---
## ETAPE 1 — Exporter ton contenu (sur TA machine)
La stack locale doit tourner (Postgres + Directus up).
```bash
cd ~/Documents/03_Dev/site-photo
bash scripts/backup.sh
```
Ca cree `backups/db-<date>.sql.gz` et `backups/uploads-<date>.tar.gz`.
Note bien la valeur de `DIRECTUS_TOKEN` de ton `.env` local : tu en auras
besoin a l'etape 3 (le token est stocke dans la base, il doit correspondre).
---
## ETAPE 2 — Recuperer le code (sur le SERVEUR)
```bash
git clone https://git.acadenice.com/Corentin/site-mariage.git mostuki
cd mostuki
```
---
## ETAPE 3 — Configurer les secrets de prod (sur le SERVEUR)
```bash
cp .env.prod.example .env
```
Edite `.env` et remplis chaque valeur :
```bash
# Genere les 3 secrets aleatoires :
openssl rand -hex 32 # -> DIRECTUS_KEY
openssl rand -hex 32 # -> DIRECTUS_SECRET
openssl rand -hex 24 # -> POSTGRES_PASSWORD
```
- `PUBLIC_SITE_URL` / `DIRECTUS_PUBLIC_URL` : tes vrais domaines (https).
- `DIRECTUS_TOKEN` : **la meme valeur que ton `.env` local** (etape 1).
- `DIRECTUS_ADMIN_PASSWORD` : un mot de passe fort (sert si la base demarre
vide ; sera ecrase par l'admin restaure a l'etape 6 — voir la note).
---
## ETAPE 4 — Demarrer la stack (sur le SERVEUR)
```bash
docker compose -f docker-compose.prod.yml up -d --build
```
Le `--build` compile l'image Astro de prod. Verifie que les 3 conteneurs
tournent : `docker compose -f docker-compose.prod.yml ps`.
A ce stade la base est VIDE (branding "Laurel & Vow" par defaut). On la
remplace avec ton contenu a l'etape 6.
---
## ETAPE 5 — Transferer les backups (depuis TA machine)
```bash
scp backups/db-*.sql.gz backups/uploads-*.tar.gz user@serveur:~/mostuki/backups/
```
(cree le dossier `backups/` sur le serveur s'il n'existe pas)
---
## ETAPE 6 — Restaurer ton contenu (sur le SERVEUR)
```bash
bash scripts/restore.sh backups/db-<date>.sql.gz backups/uploads-<date>.tar.gz
docker compose -f docker-compose.prod.yml restart directus astro
```
> **IMPORTANT securite.** La base restauree contient ton compte admin LOCAL,
> avec son ancien mot de passe (`changeme-please`). Connecte-toi tout de suite
> sur Directus admin et change le mot de passe + l'email :
> Settings -> Users -> ton compte -> nouveau mot de passe.
---
## ETAPE 7 — Brancher le reverse-proxy (sur le SERVEUR)
Les services ecoutent en local uniquement :
- Site Astro -> `127.0.0.1:4321`
- Directus -> `127.0.0.1:8055`
Configure ton reverse-proxy pour router :
- `PUBLIC_SITE_URL` (ex: mostuki.fr) -> `127.0.0.1:4321`
- `DIRECTUS_PUBLIC_URL` (ex: admin.mostuki.fr) -> `127.0.0.1:8055`
Avec TLS (Let's Encrypt) sur les deux.
---
## ETAPE 8 — Verifications finales
- [ ] La home affiche bien "Mostuki" (pas "Laurel & Vow") -> sinon la restore
DB n'a pas pris, relance l'etape 6.
- [ ] Les photos des series s'affichent (proxy `/api/files/...` OK).
- [ ] Le formulaire `/contact` enregistre bien (verifie dans Directus admin,
collection `contact_requests`).
- [ ] Mot de passe admin Directus change (etape 6).
- [ ] Port 8055 NON accessible publiquement (seulement via ton sous-domaine
admin derriere le proxy).
---
## Mises a jour ulterieures (code uniquement)
```bash
cd ~/mostuki
git pull
docker compose -f docker-compose.prod.yml up -d --build
```
La base et les photos ne sont pas touchees (volumes persistants).
---
## Sauvegardes regulieres
Relance `bash scripts/backup.sh` periodiquement (sur le serveur, en adaptant
`PG_CONTAINER=mostuki-postgres DIRECTUS_CONTAINER=mostuki-directus`) et copie
les archives ailleurs. C'est ton seul filet en cas de perte du serveur.
---
## Note : email du formulaire de contact
A ce jour, une demande de contact est STOCKEE dans Directus mais n'envoie
AUCUN email de notification (TODO dans `src/pages/api/contact.ts`). Pense a
checker la collection `contact_requests` regulierement, ou demande l'ajout de
la notif email (provider a choisir : Resend recommande) avant la mise en ligne.
```

37
Dockerfile Normal file
View file

@ -0,0 +1,37 @@
# Build de production Astro SSR (adapter node standalone).
# Multi-stage : on compile dans une image lourde, on ne garde que le runtime.
# ---- Stage 1 : build ----
FROM node:22-alpine AS build
WORKDIR /app
# Dependances (cache : ne se reinstalle que si package*.json change)
COPY package.json package-lock.json* ./
RUN npm ci
# Source + build
COPY astro.config.mjs ./
COPY tsconfig.json* ./
COPY src ./src
COPY public ./public
RUN npm run build
# ---- Stage 2 : runtime ----
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
# Le serveur standalone @astrojs/node lit HOST et PORT
ENV HOST=0.0.0.0
ENV PORT=4321
# Toutes les deps du projet sont des "dependencies" (pas de devDependencies),
# le runtime SSR en a besoin (@astrojs/node, @directus/sdk).
COPY package.json package-lock.json* ./
RUN npm ci --omit=dev && npm cache clean --force
# Artefact compile uniquement
COPY --from=build /app/dist ./dist
EXPOSE 4321
CMD ["node", "./dist/server/entry.mjs"]

View file

@ -821,3 +821,53 @@
{"timestamp":"2026-06-15T08:30:04.097Z","phase":"pre","tool":"Edit","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/.gitignore","est_input_tokens":81}
{"timestamp":"2026-06-15T08:30:04.145Z","phase":"post","tool":"Edit","ok":true,"failure_kind":null,"est_output_tokens":175}
{"timestamp":"2026-06-15T08:30:15.998Z","phase":"pre","tool":"Bash","summary":"Init git, stage, audit for secrets before commit","est_input_tokens":217}
{"timestamp":"2026-06-15T08:30:16.597Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":224}
{"timestamp":"2026-06-15T08:30:37.390Z","phase":"pre","tool":"Bash","summary":"Commit and add remote","est_input_tokens":107}
{"timestamp":"2026-06-15T08:30:37.547Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":66}
{"timestamp":"2026-06-15T08:30:40.622Z","phase":"pre","tool":"Bash","summary":"Push main to origin","est_input_tokens":40}
{"timestamp":"2026-06-15T08:30:42.728Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":70}
{"timestamp":"2026-06-15T08:53:57.991Z","phase":"pre","tool":"Bash","summary":"Check seed default content values","est_input_tokens":78}
{"timestamp":"2026-06-15T08:54:00.790Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":212}
{"timestamp":"2026-06-15T08:55:31.468Z","phase":"pre","tool":"Bash","summary":"Check runtime env reading pattern","est_input_tokens":140}
{"timestamp":"2026-06-15T08:55:31.536Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":265}
{"timestamp":"2026-06-15T08:55:43.735Z","phase":"pre","tool":"Bash","summary":"Check dockerignore","est_input_tokens":84}
{"timestamp":"2026-06-15T08:55:43.798Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":83}
{"timestamp":"2026-06-15T08:56:24.959Z","phase":"pre","tool":"Read","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/.dockerignore","est_input_tokens":20}
{"timestamp":"2026-06-15T08:56:24.995Z","phase":"post","tool":"Read","ok":true,"failure_kind":null,"est_output_tokens":76}
{"timestamp":"2026-06-15T08:56:28.421Z","phase":"pre","tool":"Edit","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/.dockerignore","est_input_tokens":60}
{"timestamp":"2026-06-15T08:56:28.462Z","phase":"post","tool":"Edit","ok":true,"failure_kind":null,"est_output_tokens":155}
{"timestamp":"2026-06-15T08:56:39.102Z","phase":"pre","tool":"Write","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/Dockerfile","est_input_tokens":279}
{"timestamp":"2026-06-15T08:56:39.148Z","phase":"post","tool":"Write","ok":true,"failure_kind":null,"est_output_tokens":298}
{"timestamp":"2026-06-15T08:56:58.736Z","phase":"pre","tool":"Write","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/docker-compose.prod.yml","est_input_tokens":722}
{"timestamp":"2026-06-15T08:56:58.786Z","phase":"post","tool":"Write","ok":true,"failure_kind":null,"est_output_tokens":741}
{"timestamp":"2026-06-15T08:57:10.618Z","phase":"pre","tool":"Write","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/.env.prod.example","est_input_tokens":321}
{"timestamp":"2026-06-15T08:57:10.657Z","phase":"post","tool":"Write","ok":true,"failure_kind":null,"est_output_tokens":341}
{"timestamp":"2026-06-15T08:57:24.754Z","phase":"pre","tool":"Write","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/scripts/backup.sh","est_input_tokens":430}
{"timestamp":"2026-06-15T08:57:24.804Z","phase":"post","tool":"Write","ok":true,"failure_kind":null,"est_output_tokens":449}
{"timestamp":"2026-06-15T08:57:38.885Z","phase":"pre","tool":"Write","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/scripts/restore.sh","est_input_tokens":510}
{"timestamp":"2026-06-15T08:57:38.924Z","phase":"post","tool":"Write","ok":true,"failure_kind":null,"est_output_tokens":529}
{"timestamp":"2026-06-15T08:57:46.216Z","phase":"pre","tool":"Bash","summary":"chmod scripts and check docker","est_input_tokens":79}
{"timestamp":"2026-06-15T08:57:46.577Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":30}
{"timestamp":"2026-06-15T08:57:51.096Z","phase":"pre","tool":"Bash","summary":"Check docker daemon / colima","est_input_tokens":65}
{"timestamp":"2026-06-15T08:57:52.126Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":49}
{"timestamp":"2026-06-15T08:58:11.383Z","phase":"pre","tool":"Bash","summary":"Validate scripts and compose coherence offline","est_input_tokens":232}
{"timestamp":"2026-06-15T08:58:11.551Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":234}
{"timestamp":"2026-06-15T08:58:28.296Z","phase":"pre","tool":"Read","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/scripts/restore.sh","est_input_tokens":27}
{"timestamp":"2026-06-15T08:58:28.337Z","phase":"post","tool":"Read","ok":true,"failure_kind":null,"est_output_tokens":152}
{"timestamp":"2026-06-15T08:58:36.252Z","phase":"pre","tool":"Read","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/scripts/restore.sh","est_input_tokens":27}
{"timestamp":"2026-06-15T08:58:36.294Z","phase":"post","tool":"Read","ok":true,"failure_kind":null,"est_output_tokens":169}
{"timestamp":"2026-06-15T08:58:46.411Z","phase":"pre","tool":"Bash","summary":"Count quotes in restore.sh","est_input_tokens":104}
{"timestamp":"2026-06-15T08:58:46.471Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":162}
{"timestamp":"2026-06-15T08:59:07.897Z","phase":"pre","tool":"Bash","summary":"Re-run bash -n on restore.sh","est_input_tokens":62}
{"timestamp":"2026-06-15T08:59:07.952Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":73}
{"timestamp":"2026-06-15T08:59:19.835Z","phase":"pre","tool":"Edit","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/scripts/restore.sh","est_input_tokens":80}
{"timestamp":"2026-06-15T08:59:19.876Z","phase":"post","tool":"Edit","ok":true,"failure_kind":null,"est_output_tokens":685}
{"timestamp":"2026-06-15T08:59:23.527Z","phase":"pre","tool":"Bash","summary":"Re-validate both scripts","est_input_tokens":59}
{"timestamp":"2026-06-15T08:59:23.586Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":29}
{"timestamp":"2026-06-15T09:00:14.377Z","phase":"pre","tool":"Write","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/DEPLOY.md","est_input_tokens":1274}
{"timestamp":"2026-06-15T09:00:14.431Z","phase":"post","tool":"Write","ok":true,"failure_kind":null,"est_output_tokens":1294}
{"timestamp":"2026-06-15T09:00:22.323Z","phase":"pre","tool":"Bash","summary":"Stage and show changes","est_input_tokens":47}
{"timestamp":"2026-06-15T09:00:22.425Z","phase":"post","tool":"Bash","ok":true,"failure_kind":null,"est_output_tokens":69}
{"timestamp":"2026-06-15T09:00:34.099Z","phase":"pre","tool":"Edit","summary":"/Users/corentinjoguet/Documents/03_Dev/site-photo/.gitignore","est_input_tokens":51}
{"timestamp":"2026-06-15T09:00:34.137Z","phase":"post","tool":"Edit","ok":true,"failure_kind":null,"est_output_tokens":157}
{"timestamp":"2026-06-15T09:00:43.652Z","phase":"pre","tool":"Bash","summary":"Fix tracking, verify env template included","est_input_tokens":147}

79
docker-compose.prod.yml Normal file
View file

@ -0,0 +1,79 @@
# Stack de PRODUCTION Mostuki Photo.
#
# Difference avec docker-compose.yml (dev) :
# - Astro est BUILDE (Dockerfile multi-stage) au lieu de tourner en mode dev
# - les ports sont bindes sur 127.0.0.1 : seul le reverse-proxy de l'hote y accede,
# rien n'est expose directement sur internet
# - aucun secret n'a de valeur par defaut : les vars DOIVENT etre fournies (.env)
#
# Lancement : docker compose -f docker-compose.prod.yml up -d --build
# Le reverse-proxy de l'hote (Traefik/nginx) route ton domaine vers 127.0.0.1:4321
# (site) et un sous-domaine admin vers 127.0.0.1:8055 (Directus) si besoin.
services:
postgres:
image: postgres:16-alpine
container_name: mostuki-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER requis}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD requis}
POSTGRES_DB: ${POSTGRES_DB:?POSTGRES_DB requis}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5
directus:
image: directus/directus:11
container_name: mostuki-directus
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:8055:8055"
volumes:
- directus_uploads:/directus/uploads
- directus_extensions:/directus/extensions
environment:
KEY: ${DIRECTUS_KEY:?DIRECTUS_KEY requis}
SECRET: ${DIRECTUS_SECRET:?DIRECTUS_SECRET requis}
ADMIN_EMAIL: ${DIRECTUS_ADMIN_EMAIL:?DIRECTUS_ADMIN_EMAIL requis}
ADMIN_PASSWORD: ${DIRECTUS_ADMIN_PASSWORD:?DIRECTUS_ADMIN_PASSWORD requis}
DB_CLIENT: "pg"
DB_HOST: "postgres"
DB_PORT: "5432"
DB_DATABASE: ${POSTGRES_DB}
DB_USER: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
CACHE_ENABLED: "false"
WEBSOCKETS_ENABLED: "true"
PUBLIC_URL: ${DIRECTUS_PUBLIC_URL:?DIRECTUS_PUBLIC_URL requis}
CORS_ENABLED: "true"
CORS_ORIGIN: ${PUBLIC_SITE_URL:?PUBLIC_SITE_URL requis}
astro:
build:
context: .
dockerfile: Dockerfile
container_name: mostuki-astro
restart: unless-stopped
depends_on:
- directus
ports:
- "127.0.0.1:4321:4321"
environment:
ASTRO_TELEMETRY_DISABLED: "1"
# Astro parle a Directus par le reseau interne Docker (pas par le domaine public)
DIRECTUS_URL: "http://directus:8055"
DIRECTUS_TOKEN: ${DIRECTUS_TOKEN:?DIRECTUS_TOKEN requis}
PUBLIC_SITE_URL: ${PUBLIC_SITE_URL}
volumes:
postgres_data:
directus_uploads:
directus_extensions:

41
scripts/backup.sh Executable file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
# ============================================================
# backup.sh — emporte TON CONTENU (base Directus + photos)
#
# A lancer la ou la stack tourne deja (ta machine locale).
# Produit dans ./backups/ :
# - db-<date>.sql.gz : dump complet Postgres (textes, series, contacts)
# - uploads-<date>.tar.gz : toutes les photos uploadees dans Directus
#
# Usage : bash scripts/backup.sh
# Conteneurs (override si besoin) :
# PG_CONTAINER=... DIRECTUS_CONTAINER=... bash scripts/backup.sh
# ============================================================
set -euo pipefail
cd "$(dirname "$0")/.."
# Charge .env pour POSTGRES_USER / POSTGRES_DB
if [ -f .env ]; then set -a; . ./.env; set +a; fi
PG_CONTAINER="${PG_CONTAINER:-laurel-vow-postgres}"
DIRECTUS_CONTAINER="${DIRECTUS_CONTAINER:-laurel-vow-directus}"
PG_USER="${POSTGRES_USER:-directus}"
PG_DB="${POSTGRES_DB:-directus}"
STAMP="$(date +%Y%m%d-%H%M%S)"
OUT="backups"
mkdir -p "$OUT"
echo "==> Dump base Postgres ($PG_CONTAINER / db=$PG_DB)"
docker exec "$PG_CONTAINER" pg_dump -U "$PG_USER" "$PG_DB" | gzip > "$OUT/db-$STAMP.sql.gz"
echo "==> Archive des photos (uploads Directus)"
# /directus/uploads est le chemin des fichiers dans le conteneur Directus
docker exec "$DIRECTUS_CONTAINER" tar czf - -C /directus/uploads . > "$OUT/uploads-$STAMP.tar.gz"
echo ""
echo "OK. Fichiers crees :"
ls -lh "$OUT/db-$STAMP.sql.gz" "$OUT/uploads-$STAMP.tar.gz"
echo ""
echo "Transfere ces 2 fichiers sur le serveur, puis lance scripts/restore.sh la-bas."

44
scripts/restore.sh Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# ============================================================
# restore.sh — restaure TON CONTENU sur le serveur de prod
#
# A lancer SUR LE SERVEUR, apres avoir demarre la stack prod
# (docker compose -f docker-compose.prod.yml up -d) et transfere
# les 2 fichiers de backup dans ./backups/.
#
# Usage :
# bash scripts/restore.sh backups/db-XXXX.sql.gz backups/uploads-XXXX.tar.gz
#
# Conteneurs prod (override si besoin) :
# PG_CONTAINER=... DIRECTUS_CONTAINER=... bash scripts/restore.sh ...
# ============================================================
set -euo pipefail
cd "$(dirname "$0")/.."
DB_DUMP="${1:?Chemin du dump DB requis (ex: backups/db-XXXX.sql.gz)}"
UPLOADS_TAR="${2:?Chemin de l archive uploads requis (ex: backups/uploads-XXXX.tar.gz)}"
if [ -f .env ]; then set -a; . ./.env; set +a; fi
PG_CONTAINER="${PG_CONTAINER:-mostuki-postgres}"
DIRECTUS_CONTAINER="${DIRECTUS_CONTAINER:-mostuki-directus}"
PG_USER="${POSTGRES_USER:-directus}"
PG_DB="${POSTGRES_DB:-directus}"
echo "ATTENTION : ceci ECRASE la base '$PG_DB' et les photos du serveur."
read -r -p "Continuer ? (tape oui) " ans
[ "$ans" = "oui" ] || { echo "Annule."; exit 1; }
echo "==> Restauration base Postgres ($PG_CONTAINER)"
# Recree un schema propre avant import pour eviter les doublons
docker exec -i "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" \
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
gunzip -c "$DB_DUMP" | docker exec -i "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB"
echo "==> Restauration des photos (uploads Directus)"
docker exec -i "$DIRECTUS_CONTAINER" sh -c 'rm -rf /directus/uploads/* && tar xzf - -C /directus/uploads' < "$UPLOADS_TAR"
echo ""
echo "OK. Redemarre Directus pour qu'il reprenne la base restauree :"
echo " docker compose -f docker-compose.prod.yml restart directus astro"