#!/usr/bin/env bash # ============================================================================ # migrate-backup.sh # ---------------------------------------------------------------------------- # Crée une archive complète de l'application (BDD + fichiers + config) afin # de migrer le CRM Ludikevent d'un serveur vers un autre. # # Contenu de l'archive : # - dump PostgreSQL (pg_dump --format=custom) # - dump Redis (RDB) si redis-cli est disponible # - dossiers d'uploads : public/media, public/images, public/pdf, public/seo # - var/storage, sauvegarde # - fichiers de config : .env.local, google.json # - manifeste (versions, date, hostname) # # Usage : # ./bin/migrate-backup.sh # archive dans ./sauvegarde/ # ./bin/migrate-backup.sh -o /tmp/backup.tar.gz # chemin de sortie explicite # ./bin/migrate-backup.sh --no-redis # ignorer Redis # ./bin/migrate-backup.sh --docker # forcer l'utilisation de docker # ============================================================================ set -euo pipefail # --- Couleurs ---------------------------------------------------------------- RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; CYAN='\033[0;36m'; RESET='\033[0m' log() { printf "${CYAN}[backup]${RESET} %s\n" "$*"; } ok() { printf "${GREEN}[ ok ]${RESET} %s\n" "$*"; } warn() { printf "${YELLOW}[warn ]${RESET} %s\n" "$*"; } err() { printf "${RED}[error]${RESET} %s\n" "$*" >&2; } # --- Args -------------------------------------------------------------------- PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" OUTPUT="" SKIP_REDIS=0 FORCE_DOCKER=0 while [[ $# -gt 0 ]]; do case "$1" in -o|--output) OUTPUT="$2"; shift 2 ;; --no-redis) SKIP_REDIS=1; shift ;; --docker) FORCE_DOCKER=1; shift ;; -h|--help) sed -n '2,22p' "$0"; exit 0 ;; *) err "Argument inconnu : $1"; exit 1 ;; esac done cd "$PROJECT_DIR" TIMESTAMP="$(date +%Y-%m-%d_%H-%M-%S)" HOSTNAME_SHORT="$(hostname -s 2>/dev/null || hostname)" [[ -z "$OUTPUT" ]] && OUTPUT="$PROJECT_DIR/sauvegarde/migrate_${HOSTNAME_SHORT}_${TIMESTAMP}.tar.gz" mkdir -p "$(dirname "$OUTPUT")" WORK_DIR="$(mktemp -d -t crm-backup-XXXXXX)" trap 'rm -rf "$WORK_DIR"' EXIT log "Projet : $PROJECT_DIR" log "Destination : $OUTPUT" log "Workdir : $WORK_DIR" # --- Lecture des variables d'environnement ---------------------------------- ENV_FILE="" for f in .env.local .env; do [[ -f "$PROJECT_DIR/$f" ]] && ENV_FILE="$PROJECT_DIR/$f" && break done [[ -z "$ENV_FILE" ]] && { err "Aucun fichier .env trouvé."; exit 1; } log "Env source : $ENV_FILE" # Récupère DATABASE_URL (dernière valeur définie, sans guillemets) DATABASE_URL="$(grep -E '^DATABASE_URL=' "$ENV_FILE" | tail -n1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")" [[ -z "$DATABASE_URL" ]] && { err "DATABASE_URL introuvable dans $ENV_FILE"; exit 1; } # postgresql://user:pass@host:port/db?... re='^postgres(ql)?://([^:]+):([^@]*)@([^:/]+)(:([0-9]+))?/([^?]+)' if [[ ! "$DATABASE_URL" =~ $re ]]; then err "DATABASE_URL non reconnue : $DATABASE_URL"; exit 1 fi PG_USER="${BASH_REMATCH[2]}" PG_PASS="${BASH_REMATCH[3]}" PG_HOST="${BASH_REMATCH[4]}" PG_PORT="${BASH_REMATCH[6]:-5432}" PG_DB="${BASH_REMATCH[7]}" log "PostgreSQL : $PG_USER@$PG_HOST:$PG_PORT/$PG_DB" # --- Détection du mode (natif ou docker) ------------------------------------ USE_DOCKER=0 if [[ $FORCE_DOCKER -eq 1 ]]; then USE_DOCKER=1 elif ! command -v pg_dump >/dev/null 2>&1; then if command -v docker >/dev/null 2>&1 && docker ps --format '{{.Names}}' | grep -q '^crm_db$'; then USE_DOCKER=1 warn "pg_dump non installé, fallback sur docker exec crm_db" fi fi # --- Dump PostgreSQL --------------------------------------------------------- log "Dump PostgreSQL en cours…" DUMP_FILE="$WORK_DIR/database.dump" if [[ $USE_DOCKER -eq 1 ]]; then docker exec -e PGPASSWORD="$PG_PASS" crm_db \ pg_dump -U "$PG_USER" -d "$PG_DB" --format=custom --no-owner --no-acl > "$DUMP_FILE" else PGPASSWORD="$PG_PASS" pg_dump \ -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \ --format=custom --no-owner --no-acl -f "$DUMP_FILE" fi DUMP_SIZE="$(du -h "$DUMP_FILE" | cut -f1)" ok "Dump BDD : $DUMP_SIZE" # --- Dump Redis (best effort) ------------------------------------------------ if [[ $SKIP_REDIS -eq 0 ]]; then REDIS_DSN="$(grep -E '^REDIS_DSN=' "$ENV_FILE" | tail -n1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")" if [[ -n "$REDIS_DSN" ]]; then # redis://[password@]host[:port] rre='^redis://(([^@]*)@)?([^:/]+)(:([0-9]+))?' if [[ "$REDIS_DSN" =~ $rre ]]; then R_PASS="${BASH_REMATCH[2]}" R_HOST="${BASH_REMATCH[3]}" R_PORT="${BASH_REMATCH[5]:-6379}" REDIS_FILE="$WORK_DIR/redis.rdb" log "Dump Redis ($R_HOST:$R_PORT)…" if command -v redis-cli >/dev/null 2>&1; then if [[ -n "$R_PASS" ]]; then redis-cli -h "$R_HOST" -p "$R_PORT" -a "$R_PASS" --no-auth-warning --rdb "$REDIS_FILE" >/dev/null \ && ok "Dump Redis : $(du -h "$REDIS_FILE" | cut -f1)" \ || warn "Échec dump Redis (ignoré)" else redis-cli -h "$R_HOST" -p "$R_PORT" --rdb "$REDIS_FILE" >/dev/null \ && ok "Dump Redis : $(du -h "$REDIS_FILE" | cut -f1)" \ || warn "Échec dump Redis (ignoré)" fi else warn "redis-cli absent, dump Redis ignoré" fi fi fi fi # --- Copie des fichiers (uploads + config) ----------------------------------- FILES_DIR="$WORK_DIR/files" mkdir -p "$FILES_DIR" PATHS=( "public/media" "public/images" "public/pdf" "public/seo" "public/tmp-sign" "var/storage" "sauvegarde" ".env.local" "google.json" ) log "Archivage des fichiers…" for p in "${PATHS[@]}"; do if [[ -e "$PROJECT_DIR/$p" ]]; then mkdir -p "$FILES_DIR/$(dirname "$p")" cp -a "$PROJECT_DIR/$p" "$FILES_DIR/$p" ok " + $p" else warn " - $p (absent)" fi done # Évite de réembarquer l'archive courante si elle est dans sauvegarde/ if [[ "$OUTPUT" == "$PROJECT_DIR/sauvegarde/"* && -e "$FILES_DIR/sauvegarde/$(basename "$OUTPUT")" ]]; then rm -f "$FILES_DIR/sauvegarde/$(basename "$OUTPUT")" fi # --- Manifeste --------------------------------------------------------------- GIT_COMMIT="$(git -C "$PROJECT_DIR" rev-parse HEAD 2>/dev/null || echo "n/a")" GIT_BRANCH="$(git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "n/a")" PHP_VER="$(php -r 'echo PHP_VERSION;' 2>/dev/null || echo "n/a")" cat > "$WORK_DIR/MANIFEST.txt" <