From 66fdd5250fd1dda37ef17ad3dcf4d0ef9aa1fe10 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Fri, 10 Apr 2026 14:25:44 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(migration):=20Ret?= =?UTF-8?q?ire=20l'usage=20de=20docker=20dans=20backup.sh=20et=20restore.s?= =?UTF-8?q?h,=20utilise=20pg=5Fdump/psql=20et=20tar=20directement=20sur=20?= =?UTF-8?q?l'h=C3=B4te=20avec=20DATABASE=5FURL.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- backup.sh | 109 ++++++++++++++++++++++++--------------------- restore.sh | 128 +++++++++++++++++++++++++++-------------------------- 2 files changed, 125 insertions(+), 112 deletions(-) diff --git a/backup.sh b/backup.sh index b2758db..b6f81d4 100755 --- a/backup.sh +++ b/backup.sh @@ -4,14 +4,15 @@ set -eu # ============================================================================ # E-COSPLAY - Script de sauvegarde pour migration vers un autre serveur # ---------------------------------------------------------------------------- -# Sauvegarde : -# - Base de données PostgreSQL (pg_dump logique) -# - Volume MinIO (fichiers utilisateurs / médias) -# - Volume Vault (secrets HashiCorp Vault) -# - Fichiers d'environnement (.env.local, google.json, account.json) +# N'UTILISE PAS DOCKER. S'appuie sur les binaires installés sur l'hôte +# (pg_dump, psql, tar, gzip) et lit DATABASE_URL depuis .env.local. # -# Le tout est empaqueté dans une archive tar.gz horodatée, prête à être -# transférée (scp/rsync) puis restaurée avec restore.sh sur le nouveau serveur. +# Sauvegarde : +# - Base PostgreSQL distante via DATABASE_URL (pg_dump logique) +# - Fichiers uploadés (public/storage) +# - Fichiers d'environnement et credentials (.env.local, google.json, ...) +# +# Le tout est empaqueté dans une archive tar.gz horodatée. # ============================================================================ RED='\033[0;31m' @@ -28,12 +29,7 @@ BACKUP_DIR="${BACKUP_DIR:-$PROJECT_DIR/var/backups}" WORK_DIR="$BACKUP_DIR/tmp_$TIMESTAMP" ARCHIVE="$BACKUP_DIR/e-cosplay_backup_$TIMESTAMP.tar.gz" -DB_CONTAINER="${DB_CONTAINER:-e-cosplay_db}" -DB_NAME="${DB_NAME:-app_db}" -DB_USER="${DB_USER:-symfony_user}" - -MINIO_CONTAINER="${MINIO_CONTAINER:-e-cosplay_minio}" -VAULT_CONTAINER="${VAULT_CONTAINER:-e-cosplay_vault}" +STORAGE_DIR="${STORAGE_DIR:-public/storage}" echo "${CYAN}############################${RESET}" echo "${CYAN}# E-COSPLAY BACKUP START #${RESET}" @@ -43,51 +39,67 @@ echo "${CYAN}Destination : ${ARCHIVE}${RESET}" mkdir -p "$WORK_DIR" # --------------------------------------------------------------------------- -# 1) Vérifie que docker tourne et que les conteneurs sont up +# 1) Vérifications # --------------------------------------------------------------------------- -if ! command -v docker >/dev/null 2>&1; then - echo "${RED}docker introuvable dans le PATH${RESET}" >&2 +for bin in pg_dump tar gzip; do + if ! command -v "$bin" >/dev/null 2>&1; then + echo "${RED}Binaire introuvable : $bin${RESET}" >&2 + exit 1 + fi +done + +# --------------------------------------------------------------------------- +# 2) Récupération de DATABASE_URL +# Priorité : variable d'env existante > .env.local > .env.prod.local > .env +# --------------------------------------------------------------------------- +get_database_url() { + if [ -n "${DATABASE_URL:-}" ]; then + echo "$DATABASE_URL" + return 0 + fi + for f in .env.local .env.prod.local .env.prod .env; do + if [ -f "$PROJECT_DIR/$f" ]; then + url="$(grep -E '^DATABASE_URL=' "$PROJECT_DIR/$f" | tail -n1 | sed -E 's/^DATABASE_URL=//; s/^"(.*)"$/\1/; s/^'"'"'(.*)'"'"'$/\1/')" + if [ -n "$url" ]; then + echo "$url" + return 0 + fi + fi + done + return 1 +} + +DATABASE_URL_RAW="$(get_database_url || true)" +if [ -z "$DATABASE_URL_RAW" ]; then + echo "${RED}DATABASE_URL introuvable (ni en env, ni dans .env.local/.env)${RESET}" >&2 exit 1 fi -ensure_running() { - name="$1" - if ! docker ps --format '{{.Names}}' | grep -q "^${name}$"; then - echo "${ORANGE}> Conteneur ${name} arrêté, démarrage...${RESET}" - docker compose up -d "$(echo "$name" | sed 's/^e-cosplay_//')" || true - sleep 3 - fi -} +# Strip des paramètres Doctrine (serverVersion, charset, ...) que libpq ignore +DATABASE_URL_CLEAN="$(echo "$DATABASE_URL_RAW" | sed -E 's/\?.*$//')" -ensure_running "$DB_CONTAINER" -ensure_running "$MINIO_CONTAINER" -ensure_running "$VAULT_CONTAINER" +# Affiche une URL masquée (cache le mot de passe) +DATABASE_URL_MASKED="$(echo "$DATABASE_URL_CLEAN" | sed -E 's#(://[^:]+:)[^@]+(@)#\1***\2#')" +echo "${CYAN}> DATABASE_URL : ${DATABASE_URL_MASKED}${RESET}" # --------------------------------------------------------------------------- -# 2) Dump PostgreSQL (logique, portable entre versions mineures) +# 3) Dump PostgreSQL # --------------------------------------------------------------------------- -echo "${CYAN}> Dump de la base PostgreSQL (${DB_NAME})...${RESET}" -docker exec -t "$DB_CONTAINER" \ - pg_dump -U "$DB_USER" -d "$DB_NAME" --clean --if-exists --no-owner --no-privileges \ +echo "${CYAN}> Dump PostgreSQL...${RESET}" +pg_dump "$DATABASE_URL_CLEAN" \ + --clean --if-exists --no-owner --no-privileges \ | gzip -9 > "$WORK_DIR/database.sql.gz" echo "${GREEN} ✓ database.sql.gz ($(du -h "$WORK_DIR/database.sql.gz" | cut -f1))${RESET}" # --------------------------------------------------------------------------- -# 3) Snapshot des données MinIO (tar du dossier /data du conteneur) +# 4) Fichiers uploadés # --------------------------------------------------------------------------- -echo "${CYAN}> Sauvegarde des données MinIO...${RESET}" -docker exec "$MINIO_CONTAINER" tar -C /data -czf - . > "$WORK_DIR/minio_data.tar.gz" -echo "${GREEN} ✓ minio_data.tar.gz ($(du -h "$WORK_DIR/minio_data.tar.gz" | cut -f1))${RESET}" - -# --------------------------------------------------------------------------- -# 4) Snapshot des données Vault -# --------------------------------------------------------------------------- -echo "${CYAN}> Sauvegarde des données Vault...${RESET}" -if docker exec "$VAULT_CONTAINER" sh -c '[ -d /vault ]' 2>/dev/null; then - docker exec "$VAULT_CONTAINER" tar -C /vault -czf - . > "$WORK_DIR/vault_data.tar.gz" - echo "${GREEN} ✓ vault_data.tar.gz ($(du -h "$WORK_DIR/vault_data.tar.gz" | cut -f1))${RESET}" +if [ -d "$PROJECT_DIR/$STORAGE_DIR" ]; then + echo "${CYAN}> Sauvegarde de ${STORAGE_DIR}...${RESET}" + tar -C "$PROJECT_DIR" -czf "$WORK_DIR/storage.tar.gz" "$STORAGE_DIR" + echo "${GREEN} ✓ storage.tar.gz ($(du -h "$WORK_DIR/storage.tar.gz" | cut -f1))${RESET}" else - echo "${ORANGE} ! /vault introuvable, vault ignoré${RESET}" + echo "${ORANGE} ! ${STORAGE_DIR} introuvable, étape ignorée${RESET}" fi # --------------------------------------------------------------------------- @@ -103,18 +115,15 @@ for f in .env .env.local .env.prod .env.prod.local google.json account.json; do done # --------------------------------------------------------------------------- -# 6) Métadonnées (utiles côté restore pour vérifier la cohérence) +# 6) Métadonnées # --------------------------------------------------------------------------- { echo "timestamp=$TIMESTAMP" echo "hostname=$(hostname)" echo "git_commit=$(git -C "$PROJECT_DIR" rev-parse HEAD 2>/dev/null || echo unknown)" echo "git_branch=$(git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" - echo "db_container=$DB_CONTAINER" - echo "db_name=$DB_NAME" - echo "db_user=$DB_USER" - echo "minio_container=$MINIO_CONTAINER" - echo "vault_container=$VAULT_CONTAINER" + echo "database_url=$DATABASE_URL_MASKED" + echo "storage_dir=$STORAGE_DIR" } > "$WORK_DIR/manifest.txt" # --------------------------------------------------------------------------- diff --git a/restore.sh b/restore.sh index 375e1dc..e2451b1 100755 --- a/restore.sh +++ b/restore.sh @@ -4,17 +4,20 @@ set -eu # ============================================================================ # E-COSPLAY - Script de restauration sur un nouveau serveur # ---------------------------------------------------------------------------- +# N'UTILISE PAS DOCKER. S'appuie sur les binaires installés sur l'hôte +# (psql, tar, gunzip) et lit DATABASE_URL depuis .env.local après l'avoir +# restauré depuis l'archive. +# # Usage : ./restore.sh # # Étapes : -# 1. Décompresse l'archive de sauvegarde +# 1. Décompresse l'archive # 2. Restaure les fichiers d'environnement (.env.local, etc.) -# 3. Démarre la stack docker compose -# 4. Restaure la base PostgreSQL via psql -# 5. Restaure les volumes MinIO et Vault -# 6. Vide le cache Symfony et lance les migrations Doctrine éventuelles +# 3. Restaure la base PostgreSQL via psql + DATABASE_URL +# 4. Restaure les fichiers uploadés (public/storage) +# 5. Vide le cache Symfony et applique les migrations Doctrine # -# ATTENTION : la restauration ÉCRASE les données existantes (DB + volumes). +# ATTENTION : la restauration ÉCRASE les données existantes (DB + storage). # ============================================================================ RED='\033[0;31m' @@ -37,17 +40,12 @@ fi PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$PROJECT_DIR" -# Résout l'archive en chemin absolu AVANT de cd dans le dossier de travail case "$ARCHIVE" in /*) ARCHIVE_ABS="$ARCHIVE" ;; *) ARCHIVE_ABS="$(pwd)/$ARCHIVE" ;; esac -DB_CONTAINER="${DB_CONTAINER:-e-cosplay_db}" -DB_NAME="${DB_NAME:-app_db}" -DB_USER="${DB_USER:-symfony_user}" -MINIO_CONTAINER="${MINIO_CONTAINER:-e-cosplay_minio}" -VAULT_CONTAINER="${VAULT_CONTAINER:-e-cosplay_vault}" +STORAGE_DIR="${STORAGE_DIR:-public/storage}" WORK_DIR="$(mktemp -d /tmp/e-cosplay_restore.XXXXXX)" trap 'rm -rf "$WORK_DIR"' EXIT @@ -57,11 +55,21 @@ echo "${CYAN}# E-COSPLAY RESTORE START #${RESET}" echo "${CYAN}#############################${RESET}" echo "${CYAN}Archive : ${ARCHIVE_ABS}${RESET}" +# --------------------------------------------------------------------------- +# Vérifications +# --------------------------------------------------------------------------- +for bin in psql tar gunzip; do + if ! command -v "$bin" >/dev/null 2>&1; then + echo "${RED}Binaire introuvable : $bin${RESET}" >&2 + exit 1 + fi +done + # --------------------------------------------------------------------------- # Confirmation interactive (skip avec FORCE=1) # --------------------------------------------------------------------------- if [ "${FORCE:-0}" != "1" ]; then - printf "${ORANGE}La restauration va ÉCRASER la base et les volumes existants. Continuer ? [y/N] ${RESET}" + printf "${ORANGE}La restauration va ÉCRASER la base et les fichiers existants. Continuer ? [y/N] ${RESET}" read -r answer case "$answer" in y|Y|yes|YES) ;; @@ -79,8 +87,6 @@ if [ -z "$SRC" ] || [ ! -f "$SRC/manifest.txt" ]; then echo "${RED}Archive invalide : manifest.txt introuvable${RESET}" >&2 exit 1 fi -echo "${GREEN} ✓ Contenu :${RESET}" -ls -la "$SRC" echo "${GREEN} ✓ Manifest :${RESET}" sed 's/^/ /' "$SRC/manifest.txt" @@ -101,22 +107,33 @@ if [ -d "$SRC/env" ]; then fi # --------------------------------------------------------------------------- -# 3) Démarrage de la stack docker compose +# 3) Récupération de DATABASE_URL (après restauration des fichiers env) # --------------------------------------------------------------------------- -echo "${CYAN}> Démarrage de la stack docker compose...${RESET}" -docker compose up -d db minio vault -echo "${CYAN} Attente que PostgreSQL soit prêt...${RESET}" -for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do - if docker exec "$DB_CONTAINER" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then - echo "${GREEN} ✓ PostgreSQL prêt${RESET}" - break +get_database_url() { + if [ -n "${DATABASE_URL:-}" ]; then + echo "$DATABASE_URL" + return 0 fi - sleep 2 - if [ "$i" = "15" ]; then - echo "${RED}PostgreSQL n'est pas devenu disponible à temps${RESET}" >&2 - exit 1 - fi -done + for f in .env.local .env.prod.local .env.prod .env; do + if [ -f "$PROJECT_DIR/$f" ]; then + url="$(grep -E '^DATABASE_URL=' "$PROJECT_DIR/$f" | tail -n1 | sed -E 's/^DATABASE_URL=//; s/^"(.*)"$/\1/; s/^'"'"'(.*)'"'"'$/\1/')" + if [ -n "$url" ]; then + echo "$url" + return 0 + fi + fi + done + return 1 +} + +DATABASE_URL_RAW="$(get_database_url || true)" +if [ -z "$DATABASE_URL_RAW" ]; then + echo "${RED}DATABASE_URL introuvable${RESET}" >&2 + exit 1 +fi +DATABASE_URL_CLEAN="$(echo "$DATABASE_URL_RAW" | sed -E 's/\?.*$//')" +DATABASE_URL_MASKED="$(echo "$DATABASE_URL_CLEAN" | sed -E 's#(://[^:]+:)[^@]+(@)#\1***\2#')" +echo "${CYAN}> DATABASE_URL : ${DATABASE_URL_MASKED}${RESET}" # --------------------------------------------------------------------------- # 4) Restauration PostgreSQL @@ -124,57 +141,44 @@ done if [ -f "$SRC/database.sql.gz" ]; then echo "${CYAN}> Restauration de la base PostgreSQL...${RESET}" gunzip -c "$SRC/database.sql.gz" \ - | docker exec -i "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 >/dev/null + | psql "$DATABASE_URL_CLEAN" -v ON_ERROR_STOP=1 >/dev/null echo "${GREEN} ✓ Base restaurée${RESET}" else echo "${ORANGE} ! database.sql.gz absent, étape ignorée${RESET}" fi # --------------------------------------------------------------------------- -# 5) Restauration MinIO +# 5) Restauration des fichiers uploadés # --------------------------------------------------------------------------- -if [ -f "$SRC/minio_data.tar.gz" ]; then - echo "${CYAN}> Restauration des données MinIO...${RESET}" - docker exec "$MINIO_CONTAINER" sh -c 'rm -rf /data/* /data/..?* /data/.[!.]* 2>/dev/null || true' - docker exec -i "$MINIO_CONTAINER" tar -C /data -xzf - < "$SRC/minio_data.tar.gz" - echo "${GREEN} ✓ MinIO restauré${RESET}" +if [ -f "$SRC/storage.tar.gz" ]; then + echo "${CYAN}> Restauration de ${STORAGE_DIR}...${RESET}" + if [ -d "$PROJECT_DIR/$STORAGE_DIR" ]; then + backup_path="$PROJECT_DIR/${STORAGE_DIR}.bak.$(date +%s)" + mv "$PROJECT_DIR/$STORAGE_DIR" "$backup_path" + echo "${ORANGE} ! ancien dossier déplacé vers $backup_path${RESET}" + fi + tar -xzf "$SRC/storage.tar.gz" -C "$PROJECT_DIR" + echo "${GREEN} ✓ ${STORAGE_DIR} restauré${RESET}" else - echo "${ORANGE} ! minio_data.tar.gz absent, étape ignorée${RESET}" + echo "${ORANGE} ! storage.tar.gz absent, étape ignorée${RESET}" fi # --------------------------------------------------------------------------- -# 6) Restauration Vault +# 6) Cache Symfony + migrations Doctrine # --------------------------------------------------------------------------- -if [ -f "$SRC/vault_data.tar.gz" ]; then - echo "${CYAN}> Restauration des données Vault...${RESET}" - docker exec "$VAULT_CONTAINER" sh -c 'rm -rf /vault/file 2>/dev/null || true' - docker exec -i "$VAULT_CONTAINER" tar -C /vault -xzf - < "$SRC/vault_data.tar.gz" - echo "${CYAN} Redémarrage du conteneur Vault...${RESET}" - docker compose restart vault - echo "${GREEN} ✓ Vault restauré${RESET}" -else - echo "${ORANGE} ! vault_data.tar.gz absent, étape ignorée${RESET}" -fi - -# --------------------------------------------------------------------------- -# 7) Démarrage du reste de la stack + cache + migrations -# --------------------------------------------------------------------------- -echo "${CYAN}> Démarrage du reste de la stack...${RESET}" -docker compose up -d - -if docker ps --format '{{.Names}}' | grep -q '^e-cosplay_php$'; then +if [ -x "$PROJECT_DIR/bin/console" ] && command -v php >/dev/null 2>&1; then echo "${CYAN}> Vidage du cache Symfony...${RESET}" - docker exec e-cosplay_php php bin/console cache:clear --no-warmup || true - docker exec e-cosplay_php php bin/console cache:warmup || true + php "$PROJECT_DIR/bin/console" cache:clear --no-warmup || true + php "$PROJECT_DIR/bin/console" cache:warmup || true echo "${CYAN}> Application des migrations Doctrine éventuelles...${RESET}" - docker exec e-cosplay_php php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration || true + php "$PROJECT_DIR/bin/console" doctrine:migrations:migrate --no-interaction --allow-no-migration || true fi echo "${GREEN}#############################${RESET}" echo "${GREEN}# RESTORE TERMINÉ #${RESET}" echo "${GREEN}#############################${RESET}" echo "${GREEN}Pense à vérifier :${RESET}" -echo " - les variables d'environnement spécifiques au nouveau serveur (PATH_URL, DEV_URL, etc.)" -echo " - les certificats et la configuration Caddy" -echo " - le déverrouillage de Vault si tu utilises un mode non-dev" +echo " - les variables d'environnement spécifiques au nouveau serveur (PATH_URL, DEV_URL, ...)" +echo " - les permissions du dossier $STORAGE_DIR (utilisateur du serveur web)" +echo " - la conf Caddy / Nginx / Apache et les certificats TLS"