#!/bin/bash # Script de déploiement unifié pour GEOSECTOR API # Version: 4.0 (Janvier 2025) # Auteur: Pierre (avec l'aide de Claude) # # Usage: # ./deploy-api.sh # Déploiement local DEV (code → container geo) # ./deploy-api.sh rca # Livraison RECETTE (container geo → rca-geo) # ./deploy-api.sh pra # Livraison PRODUCTION (rca-geo → pra-geo) set -euo pipefail # ===================================== # Configuration générale # ===================================== # Paramètre optionnel pour l'environnement cible TARGET_ENV=${1:-dev} # Configuration SSH HOST_KEY="/home/pierre/.ssh/id_rsa_mbpi" HOST_PORT="22" HOST_USER="root" # Configuration des serveurs RCA_HOST="195.154.80.116" # IN3 - Serveur de recette PRA_HOST="51.159.7.190" # IN4 - Serveur de production # Configuration Incus INCUS_PROJECT="default" API_PATH="/var/www/geosector/api" FINAL_OWNER="nginx" FINAL_GROUP="nginx" FINAL_OWNER_LOGS="nobody" FINAL_GROUP_LOGS="nginx" # Configuration de sauvegarde BACKUP_DIR="/data/backup/geosector/api" # Couleurs pour les messages GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # ===================================== # Fonctions utilitaires # ===================================== echo_step() { echo -e "${GREEN}==>${NC} $1" } echo_info() { echo -e "${BLUE}Info:${NC} $1" } echo_warning() { echo -e "${YELLOW}Warning:${NC} $1" } echo_error() { echo -e "${RED}Error:${NC} $1" exit 1 } # Fonction pour nettoyer les anciens backups cleanup_old_backups() { local prefix="" case $TARGET_ENV in "dev") prefix="api-dev-" ;; "rca") prefix="api-rca-" ;; "pra") prefix="api-pra-" ;; esac echo_info "Cleaning old backups (keeping last 5)..." ls -t "${BACKUP_DIR}"/${prefix}*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f && { REMAINING_BACKUPS=$(ls "${BACKUP_DIR}"/${prefix}*.tar.gz 2>/dev/null | wc -l) echo_info "Kept ${REMAINING_BACKUPS} backup(s) for ${TARGET_ENV}" } } # ===================================== # Détermination de la configuration selon l'environnement # ===================================== case $TARGET_ENV in "dev") echo_step "Configuring for DEV deployment on IN3" SOURCE_TYPE="local_code" DEST_CONTAINER="dva-geo" DEST_HOST="${RCA_HOST}" # IN3 pour le DEV aussi ENV_NAME="DEVELOPMENT" ;; "rca") echo_step "Configuring for RECETTE delivery" SOURCE_TYPE="remote_container" SOURCE_CONTAINER="dva-geo" SOURCE_HOST="${RCA_HOST}" DEST_CONTAINER="rca-geo" DEST_HOST="${RCA_HOST}" ENV_NAME="RECETTE" ;; "pra") echo_step "Configuring for PRODUCTION delivery" SOURCE_TYPE="remote_container" SOURCE_HOST="${RCA_HOST}" SOURCE_CONTAINER="rca-geo" DEST_CONTAINER="pra-geo" DEST_HOST="${PRA_HOST}" ENV_NAME="PRODUCTION" ;; *) echo_error "Unknown environment: $TARGET_ENV. Use 'dev', 'rca' or 'pra'" ;; esac echo_info "Deployment flow: ${ENV_NAME}" # ===================================== # Création de l'archive selon la source # ===================================== # Créer le dossier de backup s'il n'existe pas if [ ! -d "${BACKUP_DIR}" ]; then echo_info "Creating backup directory ${BACKUP_DIR}..." mkdir -p "${BACKUP_DIR}" || echo_error "Failed to create backup directory" fi # Horodatage format YYYYMMDDHH TIMESTAMP=$(date +%Y%m%d%H) # Nom de l'archive selon l'environnement case $TARGET_ENV in "dev") ARCHIVE_NAME="api-dev-${TIMESTAMP}.tar.gz" ;; "rca") ARCHIVE_NAME="api-rca-${TIMESTAMP}.tar.gz" ;; "pra") ARCHIVE_NAME="api-pra-${TIMESTAMP}.tar.gz" ;; esac ARCHIVE_PATH="${BACKUP_DIR}/${ARCHIVE_NAME}" if [ "$SOURCE_TYPE" = "local_code" ]; then # DEV: Créer une archive depuis le code local echo_step "Creating archive from local code..." # Vérification des fichiers requis if [ ! -f "src/Config/AppConfig.php" ]; then echo_error "Configuration file missing" fi if [ ! -f "composer.json" ] || [ ! -f "composer.lock" ]; then echo_error "Composer files missing" fi tar --exclude='.git' \ --exclude='.gitignore' \ --exclude='.vscode' \ --exclude='logs' \ --exclude='sessions' \ --exclude='opendata' \ --exclude='*.template' \ --exclude='*.sh' \ --exclude='.env' \ --exclude='.env_marker' \ --exclude='*.log' \ --exclude='.DS_Store' \ --exclude='README.md' \ --exclude="*.tar.gz" \ --exclude='node_modules' \ --exclude='vendor' \ --exclude='*.swp' \ --exclude='*.swo' \ --exclude='*~' \ -czf "${ARCHIVE_PATH}" . 2>/dev/null || echo_error "Failed to create archive" echo_info "Archive created: ${ARCHIVE_PATH}" echo_info "Archive size: $(du -h "${ARCHIVE_PATH}" | cut -f1)" # Cette section n'est plus utilisée car RCA utilise maintenant remote_container elif [ "$SOURCE_TYPE" = "remote_container" ]; then # RCA et PRA: Créer une archive depuis un container distant echo_step "Creating archive from remote container ${SOURCE_CONTAINER} on ${SOURCE_HOST}..." # Créer l'archive sur le serveur source ssh -i ${HOST_KEY} -p ${HOST_PORT} ${HOST_USER}@${SOURCE_HOST} " incus project switch ${INCUS_PROJECT} && incus exec ${SOURCE_CONTAINER} -- tar \ --exclude='logs' \ --exclude='uploads' \ --exclude='sessions' \ --exclude='opendata' \ -czf /tmp/${ARCHIVE_NAME} -C ${API_PATH} . " || echo_error "Failed to create archive on remote" # Extraire l'archive du container vers l'hôte ssh -i ${HOST_KEY} -p ${HOST_PORT} ${HOST_USER}@${SOURCE_HOST} " incus file pull ${SOURCE_CONTAINER}/tmp/${ARCHIVE_NAME} /tmp/${ARCHIVE_NAME} && incus exec ${SOURCE_CONTAINER} -- rm -f /tmp/${ARCHIVE_NAME} " || echo_error "Failed to extract archive from remote container" # Copier l'archive vers la machine locale scp -i ${HOST_KEY} -P ${HOST_PORT} ${HOST_USER}@${SOURCE_HOST}:/tmp/${ARCHIVE_NAME} ${ARCHIVE_PATH} || echo_error "Failed to copy archive locally" echo_info "Archive saved: ${ARCHIVE_PATH}" echo_info "Archive size: $(du -h "${ARCHIVE_PATH}" | cut -f1)" fi # Nettoyer les anciens backups cleanup_old_backups # ===================================== # Déploiement selon la destination # ===================================== # Tous les déploiements se font maintenant sur des containers distants if [ "$DEST_HOST" != "local" ]; then # Déploiement sur container distant (DEV, RCA ou PRA) echo_step "Deploying to remote container ${DEST_CONTAINER} on ${DEST_HOST}..." # Créer une sauvegarde sur le serveur de destination BACKUP_TIMESTAMP=$(date +"%Y%m%d_%H%M%S") REMOTE_BACKUP_DIR="${API_PATH}_backup_${BACKUP_TIMESTAMP}" echo_info "Creating backup on destination..." ssh -i ${HOST_KEY} -p ${HOST_PORT} ${HOST_USER}@${DEST_HOST} " incus project switch ${INCUS_PROJECT} && incus exec ${DEST_CONTAINER} -- test -d ${API_PATH} && incus exec ${DEST_CONTAINER} -- cp -r ${API_PATH} ${REMOTE_BACKUP_DIR} && echo 'Backup created: ${REMOTE_BACKUP_DIR}' " || echo_warning "No existing installation to backup" # Transférer l'archive vers le serveur de destination echo_info "Transferring archive to ${DEST_HOST}..." if [ "$SOURCE_TYPE" = "local_code" ]; then # Pour DEV: copier depuis local vers IN3 scp -i ${HOST_KEY} -P ${HOST_PORT} ${ARCHIVE_PATH} ${HOST_USER}@${DEST_HOST}:/tmp/${ARCHIVE_NAME} || echo_error "Failed to copy archive to destination" elif [ "$SOURCE_TYPE" = "remote_container" ] && [ "$SOURCE_HOST" = "$DEST_HOST" ]; then # Pour RCA: même serveur (IN3), pas de transfert nécessaire, l'archive est déjà là echo_info "Archive already on destination server (same host)" else # Pour PRA: l'archive est déjà sur la machine locale (copiée depuis IN3) # On la transfère maintenant vers IN4 echo_info "Transferring archive from local to IN4..." scp -i ${HOST_KEY} -P ${HOST_PORT} ${ARCHIVE_PATH} ${HOST_USER}@${DEST_HOST}:/tmp/${ARCHIVE_NAME} || echo_error "Failed to copy archive to IN4" # Nettoyer sur le serveur source IN3 ssh -i ${HOST_KEY} -p ${HOST_PORT} ${HOST_USER}@${SOURCE_HOST} "rm -f /tmp/${ARCHIVE_NAME}" || echo_warning "Could not clean source server" fi # Déployer sur le container de destination echo_info "Extracting on destination container..." # Déterminer le nom de l'environnement pour le marqueur case $TARGET_ENV in "dev") ENV_MARKER="development" ;; "rca") ENV_MARKER="recette" ;; "pra") ENV_MARKER="production" ;; esac ssh -i ${HOST_KEY} -p ${HOST_PORT} ${HOST_USER}@${DEST_HOST} " set -euo pipefail # Pousser l'archive dans le container incus project switch ${INCUS_PROJECT} && incus file push /tmp/${ARCHIVE_NAME} ${DEST_CONTAINER}/tmp/${ARCHIVE_NAME} && # Nettoyer sélectivement (préserver logs, uploads et sessions) incus exec ${DEST_CONTAINER} -- find ${API_PATH} -mindepth 1 -maxdepth 1 ! -name 'uploads' ! -name 'logs' ! -name 'sessions' -exec rm -rf {} \; 2>/dev/null || true && # Extraire l'archive incus exec ${DEST_CONTAINER} -- tar -xzf /tmp/${ARCHIVE_NAME} -C ${API_PATH}/ && # Créer le marqueur d'environnement pour la détection CLI incus exec ${DEST_CONTAINER} -- bash -c 'echo \"${ENV_MARKER}\" > ${API_PATH}/.env_marker' && # Permissions incus exec ${DEST_CONTAINER} -- chown -R ${FINAL_OWNER}:${FINAL_GROUP} ${API_PATH} && incus exec ${DEST_CONTAINER} -- find ${API_PATH} -type d -exec chmod 755 {} \; && incus exec ${DEST_CONTAINER} -- find ${API_PATH} -type f -exec chmod 644 {} \; && # Permissions spéciales pour logs incus exec ${DEST_CONTAINER} -- mkdir -p ${API_PATH}/logs/events && incus exec ${DEST_CONTAINER} -- chown -R ${FINAL_OWNER_LOGS}:${FINAL_GROUP} ${API_PATH}/logs && incus exec ${DEST_CONTAINER} -- find ${API_PATH}/logs -type d -exec chmod 750 {} \; && incus exec ${DEST_CONTAINER} -- find ${API_PATH}/logs -type f -exec chmod 640 {} \; && # Permissions spéciales pour uploads incus exec ${DEST_CONTAINER} -- mkdir -p ${API_PATH}/uploads && incus exec ${DEST_CONTAINER} -- chown -R ${FINAL_OWNER_LOGS}:${FINAL_GROUP} ${API_PATH}/uploads && incus exec ${DEST_CONTAINER} -- find ${API_PATH}/uploads -type d -exec chmod 750 {} \; && incus exec ${DEST_CONTAINER} -- find ${API_PATH}/uploads -type f -exec chmod 640 {} \; && # Permissions spéciales pour sessions incus exec ${DEST_CONTAINER} -- mkdir -p ${API_PATH}/sessions && incus exec ${DEST_CONTAINER} -- chown -R ${FINAL_OWNER_LOGS}:${FINAL_GROUP} ${API_PATH}/sessions && incus exec ${DEST_CONTAINER} -- chmod 700 ${API_PATH}/sessions && # Composer (installation stricte - échec bloquant) incus exec ${DEST_CONTAINER} -- bash -c 'cd ${API_PATH} && composer install --no-dev --optimize-autoloader' || { echo 'ERROR: Composer install failed'; exit 1; } && # Nettoyage incus exec ${DEST_CONTAINER} -- rm -f /tmp/${ARCHIVE_NAME} && rm -f /tmp/${ARCHIVE_NAME} " || echo_error "Deployment failed on destination" echo_info "Remote backup saved: ${REMOTE_BACKUP_DIR} on ${DEST_CONTAINER}" # Nettoyage des anciens backups sur le container distant echo_info "Cleaning old backup directories on ${DEST_CONTAINER}..." ssh -i ${HOST_KEY} -p ${HOST_PORT} ${HOST_USER}@${DEST_HOST} " incus exec ${DEST_CONTAINER} -- bash -c 'rm -rf ${API_PATH}_backup_*' " && echo_info "Old backups cleaned" || echo_warning "Could not clean old backups" # ===================================== # Configuration des tâches CRON # ===================================== echo_step "Configuring CRON tasks..." ssh -i ${HOST_KEY} -p ${HOST_PORT} ${HOST_USER}@${DEST_HOST} " incus exec ${DEST_CONTAINER} -- bash <<'EOFCRON' # Sauvegarder les crons existants (hors geosector) crontab -l 2>/dev/null | grep -v 'geosector/api/scripts/cron' > /tmp/crontab_backup || true # Créer le nouveau crontab avec les tâches CRON pour l'API cat /tmp/crontab_backup > /tmp/new_crontab cat >> /tmp/new_crontab <<'EOF' # GEOSECTOR API - Email queue processing (every 5 minutes) */5 * * * * /usr/bin/php /var/www/geosector/api/scripts/cron/process_email_queue.php >> /var/www/geosector/api/logs/email_queue.log 2>&1 # GEOSECTOR API - Security data cleanup (daily at 2am) 0 2 * * * /usr/bin/php /var/www/geosector/api/scripts/cron/cleanup_security_data.php >> /var/www/geosector/api/logs/cleanup_security.log 2>&1 # GEOSECTOR API - Stripe devices update (weekly Sunday at 3am) 0 3 * * 0 /usr/bin/php /var/www/geosector/api/scripts/cron/update_stripe_devices.php >> /var/www/geosector/api/logs/stripe_devices.log 2>&1 EOF # Installer le nouveau crontab crontab /tmp/new_crontab # Nettoyer rm -f /tmp/crontab_backup /tmp/new_crontab # Afficher les crons installés echo 'CRON tasks installed:' crontab -l | grep geosector EOFCRON " && echo_info "CRON tasks configured successfully" || echo_warning "CRON configuration failed" fi # L'archive reste dans le dossier de backup, pas de nettoyage nécessaire echo_info "Archive preserved in backup directory: ${ARCHIVE_PATH}" # ===================================== # Résumé final # ===================================== echo_step "Deployment completed successfully!" echo_info "Environment: ${ENV_NAME}" if [ "$TARGET_ENV" = "dev" ]; then echo_info "Deployed from local code to container ${DEST_CONTAINER} on IN3 (${DEST_HOST})" elif [ "$TARGET_ENV" = "rca" ]; then echo_info "Delivered from ${SOURCE_CONTAINER} to ${DEST_CONTAINER} on ${DEST_HOST}" elif [ "$TARGET_ENV" = "pra" ]; then echo_info "Delivered from ${SOURCE_CONTAINER} on ${SOURCE_HOST} to ${DEST_CONTAINER} on ${DEST_HOST}" fi echo_info "Deployment completed at: $(date)" # Journaliser le déploiement echo "$(date '+%Y-%m-%d %H:%M:%S') - API deployed to ${ENV_NAME} (${DEST_CONTAINER}) - Archive: ${ARCHIVE_NAME}" >> ~/.geo_deploy_history