From b0030246e4e0dcbdcb900cdac5860e5dac073dec Mon Sep 17 00:00:00 2001 From: Corentin Joguet Date: Mon, 15 Jun 2026 11:00:52 +0200 Subject: [PATCH] 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) --- .dockerignore | 4 + .env.prod.example | 31 +++++++ .gitignore | 4 + DEPLOY.md | 164 ++++++++++++++++++++++++++++++++++++ Dockerfile | 37 ++++++++ _byan-output/tool-log.jsonl | 50 +++++++++++ docker-compose.prod.yml | 79 +++++++++++++++++ scripts/backup.sh | 41 +++++++++ scripts/restore.sh | 44 ++++++++++ 9 files changed, 454 insertions(+) create mode 100644 .env.prod.example create mode 100644 DEPLOY.md create mode 100644 Dockerfile create mode 100644 docker-compose.prod.yml create mode 100755 scripts/backup.sh create mode 100755 scripts/restore.sh diff --git a/.dockerignore b/.dockerignore index b5b48c6..c695b4c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,10 @@ node_modules dist .astro +.env +.env.* +!.env.example +.byan-strict .git .github .claude diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 0000000..c4d0be7 --- /dev/null +++ b/.env.prod.example @@ -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 diff --git a/.gitignore b/.gitignore index 8da8d08..e85314f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..a933a9e --- /dev/null +++ b/DEPLOY.md @@ -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-.sql.gz` et `backups/uploads-.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-.sql.gz backups/uploads-.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. +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..becc5ba --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/_byan-output/tool-log.jsonl b/_byan-output/tool-log.jsonl index 1e05bed..01af87d 100644 --- a/_byan-output/tool-log.jsonl +++ b/_byan-output/tool-log.jsonl @@ -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} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..444b879 --- /dev/null +++ b/docker-compose.prod.yml @@ -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: diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..986db64 --- /dev/null +++ b/scripts/backup.sh @@ -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-.sql.gz : dump complet Postgres (textes, series, contacts) +# - uploads-.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." diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100755 index 0000000..f8188b3 --- /dev/null +++ b/scripts/restore.sh @@ -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"