Files
ludikevent_crm/bin/migrate-backup.sh

207 lines
7.6 KiB
Bash
Raw Normal View History

#!/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" <<EOF
Migration backup CRM Ludikevent
================================
Date : $(date -Iseconds)
Hostname : $(hostname)
Project dir : $PROJECT_DIR
Git branch : $GIT_BRANCH
Git commit : $GIT_COMMIT
PHP version : $PHP_VER
DB target : $PG_USER@$PG_HOST:$PG_PORT/$PG_DB
Mode : $([[ $USE_DOCKER -eq 1 ]] && echo "docker" || echo "native")
EOF
# --- Création de l'archive ---------------------------------------------------
log "Compression de l'archive…"
tar -czf "$OUTPUT" -C "$WORK_DIR" .
ARCHIVE_SIZE="$(du -h "$OUTPUT" | cut -f1)"
ok "Archive prête : $OUTPUT ($ARCHIVE_SIZE)"
cat <<EOF
${GREEN}✔ Backup terminé.${RESET}
Pour migrer vers un autre serveur :
scp "$OUTPUT" user@nouveau-serveur:/tmp/
ssh user@nouveau-serveur
cd /chemin/vers/crm
./bin/migrate-restore.sh /tmp/$(basename "$OUTPUT")
EOF