♻️ refactor(migration): Retire l'usage de docker dans backup.sh et restore.sh, utilise pg_dump/psql et tar directement sur l'hôte avec DATABASE_URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
109
backup.sh
109
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"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
128
restore.sh
128
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 <chemin/vers/e-cosplay_backup_YYYYMMDD_HHMMSS.tar.gz>
|
||||
#
|
||||
# É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"
|
||||
|
||||
Reference in New Issue
Block a user