#!/bin/bash # Copyright (C) 2026 Cédric Abonnel # License: GNU Affero General Public License v3 set -euo pipefail # --- Configuration --- BASE_DIR="/opt/monitoring" CONF_DIR="${BASE_DIR}/conf" LOG_DIR="/var/log/monitoring" STATE_DIR="/var/lib/monitoring" LOCK_DIR="/var/lock/monitoring" TMP_DIR="/tmp/monitoring-install" # Journal des fichiers installés pour déinstallation/état des lieux INSTALLED_LOG="${STATE_DIR}/installed-files.log" UPDATE_BASE_URL="https://git.abonnel.fr/cedricAbonnel/scripts-bash/raw/branch/main/servers/linux/monitoring" MANIFEST_URL="${UPDATE_BASE_URL}/manifest.txt" INSTALL_DEPS="${INSTALL_DEPS:-true}" # --- Fonctions d'affichage --- info() { echo -e "\e[34m[INFO]\e[0m $1"; } ok() { echo -e "\e[32m[OK]\e[0m $1"; } warn() { echo -e "\e[33m[WARN]\e[0m $1"; } err() { echo -e "\e[31m[ERR]\e[0m $1"; } # --- Fonctions Techniques --- require_root() { if [ "${EUID}" -ne 0 ]; then err "Ce script doit être exécuté en root." exit 1 fi } install_deps() { if [ "${INSTALL_DEPS}" != "true" ]; then return 0; fi info "Vérification des dépendances système..." if command -v apt-get >/dev/null 2>&1; then apt-get update -qq apt-get install -y -qq curl coreutils findutils grep sed gawk util-linux ca-certificates php-cli php-curl php-common smartmontools > /dev/null ok "Dépendances installées." fi } prepare_dirs() { info "Préparation de l'arborescence dans ${BASE_DIR}..." mkdir -p "${BASE_DIR}/bin" "${BASE_DIR}/lib" "${CONF_DIR}" "${LOG_DIR}" "${STATE_DIR}" "${LOCK_DIR}" "${TMP_DIR}" touch "$INSTALLED_LOG" } fetch_manifest() { info "Téléchargement du manifeste distant..." curl -fsS "${MANIFEST_URL}" -o "${TMP_DIR}/manifest.txt" } validate_manifest() { awk ' NF == 3 && $1 ~ /^[0-9a-fA-F]{64}$/ && $2 ~ /^(644|755|600)$/ && $3 ~ /^(bin|lib|conf)\/[A-Za-z0-9._\/-]+$/ && $3 !~ /\.\./ ' "${TMP_DIR}/manifest.txt" } download_and_install() { local expected_hash=$1 mode=$2 rel_path=$3 local dst="${BASE_DIR}/${rel_path}" if [ -f "$dst" ]; then local current_hash current_hash=$(sha256sum "$dst" | awk '{print $1}') [ "$current_hash" == "$expected_hash" ] && return 0 info "Mise à jour : $rel_path" else info "Installation : $rel_path" fi local tmp_file tmp_file="$(mktemp "${TMP_DIR}/file.XXXXXX")" if ! curl -fsS "${UPDATE_BASE_URL}/${rel_path}" -o "$tmp_file"; then err "Échec du téléchargement pour $rel_path" rm -f "$tmp_file" return 1 fi local got_hash got_hash=$(sha256sum "$tmp_file" | awk '{print $1}') if [ "$got_hash" != "$expected_hash" ]; then err "Hash invalide pour $rel_path" rm -f "$tmp_file" return 1 fi mkdir -p "$(dirname "$dst")" mv -f "$tmp_file" "$dst" chmod "$mode" "$dst" } # --- NOUVEAUTÉ : Gestion du journal et purge propre --- update_installed_log() { # On sauvegarde la liste des chemins relatifs du manifeste validé dans le journal permanent awk '{print $3}' "${TMP_DIR}/manifest-valid.txt" > "$INSTALLED_LOG" ok "Journal des fichiers déployés mis à jour ($INSTALLED_LOG)." } purge_obsolete_files() { info "Analyse des fichiers obsolètes (Synchronisation avec le journal)..." # On compare ce qui était installé (journal) avec ce qui est dans le nouveau manifeste if [ ! -s "$INSTALLED_LOG" ]; then warn "Journal vide, passage en mode scan classique." # Fallback sur le scan de dossier si le journal n'existe pas encore find "${BASE_DIR}/bin" "${BASE_DIR}/lib" "${BASE_DIR}/conf" -type f 2>/dev/null | while read -r local_file; do local rel_path="${local_file#$BASE_DIR/}" [[ "$rel_path" == *".local."* ]] && continue if ! grep -qw "$rel_path" "${TMP_DIR}/manifest-valid.txt"; then warn "Suppression : $rel_path" rm -f "$local_file" fi done return fi # Mode Journal : On lit l'ancien journal pour voir ce qui doit disparaître while read -r old_file; do # Si le fichier du journal n'est plus dans le nouveau manifeste if ! grep -qw "$old_file" "${TMP_DIR}/manifest-valid.txt"; then if [ -f "${BASE_DIR}/$old_file" ]; then # Protection ultime des fichiers .local (au cas où ils auraient été loggués par erreur) if [[ "$old_file" != *".local."* ]]; then warn "Suppression du fichier obsolète : $old_file" rm -f "${BASE_DIR}/$old_file" fi fi fi done < "$INSTALLED_LOG" } # --- Main --- main() { require_root install_deps prepare_dirs fetch_manifest if ! validate_manifest > "${TMP_DIR}/manifest-valid.txt"; then err "Le manifeste est invalide ou corrompu." exit 1 fi echo "--------------------------------------------------" info "Phase 1 : Installation et mises à jour" while read -r hash mode rel_path; do [ -n "${hash:-}" ] || continue download_and_install "$hash" "$mode" "$rel_path" done < "${TMP_DIR}/manifest-valid.txt" echo "--------------------------------------------------" info "Phase 2 : Nettoyage et Journalisation" purge_obsolete_files update_installed_log rm -rf "${TMP_DIR}" echo "--------------------------------------------------" ok "Opération terminée avec succès." # --- Vérification de la configuration --- local_conf="${CONF_DIR}/monitoring.local.conf.php" orig_conf="${CONF_DIR}/monitoring.conf.php" if [ -f "$local_conf" ] && [ -f "$orig_conf" ]; then if [ "$(sha256sum "$local_conf" | awk '{print $1}')" == "$(sha256sum "$orig_conf" | awk '{print $1}')" ]; then echo -e "\n\e[33m[ATTENTION]\e[0m Votre fichier de configuration est identique à l'original." warn "Pensez à éditer ${local_conf}." else ok "Configuration locale personnalisée détectée." fi fi } main "$@"