#!/usr/bin/env bash # ============================================================================== # NexusPBX - Script de Mise à Jour Automatique (OTA) # ============================================================================== set -e # Couleurs pour le terminal C_RESET="\033[0m" C_GREEN="\033[1;32m" C_CYAN="\033[1;36m" C_YELLOW="\033[1;33m" C_RED="\033[1;31m" C_BOLD="\033[1m" C_GRAY="\033[0;90m" APP_DIR="/var/www/nexus-pbx" GIT_REMOTE="origin" GIT_BRANCH="main" # Vérification des privilèges if [ "$(id -u)" -ne 0 ]; then echo -e "${C_RED}[ERREUR] Ce script doit être exécuté en root (ou via sudo).${C_RESET}" exit 1 fi MODE="update" for arg in "$@"; do case "$arg" in --check|-c) MODE="check" ;; --force|-f) MODE="force" ;; esac done cd "$APP_DIR" || exit 1 git config --system --add safe.directory "$APP_DIR" 2>/dev/null || true git config --global --add safe.directory "$APP_DIR" 2>/dev/null || true # Lecture de la version locale CURRENT_VER="Inconnue" if [ -f "$APP_DIR/version.json" ]; then CURRENT_VER=$(grep -o '"version"[^:]*:[^"]*"[^"]*"' "$APP_DIR/version.json" | cut -d'"' -f4) fi echo -e "${C_CYAN}${C_BOLD}==========================================================${C_RESET}" echo -e "${C_CYAN}${C_BOLD} NexusPBX - Gestionnaire de Mises à Jour ${C_RESET}" echo -e "${C_CYAN}${C_BOLD}==========================================================${C_RESET}" echo -e "Version actuellement installée : ${C_BOLD}v${CURRENT_VER}${C_RESET}" # Récupération des informations distantes echo -e "${C_GRAY}-> Recherche de nouvelles versions sur le dépôt officiel...${C_RESET}" git fetch "$GIT_REMOTE" "$GIT_BRANCH" --quiet 2>/dev/null || { echo -e "${C_RED}[AVERTISSEMENT] Impossible de contacter le serveur distant Git.${C_RESET}" } # Extraction de la version distante depuis le commit distant REMOTE_VER=$(git show "${GIT_REMOTE}/${GIT_BRANCH}:version.json" 2>/dev/null | grep -o '"version"[^:]*:[^"]*"[^"]*"' | cut -d'"' -f4 || echo "$CURRENT_VER") LOCAL_HASH=$(git rev-parse HEAD 2>/dev/null || echo "local") REMOTE_HASH=$(git rev-parse "${GIT_REMOTE}/${GIT_BRANCH}" 2>/dev/null || echo "remote") if [ "$MODE" = "check" ]; then if [ "$LOCAL_HASH" != "$REMOTE_HASH" ] || [ "$CURRENT_VER" != "$REMOTE_VER" ]; then echo -e "${C_GREEN}${C_BOLD}[NOUVEAUTÉ] Une mise à jour est disponible !${C_RESET}" echo -e "Version disponible : ${C_GREEN}${C_BOLD}v${REMOTE_VER}${C_RESET} (actuelle: v${CURRENT_VER})" exit 0 else echo -e "${C_GREEN}[OK] Votre système NexusPBX est à jour (v${CURRENT_VER}).${C_RESET}" exit 0 fi fi if [ "$LOCAL_HASH" = "$REMOTE_HASH" ] && [ "$CURRENT_VER" = "$REMOTE_VER" ] && [ "$MODE" != "force" ]; then echo -e "${C_GREEN}[OK] Vous disposez déjà de la dernière version (v${CURRENT_VER}).${C_RESET}" echo -e "${C_GRAY}(Utilisez 'nexus-update --force' pour forcer une réinstallation complète des fichiers.)${C_RESET}" exit 0 fi echo -e "\n${C_YELLOW}-> Mise à jour vers la version v${REMOTE_VER}...${C_RESET}" # Nettoyage préventif d'éventuels anciens dossiers temporaires HA dans /tmp find /tmp -maxdepth 1 \( -name 'nexus_cluster_extract_*' -o -name 'nexus_cluster_tar_*' -o -name 'nexus_cluster_recv_*' -o -name 'nexus_cluster_sync_*' -o -name 'nexus_cluster_db_*' \) -mmin +10 -exec rm -rf {} + 2>/dev/null || true # 1. Sauvegarde préventive automatique (Disaster Recovery complète) echo -e "${C_GRAY}[1/6] Création d'une sauvegarde préventive du système (Disaster Recovery)...${C_RESET}" php -r ' require_once "/var/www/nexus-pbx/src/Database.php"; require_once "/var/www/nexus-pbx/src/BackupManager.php"; try { $res = App\BackupManager::createBackup("pre-update"); echo " ✓ Sauvegarde Disaster Recovery créée : " . $res["filename"] . " (" . $res["size_formatted"] . ")\n"; } catch (\Throwable $e) { echo " ! Avertissement sauvegarde préventive : " . $e->getMessage() . "\n"; } ' || true # 2. Application des derniers fichiers de code Git echo -e "${C_GRAY}[2/6] Déploiement des nouveaux fichiers de code...${C_RESET}" git reset --hard FETCH_HEAD 2>/dev/null || git reset --hard "${GIT_REMOTE}/${GIT_BRANCH}" chown -R www-data:asterisk "$APP_DIR" 2>/dev/null || true chmod 2775 "$APP_DIR/storage" "$APP_DIR/storage/backups" "$APP_DIR/storage/sounds" 2>/dev/null || true mkdir -p /etc/letsencrypt /var/log/letsencrypt /var/lib/letsencrypt 2>/dev/null || true chgrp -R asterisk /etc/letsencrypt 2>/dev/null || true chmod -R g+rX /etc/letsencrypt 2>/dev/null || true chmod +x "$APP_DIR/scripts/update.sh" "$APP_DIR/scripts/nexus-firewall" "$APP_DIR/scripts/nexus-banner.sh" "$APP_DIR/scripts/nexus-cluster" 2>/dev/null || true chmod +x "$APP_DIR"/scripts/cluster/*.sh 2>/dev/null || true # Liens symboliques CLI (non-bloquants si exécuté sous sandbox PHP ProtectSystem) ln -sf "$APP_DIR/scripts/update.sh" /usr/local/bin/nexus-update 2>/dev/null || true ln -sf "$APP_DIR/scripts/nexus-firewall" /usr/local/bin/nexus-firewall 2>/dev/null || true ln -sf "$APP_DIR/scripts/nexus-banner.sh" /usr/local/bin/nexus-banner 2>/dev/null || true ln -sf "$APP_DIR/scripts/nexus-cluster" /usr/local/bin/nexus-cluster 2>/dev/null || true # Configuration sysctl pour l'IP flottante (VIP HA) if [ -w /etc/sysctl.d ]; then sysctl -w net.ipv4.ip_nonlocal_bind=1 >/dev/null 2>&1 || true echo "net.ipv4.ip_nonlocal_bind=1" > /etc/sysctl.d/99-nexus-ha.conf 2>/dev/null || true fi # Pré-déploiement des scripts Keepalived HA if [ -d "$APP_DIR/scripts/cluster" ] && [ -w /etc ]; then mkdir -p /etc/keepalived/scripts 2>/dev/null || true cp -f "$APP_DIR/scripts/cluster/"*.sh /etc/keepalived/scripts/ 2>/dev/null || true chown -R root:root /etc/keepalived 2>/dev/null || true chmod 755 /etc/keepalived /etc/keepalived/scripts 2>/dev/null || true chmod 755 /etc/keepalived/scripts/*.sh 2>/dev/null || true fi # Cron synchronisation Cluster HA if [ -w /etc/cron.d ]; then cat << 'EOF_CRON' > /etc/cron.d/nexus-cluster-sync 2>/dev/null || true # Synchronisation périodique du cluster NexusPBX HA vers le nœud Backup */2 * * * * www-data /usr/bin/php /var/www/nexus-pbx/scripts/nexus_cluster_sync.php >> /var/log/nexus_cluster.log 2>&1 EOF_CRON chmod 0644 /etc/cron.d/nexus-cluster-sync 2>/dev/null || true touch /var/log/nexus_cluster.log 2>/dev/null || true chown www-data:asterisk /var/log/nexus_cluster.log 2>/dev/null || true chmod 666 /var/log/nexus_cluster.log 2>/dev/null || true fi # Optimisation de la réserve de processus PHP-FPM for pool in /etc/php/*/fpm/pool.d/www.conf; do if [ -f "$pool" ] && [ -w "$pool" ]; then sed -i 's/^pm.max_children = .*/pm.max_children = 25/' "$pool" 2>/dev/null || true sed -i 's/^pm.start_servers = .*/pm.start_servers = 4/' "$pool" 2>/dev/null || true sed -i 's/^pm.min_spare_servers = .*/pm.min_spare_servers = 2/' "$pool" 2>/dev/null || true sed -i 's/^pm.max_spare_servers = .*/pm.max_spare_servers = 6/' "$pool" 2>/dev/null || true fi done # Mise à jour des privilèges sudoers (www-data) si accessible en écriture if [ -w /etc/sudoers.d ]; then cat << 'EOF_SUDO' > /etc/sudoers.d/nexus-pbx 2>/dev/null || true www-data ALL=(ALL) NOPASSWD: /usr/sbin/asterisk, /usr/sbin/nginx, /usr/bin/systemctl, /usr/bin/fail2ban-client, /bin/fail2ban-client, /var/www/nexus-pbx/scripts/nexus-firewall, /usr/bin/certbot, /usr/sbin/nft, /usr/sbin/iptables, /usr/sbin/ufw, /var/www/nexus-pbx/scripts/update.sh, /usr/local/bin/nexus-update, /var/www/nexus-pbx/scripts/nexus-cluster EOF_SUDO chmod 0440 /etc/sudoers.d/nexus-pbx 2>/dev/null || true fi # 3. Vérification des dépendances Debian echo -e "${C_GRAY}[3/6] Vérification des composants et outils système...${C_RESET}" which sqlite3 >/dev/null 2>&1 && which ffmpeg >/dev/null 2>&1 && which rsync >/dev/null 2>&1 && which sox >/dev/null 2>&1 && which sshpass >/dev/null 2>&1 && which keepalived >/dev/null 2>&1 && which arping >/dev/null 2>&1 || { echo -e " -> Installation de paquets complémentaires..." (apt-get update -y -qq && apt-get install -y -qq --no-install-recommends sqlite3 acl ffmpeg rsync sox sshpass keepalived iputils-arping) 2>/dev/null || true } # 4. Exécution des migrations SQLite, régénération Dialplan, Nginx & Fail2ban UCP echo -e "${C_GRAY}[4/6] Exécution des migrations de base de données & régénération dialplan/web...${C_RESET}" if [ -w /var/log ]; then touch /var/log/nexus_ucp_security.log 2>/dev/null || true chown www-data:asterisk /var/log/nexus_ucp_security.log 2>/dev/null || true chmod 0664 /var/log/nexus_ucp_security.log 2>/dev/null || true fi [ -d "$APP_DIR/ucp" ] && chmod 0755 "$APP_DIR/ucp" "$APP_DIR/ucp/assets" 2>/dev/null || true php -r ' require_once "/var/www/nexus-pbx/src/Database.php"; require_once "/var/www/nexus-pbx/src/ConfigGenerator.php"; require_once "/var/www/nexus-pbx/src/FirewallManager.php"; $db = App\Database::getConnection(true); $gen = new App\ConfigGenerator($db); $gen->writeAllConfigs(); try { $st = App\FirewallManager::getSettings(); App\FirewallManager::ensureUcpFail2banArtifacts(); App\FirewallManager::applyNginxConfig( (int)($st["admin_https_port"] ?? 8443), (int)($st["certbot_strict_port80"] ?? 0), (int)($st["ucp_enabled"] ?? 1), (int)($st["ucp_https_port"] ?? 8444), (int)($st["ucp_http_port"] ?? 8084) ); App\FirewallManager::generateJailLocal( (string)($st["fail2ban_whitelist_ips"] ?? ""), (int)($st["fail2ban_sip_maxretry"] ?? 3), (int)($st["fail2ban_sip_findtime"] ?? 600), (int)($st["fail2ban_sip_bantime"] ?? 3600), (int)($st["fail2ban_web_maxretry"] ?? 3), (int)($st["fail2ban_web_bantime"] ?? 3600), (int)($st["admin_https_port"] ?? 8443), (int)($st["ucp_enabled"] ?? 1), (int)($st["ucp_https_port"] ?? 8444), (int)($st["ucp_http_port"] ?? 8084), (int)($st["fail2ban_ucp_maxretry"] ?? 3), (int)($st["fail2ban_ucp_bantime"] ?? 3600) ); } catch (\Throwable $e) { fwrite(STDERR, "Warning UCP/Firewall update: " . $e->getMessage() . "\n"); } ' # 5. Fix permissions & ACLs echo -e "${C_GRAY}[5/6] Application des permissions et ACLs système...${C_RESET}" php -r ' require_once "/var/www/nexus-pbx/src/Database.php"; require_once "/var/www/nexus-pbx/src/SystemChecker.php"; App\SystemChecker::ensureAsteriskIncludes(); ' 2>/dev/null || true mkdir -p /var/spool/asterisk/voicemail/default /var/lib/asterisk/sounds/custom /var/lib/asterisk/moh 2>/dev/null || true for lang in fr en es; do if [ -d "/usr/share/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang" ]; then ln -sf "/usr/share/asterisk/sounds/$lang" "/var/lib/asterisk/sounds/$lang" 2>/dev/null || true fi if [ -d "/var/lib/asterisk/sounds/$lang" ] && [ ! -e "/var/lib/asterisk/sounds/$lang/custom" ]; then ln -sf /var/lib/asterisk/sounds/custom "/var/lib/asterisk/sounds/$lang/custom" 2>/dev/null || true fi done if [ ! -e "/usr/share/asterisk/sounds/custom" ]; then ln -sf /var/lib/asterisk/sounds/custom /usr/share/asterisk/sounds/custom 2>/dev/null || true fi chown -R asterisk:asterisk /var/spool/asterisk /var/lib/asterisk/sounds /var/lib/asterisk/moh /var/log/asterisk 2>/dev/null || true chmod 2775 /var/spool/asterisk /var/spool/asterisk/voicemail /var/lib/asterisk/sounds /var/lib/asterisk/moh /var/log/asterisk /var/log/asterisk/cdr-csv 2>/dev/null || true mkdir -p /var/log/asterisk/cdr-csv 2>/dev/null || true touch /var/log/asterisk/cdr-csv/Master.csv 2>/dev/null || true chmod 0664 /var/log/asterisk/cdr-csv/Master.csv 2>/dev/null || true setfacl -R -m u:www-data:rwx,g:asterisk:rwx /etc/asterisk /var/lib/asterisk/sounds /var/lib/asterisk/moh /var/spool/asterisk /var/log/asterisk 2>/dev/null || true setfacl -R -d -m u:www-data:rwx,g:asterisk:rwx /etc/asterisk /var/lib/asterisk/sounds /var/lib/asterisk/moh /var/spool/asterisk /var/log/asterisk 2>/dev/null || true chmod 0664 /etc/asterisk/*.conf /etc/asterisk/*.custom.conf 2>/dev/null || true chown root:asterisk /etc/fail2ban/jail.local /etc/nginx/sites-available/nexus-pbx 2>/dev/null || true chmod 0664 /etc/fail2ban/jail.local /etc/nginx/sites-available/nexus-pbx 2>/dev/null || true setfacl -m u:www-data:rw /etc/fail2ban/jail.local /etc/nginx/sites-available/nexus-pbx 2>/dev/null || true # 6. Rechargement des services echo -e "${C_GRAY}[6/6] Rechargement à chaud d'Asterisk et des services web...${C_RESET}" asterisk -rx "core reload" >/dev/null 2>&1 || true asterisk -rx "pjsip reload" >/dev/null 2>&1 || true asterisk -rx "dialplan reload" >/dev/null 2>&1 || true # Rechargement des services web : différé si exécuté depuis l'interface web pour éviter de couper la requête HTTP if [ -t 1 ]; then systemctl reload php8.4-fpm 2>/dev/null || systemctl reload php8.2-fpm 2>/dev/null || systemctl reload php*-fpm 2>/dev/null || true systemctl reload nginx 2>/dev/null || true fail2ban-client reload >/dev/null 2>&1 || true else (nohup bash -c "sleep 2 && (systemctl reload php8.4-fpm 2>/dev/null || systemctl reload php8.2-fpm 2>/dev/null || systemctl reload php*-fpm 2>/dev/null || true); systemctl reload nginx 2>/dev/null || true; fail2ban-client reload >/dev/null 2>&1 || true" >/dev/null 2>&1 &) fi NEW_VER="Inconnue" if [ -f "$APP_DIR/version.json" ]; then NEW_VER=$(grep -o '"version"[^:]*:[^"]*"[^"]*"' "$APP_DIR/version.json" | cut -d'"' -f4) fi echo "" echo -e "${C_GREEN}${C_BOLD}==========================================================${C_RESET}" echo -e "${C_GREEN}${C_BOLD} [SUCCÈS] NexusPBX a été mis à jour avec succès en v${NEW_VER} !${C_RESET}" echo -e "${C_GREEN}${C_BOLD}==========================================================${C_RESET}" echo "" # Affichage du changelog de la nouvelle version if [ -f "$APP_DIR/version.json" ]; then echo -e "${C_CYAN}${C_BOLD}Nouveautés de cette version :${C_RESET}" php -r ' $v = json_decode(file_get_contents("/var/www/nexus-pbx/version.json"), true); if (!empty($v["changelog"])) { foreach ($v["changelog"] as $line) { echo " • " . $line . "\n"; } } ' fi echo ""