#!/bin/bash ############################################################################# # Script de mise à jour production - Formation Manager Itinova # Version: 2.0 # # Ce script gère: # - Sauvegarde automatique de la base de données # - Sauvegarde des fichiers # - Extraction du package # - Application des migrations SQL versionnées # - Redémarrage du service # - Vérification post-déploiement ############################################################################# set -e # Arrêter en cas d'erreur set -u # Arrêter si variable non définie # Couleurs pour les messages RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration APP_DIR="/var/www/formation-manager-itinova" BACKUP_DIR="$APP_DIR/backups" MIGRATIONS_DIR="$APP_DIR/migrations" TIMESTAMP=$(date +%Y%m%d-%H%M%S) LOG_FILE="$APP_DIR/logs/update-$TIMESTAMP.log" # Fonction pour afficher les messages log_info() { echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE" } log_success() { echo -e "${GREEN}[✓]${NC} $1" | tee -a "$LOG_FILE" } log_warning() { echo -e "${YELLOW}[⚠]${NC} $1" | tee -a "$LOG_FILE" } log_error() { echo -e "${RED}[✗]${NC} $1" | tee -a "$LOG_FILE" } # Fonction pour vérifier les prérequis check_prerequisites() { log_info "Vérification des prérequis..." # Vérifier que nous sommes dans le bon répertoire if [ ! -f "$APP_DIR/package.json" ]; then log_error "Erreur: package.json non trouvé dans $APP_DIR" exit 1 fi # Vérifier que le fichier .env existe if [ ! -f "$APP_DIR/.env" ]; then log_error "Erreur: fichier .env non trouvé" exit 1 fi # Créer les répertoires nécessaires mkdir -p "$BACKUP_DIR" "$MIGRATIONS_DIR" "$(dirname "$LOG_FILE")" log_success "Prérequis vérifiés" } # Fonction pour charger les variables d'environnement load_env() { log_info "Chargement des variables d'environnement..." # Charger le .env set -a source "$APP_DIR/.env" set +a # Extraire les informations de connexion MySQL depuis DATABASE_URL # Format: mysql://user:password@host:port/database if [ -z "${DATABASE_URL:-}" ]; then log_error "DATABASE_URL non définie dans .env" exit 1 fi # Parser DATABASE_URL DB_USER=$(echo "$DATABASE_URL" | sed -n 's/.*:\/\/\([^:]*\):.*/\1/p') DB_PASS=$(echo "$DATABASE_URL" | sed -n 's/.*:\/\/[^:]*:\([^@]*\)@.*/\1/p') DB_HOST=$(echo "$DATABASE_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p') DB_PORT=$(echo "$DATABASE_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p') DB_NAME=$(echo "$DATABASE_URL" | sed -n 's/.*\/\([^?]*\).*/\1/p') if [ -z "$DB_USER" ] || [ -z "$DB_NAME" ]; then log_error "Impossible de parser DATABASE_URL" exit 1 fi log_success "Variables d'environnement chargées" } # Fonction pour sauvegarder la base de données backup_database() { log_info "Sauvegarde de la base de données..." BACKUP_FILE="$BACKUP_DIR/db-backup-$TIMESTAMP.sql" # Créer un fichier de configuration MySQL temporaire pour éviter le warning MYSQL_CONFIG=$(mktemp) cat > "$MYSQL_CONFIG" << EOF [client] user=$DB_USER password=$DB_PASS host=$DB_HOST port=$DB_PORT EOF # Sauvegarder if mysqldump --defaults-extra-file="$MYSQL_CONFIG" \ --single-transaction \ --routines \ --triggers \ "$DB_NAME" > "$BACKUP_FILE" 2>&1; then log_success "Sauvegarde créée: $BACKUP_FILE" else log_warning "Sauvegarde partielle (certains privilèges manquants)" # Ne pas arrêter le script, la sauvegarde partielle est acceptable fi # Nettoyer rm -f "$MYSQL_CONFIG" # Compresser la sauvegarde gzip "$BACKUP_FILE" log_success "Sauvegarde compressée: $BACKUP_FILE.gz" # Garder seulement les 10 dernières sauvegardes ls -t "$BACKUP_DIR"/db-backup-*.sql.gz | tail -n +11 | xargs -r rm } # Fonction pour sauvegarder les fichiers backup_files() { log_info "Sauvegarde des fichiers..." BACKUP_FILE="$BACKUP_DIR/files-backup-$TIMESTAMP.tar.gz" # Sauvegarder les fichiers importants (exclure node_modules et dist) tar -czf "$BACKUP_FILE" \ --exclude='node_modules' \ --exclude='dist' \ --exclude='backups' \ --exclude='logs' \ -C "$APP_DIR" \ . 2>/dev/null || true log_success "Fichiers sauvegardés: $BACKUP_FILE" # Garder seulement les 5 dernières sauvegardes de fichiers ls -t "$BACKUP_DIR"/files-backup-*.tar.gz | tail -n +6 | xargs -r rm } # Fonction pour détecter le gestionnaire de processus detect_process_manager() { if systemctl is-active --quiet formation-manager 2>/dev/null; then echo "systemd" elif pm2 list | grep -q "formation-manager"; then echo "pm2" else echo "unknown" fi } # Fonction pour arrêter le service stop_service() { log_info "Arrêt du service..." PROCESS_MANAGER=$(detect_process_manager) case "$PROCESS_MANAGER" in systemd) sudo systemctl stop formation-manager log_success "Service arrêté (systemd)" ;; pm2) pm2 stop formation-manager 2>/dev/null || true log_success "Service arrêté (PM2)" ;; *) log_warning "Gestionnaire de processus non détecté, le service n'a pas été arrêté" ;; esac } # Fonction pour extraire le package extract_package() { local PACKAGE_PATH="$1" log_info "Extraction du package..." if [ ! -f "$PACKAGE_PATH" ]; then log_error "Package non trouvé: $PACKAGE_PATH" exit 1 fi # Créer un répertoire temporaire TEMP_DIR=$(mktemp -d) # Extraire dans le répertoire temporaire tar -xzf "$PACKAGE_PATH" -C "$TEMP_DIR" # Copier les nouveaux fichiers (préserver .env) rsync -av --exclude='.env' --exclude='backups' --exclude='logs' "$TEMP_DIR/" "$APP_DIR/" # Nettoyer rm -rf "$TEMP_DIR" log_success "Package extrait" } # Fonction pour obtenir la version actuelle de la base de données get_current_db_version() { MYSQL_CONFIG=$(mktemp) cat > "$MYSQL_CONFIG" << EOF [client] user=$DB_USER password=$DB_PASS host=$DB_HOST port=$DB_PORT EOF # Créer la table de versions si elle n'existe pas mysql --defaults-extra-file="$MYSQL_CONFIG" "$DB_NAME" << 'EOF' 2>/dev/null CREATE TABLE IF NOT EXISTS schema_migrations ( version VARCHAR(50) PRIMARY KEY, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, description TEXT ); EOF # Obtenir la dernière version VERSION=$(mysql --defaults-extra-file="$MYSQL_CONFIG" "$DB_NAME" -sN << 'EOF' 2>/dev/null SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1; EOF ) rm -f "$MYSQL_CONFIG" echo "${VERSION:-0}" } # Fonction pour appliquer les migrations apply_migrations() { log_info "Application des migrations..." CURRENT_VERSION=$(get_current_db_version) log_info "Version actuelle de la base de données: $CURRENT_VERSION" # Créer un fichier de configuration MySQL MYSQL_CONFIG=$(mktemp) cat > "$MYSQL_CONFIG" << EOF [client] user=$DB_USER password=$DB_PASS host=$DB_HOST port=$DB_PORT EOF # Appliquer chaque migration non appliquée MIGRATIONS_APPLIED=0 for MIGRATION_FILE in "$MIGRATIONS_DIR"/*.sql; do if [ ! -f "$MIGRATION_FILE" ]; then continue fi MIGRATION_VERSION=$(basename "$MIGRATION_FILE" .sql) # Vérifier si la migration a déjà été appliquée ALREADY_APPLIED=$(mysql --defaults-extra-file="$MYSQL_CONFIG" "$DB_NAME" -sN << EOF SELECT COUNT(*) FROM schema_migrations WHERE version = '$MIGRATION_VERSION'; EOF ) if [ "$ALREADY_APPLIED" -eq 0 ]; then log_info "Application de la migration: $MIGRATION_VERSION" # Appliquer la migration if mysql --defaults-extra-file="$MYSQL_CONFIG" "$DB_NAME" < "$MIGRATION_FILE" 2>&1 | tee -a "$LOG_FILE"; then # Enregistrer la migration mysql --defaults-extra-file="$MYSQL_CONFIG" "$DB_NAME" << EOF INSERT INTO schema_migrations (version, description) VALUES ('$MIGRATION_VERSION', 'Migration appliquée automatiquement'); EOF log_success "Migration $MIGRATION_VERSION appliquée avec succès" MIGRATIONS_APPLIED=$((MIGRATIONS_APPLIED + 1)) else log_error "Échec de la migration $MIGRATION_VERSION" rm -f "$MYSQL_CONFIG" exit 1 fi else log_info "Migration $MIGRATION_VERSION déjà appliquée (ignorée)" fi done rm -f "$MYSQL_CONFIG" if [ $MIGRATIONS_APPLIED -eq 0 ]; then log_info "Aucune nouvelle migration à appliquer" else log_success "$MIGRATIONS_APPLIED migration(s) appliquée(s)" fi } # Fonction pour installer les dépendances install_dependencies() { log_info "Installation des dépendances..." cd "$APP_DIR" if command -v pnpm &> /dev/null; then pnpm install --prod 2>&1 | tee -a "$LOG_FILE" else npm install --production 2>&1 | tee -a "$LOG_FILE" fi log_success "Dépendances installées" } # Fonction pour démarrer le service start_service() { log_info "Démarrage du service..." PROCESS_MANAGER=$(detect_process_manager) case "$PROCESS_MANAGER" in systemd) sudo systemctl start formation-manager sleep 3 if systemctl is-active --quiet formation-manager; then log_success "Service démarré (systemd)" else log_error "Échec du démarrage du service" exit 1 fi ;; pm2) pm2 restart formation-manager 2>&1 | tee -a "$LOG_FILE" sleep 3 if pm2 list | grep -q "formation-manager.*online"; then log_success "Service démarré (PM2)" else log_error "Échec du démarrage du service" exit 1 fi ;; *) log_warning "Gestionnaire de processus non détecté, démarrage manuel requis" ;; esac } # Fonction pour vérifier le service verify_service() { log_info "Vérification du service..." # Attendre que le service démarre sleep 5 # Vérifier que le service répond if curl -f http://localhost:3000/ &>/dev/null; then log_success "Service opérationnel" else log_warning "Le service ne répond pas sur le port 3000" fi # Afficher les derniers logs PROCESS_MANAGER=$(detect_process_manager) case "$PROCESS_MANAGER" in systemd) log_info "Derniers logs:" sudo journalctl -u formation-manager -n 20 --no-pager | tee -a "$LOG_FILE" ;; pm2) log_info "Derniers logs:" pm2 logs formation-manager --lines 20 --nostream | tee -a "$LOG_FILE" ;; esac } # Fonction principale main() { echo "" echo "=========================================" echo " Mise à jour Formation Manager Itinova" echo "=========================================" echo "" # Demander le chemin du package if [ $# -eq 0 ]; then read -p "Chemin du package tar.gz: " PACKAGE_PATH else PACKAGE_PATH="$1" fi # Vérifier les prérequis check_prerequisites # Charger les variables d'environnement load_env # Sauvegardes backup_database backup_files # Arrêter le service stop_service # Extraire le package extract_package "$PACKAGE_PATH" # Appliquer les migrations apply_migrations # Installer les dépendances install_dependencies # Démarrer le service start_service # Vérifier le service verify_service echo "" log_success "Mise à jour terminée avec succès!" echo "" log_info "Logs complets: $LOG_FILE" log_info "Sauvegarde DB: $BACKUP_DIR/db-backup-$TIMESTAMP.sql.gz" echo "" } # Exécuter main "$@"