#!/bin/bash # E-Ticket - Script d'import pour migration de serveur # Restaure un bundle de migration genere par backup.sh sur un nouveau serveur. # # Le bundle est attendu au format e_ticket_migration_.tar.gz et contient: # db.sql.gz, public_uploads.tar.gz, var_state.tar.gz, env.local, cert/, manifest.txt # # Usage: # ./restore.sh [--yes] [--skip-db] [--skip-uploads] [--skip-var] [--skip-secrets] # # Variables d'environnement supportees: # COMPOSE_FILE : fichier docker-compose a utiliser (defaut: docker-compose-prod.yml) # DB_SERVICE : service Docker de la base (defaut: db-master) # DB_USER : utilisateur PostgreSQL (defaut: e-ticket) # DB_NAME : base de donnees a restaurer (defaut: e-ticket) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" COMPOSE_FILE="${COMPOSE_FILE:-${SCRIPT_DIR}/docker-compose-prod.yml}" DB_SERVICE="${DB_SERVICE:-db-master}" DB_USER="${DB_USER:-e-ticket}" DB_NAME="${DB_NAME:-e-ticket}" BUNDLE="" ASSUME_YES="false" SKIP_DB="false" SKIP_UPLOADS="false" SKIP_VAR="false" SKIP_SECRETS="false" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" } err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERREUR: $*" >&2 } usage() { cat <<'EOF' Usage: ./restore.sh [options] Options: --yes, -y Mode non interactif (pas de confirmation) --skip-db Ne pas restaurer la base de donnees --skip-uploads Ne pas restaurer public/uploads --skip-var Ne pas restaurer var/ (payouts, billets, invoices, unsubscribed.json) --skip-secrets Ne pas restaurer .env.local et config/cert/ -h, --help Affiche cette aide ATTENTION: La restauration ECRASE les donnees existantes ! EOF } # --- Parsing des arguments --- while [ $# -gt 0 ]; do case "$1" in --yes|-y) ASSUME_YES="true"; shift ;; --skip-db) SKIP_DB="true"; shift ;; --skip-uploads) SKIP_UPLOADS="true"; shift ;; --skip-var) SKIP_VAR="true"; shift ;; --skip-secrets) SKIP_SECRETS="true"; shift ;; -h|--help) usage; exit 0 ;; --*) err "Option inconnue: $1"; usage; exit 1 ;; *) if [ -z "${BUNDLE}" ]; then BUNDLE="$1" else err "Argument en trop: $1" usage exit 1 fi shift ;; esac done if [ -z "${BUNDLE}" ]; then err "Aucun bundle specifie" usage exit 1 fi if [ ! -f "${BUNDLE}" ]; then err "Bundle introuvable: ${BUNDLE}" exit 1 fi # --- Extraction du bundle dans un dossier temporaire --- TMP_DIR="$(mktemp -d -t e_ticket_restore_XXXXXX)" cleanup() { rm -rf "${TMP_DIR}" } trap cleanup EXIT log "=== Restauration du bundle de migration E-Ticket ===" log "Bundle: ${BUNDLE}" log "Extraction dans ${TMP_DIR}..." if ! tar -xzf "${BUNDLE}" -C "${TMP_DIR}"; then err "Echec d'extraction du bundle" exit 1 fi # Trouve le dossier extrait (e_ticket_migration_) EXTRACTED_DIR="$(find "${TMP_DIR}" -maxdepth 1 -mindepth 1 -type d -name 'e_ticket_migration_*' | head -n1)" if [ -z "${EXTRACTED_DIR}" ] || [ ! -d "${EXTRACTED_DIR}" ]; then err "Bundle invalide: dossier e_ticket_migration_* introuvable" exit 1 fi # --- Verification du manifest et des checksums --- if [ ! -f "${EXTRACTED_DIR}/manifest.txt" ]; then err "Manifest manquant dans le bundle" exit 1 fi log "Verification des checksums..." (cd "${EXTRACTED_DIR}" && grep -E '^[a-f0-9]{64} ' manifest.txt | sha256sum -c --quiet) || { err "Checksums invalides - bundle corrompu" exit 1 } log " OK - bundle integre" echo echo "================================================================" echo " Manifest du bundle:" echo "================================================================" grep -E '^(bundle_name|created_at|source_host|git_commit|git_branch|db_name|skip_secrets):' "${EXTRACTED_DIR}/manifest.txt" || true echo "================================================================" # --- Verification des composants disponibles --- HAS_DB="false" HAS_UPLOADS="false" HAS_VAR="false" HAS_ENV="false" HAS_CERT="false" [ -f "${EXTRACTED_DIR}/db.sql.gz" ] && HAS_DB="true" [ -f "${EXTRACTED_DIR}/public_uploads.tar.gz" ] && HAS_UPLOADS="true" [ -f "${EXTRACTED_DIR}/var_state.tar.gz" ] && HAS_VAR="true" [ -f "${EXTRACTED_DIR}/env.local" ] && HAS_ENV="true" [ -d "${EXTRACTED_DIR}/cert" ] && HAS_CERT="true" echo echo " Plan de restauration:" echo "----------------------------------------------------------------" [ "${HAS_DB}" = "true" ] && [ "${SKIP_DB}" = "false" ] && echo " [X] Base de donnees -> ${DB_NAME} (ECRASE)" || echo " [ ] Base de donnees" [ "${HAS_UPLOADS}" = "true" ] && [ "${SKIP_UPLOADS}" = "false" ] && echo " [X] public/uploads/ (ECRASE, sauvegarde de securite)" || echo " [ ] public/uploads/" [ "${HAS_VAR}" = "true" ] && [ "${SKIP_VAR}" = "false" ] && echo " [X] var/ (payouts, billets, invoices, unsubscribed.json)" || echo " [ ] var/" [ "${HAS_ENV}" = "true" ] && [ "${SKIP_SECRETS}" = "false" ] && echo " [X] .env.local (ECRASE)" || echo " [ ] .env.local" [ "${HAS_CERT}" = "true" ] && [ "${SKIP_SECRETS}" = "false" ] && echo " [X] config/cert/ (ECRASE)" || echo " [ ] config/cert/" echo "----------------------------------------------------------------" echo if [ "${ASSUME_YES}" != "true" ]; then read -r -p "Confirmer la restauration ? (tapez 'oui' pour continuer) : " CONFIRM if [ "${CONFIRM}" != "oui" ]; then log "Restauration annulee par l'utilisateur" exit 0 fi fi # --- 1. Secrets (en premier pour que la stack puisse demarrer si besoin) --- if [ "${SKIP_SECRETS}" = "false" ]; then if [ "${HAS_ENV}" = "true" ]; then log "[secrets] Restauration de .env.local..." if [ -f "${SCRIPT_DIR}/.env.local" ]; then cp -p "${SCRIPT_DIR}/.env.local" "${SCRIPT_DIR}/.env.local.before_restore_$(date +%Y%m%d_%H%M%S)" fi cp -p "${EXTRACTED_DIR}/env.local" "${SCRIPT_DIR}/.env.local" chmod 600 "${SCRIPT_DIR}/.env.local" log " OK" fi if [ "${HAS_CERT}" = "true" ]; then log "[secrets] Restauration de config/cert/..." mkdir -p "${SCRIPT_DIR}/config/cert" cp -rp "${EXTRACTED_DIR}/cert/." "${SCRIPT_DIR}/config/cert/" chmod -R go-rwx "${SCRIPT_DIR}/config/cert" log " OK" fi fi # --- 2. Base de donnees --- if [ "${HAS_DB}" = "true" ] && [ "${SKIP_DB}" = "false" ]; then log "[db] Verification du service ${DB_SERVICE}..." if [ ! -f "${COMPOSE_FILE}" ]; then err "Fichier docker-compose introuvable: ${COMPOSE_FILE}" exit 1 fi if ! docker compose -f "${COMPOSE_FILE}" ps --services --status running 2>/dev/null | grep -qx "${DB_SERVICE}"; then err "Le service '${DB_SERVICE}' n'est pas demarre. Lancer: make start_prod" exit 1 fi log "[db] Restauration de la base ${DB_NAME}..." if ! gunzip -c "${EXTRACTED_DIR}/db.sql.gz" \ | docker compose -f "${COMPOSE_FILE}" exec -T "${DB_SERVICE}" \ psql -U "${DB_USER}" -d "${DB_NAME}" -v ON_ERROR_STOP=1 >/dev/null; then err "Echec de la restauration PostgreSQL" exit 1 fi log " OK" fi # --- 3. public/uploads --- if [ "${HAS_UPLOADS}" = "true" ] && [ "${SKIP_UPLOADS}" = "false" ]; then log "[uploads] Restauration de public/uploads..." if [ -d "${SCRIPT_DIR}/public/uploads" ]; then SAFETY="${SCRIPT_DIR}/public/uploads.before_restore_$(date +%Y%m%d_%H%M%S)" log " Sauvegarde de securite: ${SAFETY}" mv "${SCRIPT_DIR}/public/uploads" "${SAFETY}" fi mkdir -p "${SCRIPT_DIR}/public" if ! tar -xzf "${EXTRACTED_DIR}/public_uploads.tar.gz" -C "${SCRIPT_DIR}/public"; then err "Echec d'extraction de public_uploads.tar.gz" exit 1 fi log " OK" fi # --- 4. var/ (PDFs generes, unsubscribed.json...) --- if [ "${HAS_VAR}" = "true" ] && [ "${SKIP_VAR}" = "false" ]; then log "[var] Restauration de var/ (payouts, billets, invoices, unsubscribed.json)..." mkdir -p "${SCRIPT_DIR}/var" # Sauvegarde de securite des elements existants qui seront ecrases SAFETY_VAR="${SCRIPT_DIR}/var/.before_restore_$(date +%Y%m%d_%H%M%S)" mkdir -p "${SAFETY_VAR}" for item in payouts billets invoices unsubscribed.json; do if [ -e "${SCRIPT_DIR}/var/${item}" ]; then mv "${SCRIPT_DIR}/var/${item}" "${SAFETY_VAR}/" fi done # Si rien n'a ete sauvegarde, on supprime le dossier vide if [ -z "$(ls -A "${SAFETY_VAR}")" ]; then rmdir "${SAFETY_VAR}" else log " Sauvegarde de securite: ${SAFETY_VAR}" fi if ! tar -xzf "${EXTRACTED_DIR}/var_state.tar.gz" -C "${SCRIPT_DIR}/var"; then err "Echec d'extraction de var_state.tar.gz" exit 1 fi log " OK" fi log "=== Migration terminee avec succes ===" log log "Etapes recommandees post-migration:" log " 1. make start_prod (si pas deja fait)" log " 2. make migrate_prod (appliquer les migrations Doctrine eventuelles)" log " 3. make clear_prod (vider le cache)" log " 4. Verifier le bon fonctionnement de l'application"