mise à jour de la logique d'install et update

This commit is contained in:
2026-03-18 08:20:13 +01:00
parent 312ba59343
commit fba9bc89e2
2 changed files with 104 additions and 313 deletions

View File

@@ -17,45 +17,42 @@ MANIFEST_URL="${UPDATE_BASE_URL}/manifest.txt"
INSTALL_DEPS="${INSTALL_DEPS:-true}" INSTALL_DEPS="${INSTALL_DEPS:-true}"
# --- Fonctions --- # --- 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() { require_root() {
if [ "${EUID}" -ne 0 ]; then if [ "${EUID}" -ne 0 ]; then
echo "ERREUR: Ce script doit être exécuté en root." >&2 err "Ce script doit être exécuté en root."
exit 1 exit 1
fi fi
} }
install_deps() { install_deps() {
if [ "${INSTALL_DEPS}" != "true" ]; then if [ "${INSTALL_DEPS}" != "true" ]; then return 0; fi
return 0 info "Vérification des dépendances système..."
fi
echo "--- Installation des dépendances ---"
if command -v apt-get >/dev/null 2>&1; then if command -v apt-get >/dev/null 2>&1; then
apt-get update apt-get update -qq
apt-get install -y curl coreutils findutils grep sed gawk util-linux ca-certificates apt-get install -y -qq curl coreutils findutils grep sed gawk util-linux ca-certificates php-cli php-curl php-common > /dev/null
# Ajout des modules PHP nécessaires pour vos scripts (curl pour ntfy) ok "Dépendances installées."
apt-get install -y php-cli php-curl php-common
else
echo "AVERTISSEMENT: Gestionnaire de paquets apt non détecté. Assurez-vous que php-cli et php-curl sont installés."
fi fi
} }
prepare_dirs() { prepare_dirs() {
echo "--- Préparation des répertoires ---" 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}" mkdir -p "${BASE_DIR}/bin" "${BASE_DIR}/lib" "${CONF_DIR}" "${LOG_DIR}" "${STATE_DIR}" "${LOCK_DIR}" "${TMP_DIR}"
chmod 755 "${BASE_DIR}" "${CONF_DIR}" "${LOG_DIR}" "${STATE_DIR}" "${LOCK_DIR}"
} }
fetch_manifest() { fetch_manifest() {
echo "--- Récupération du manifeste ---" info "Téléchargement du manifeste distant..."
curl -fsS "${MANIFEST_URL}" -o "${TMP_DIR}/manifest.txt" curl -fsS "${MANIFEST_URL}" -o "${TMP_DIR}/manifest.txt"
} }
validate_manifest() { validate_manifest() {
# Validation du format : Hash Mode Chemin
# Exemple : a1b2... 755 bin/script.php
awk ' awk '
NF == 3 && NF == 3 &&
$1 ~ /^[0-9a-fA-F]{64}$/ && $1 ~ /^[0-9a-fA-F]{64}$/ &&
@@ -65,35 +62,36 @@ validate_manifest() {
' "${TMP_DIR}/manifest.txt" ' "${TMP_DIR}/manifest.txt"
} }
download_one() { download_and_install() {
local expected_hash="$1" local expected_hash="$1"
local mode="$2" local mode="$2"
local rel_path="$3" local rel_path="$3"
local url="${UPDATE_BASE_URL}/${rel_path}"
local dst="${BASE_DIR}/${rel_path}" local dst="${BASE_DIR}/${rel_path}"
# On ignore le téléchargement si c'est un fichier de conf qui existe déjà if [ -f "$dst" ]; then
if [[ "$rel_path" == conf/* ]] && [ -f "$dst" ]; then local current_hash
echo "Skip: $rel_path (existe déjà)" current_hash=$(sha256sum "$dst" | awk '{print $1}')
return 0 if [ "$current_hash" == "$expected_hash" ]; then
return 0 # Déjà à jour
fi
info "Mise à jour : $rel_path"
else
info "Installation : $rel_path"
fi fi
echo "Téléchargement: $rel_path"
local tmp_file local tmp_file
tmp_file="$(mktemp "${TMP_DIR}/file.XXXXXX")" tmp_file="$(mktemp "${TMP_DIR}/file.XXXXXX")"
if ! curl -fsS "$url" -o "$tmp_file"; then if ! curl -fsS "${UPDATE_BASE_URL}/${rel_path}" -o "$tmp_file"; then
echo "ERREUR: Échec du téléchargement de ${url}" >&2 err "Échec du téléchargement pour $rel_path"
rm -f "$tmp_file" rm -f "$tmp_file"
return 1 return 1
fi fi
local got_hash local got_hash
got_hash="$(sha256sum "$tmp_file" | awk '{print $1}')" got_hash=$(sha256sum "$tmp_file" | awk '{print $1}')
if [ "$got_hash" != "$expected_hash" ]; then if [ "$got_hash" != "$expected_hash" ]; then
echo "ERREUR: Hash invalide pour ${rel_path}" >&2 err "Hash invalide pour $rel_path"
rm -f "$tmp_file" rm -f "$tmp_file"
return 1 return 1
fi fi
@@ -103,34 +101,24 @@ download_one() {
chmod "$mode" "$dst" chmod "$mode" "$dst"
} }
install_from_manifest() { purge_obsolete_files() {
echo "--- Installation des fichiers ---" info "Analyse des fichiers obsolètes (Synchronisation avec Git)..."
while read -r hash mode rel_path; do # On scanne bin, lib et conf
[ -n "${hash:-}" ] || continue find "${BASE_DIR}/bin" "${BASE_DIR}/lib" "${BASE_DIR}/conf" -type f | while read -r local_file; do
download_one "$hash" "$mode" "$rel_path"
done < "${TMP_DIR}/manifest-valid.txt"
}
show_next_steps() { local rel_path="${local_file#$BASE_DIR/}"
cat <<EOF
Installation terminée avec succès dans ${BASE_DIR}. # PROTECTION : On ne touche jamais aux fichiers locaux personnels
if [[ "$rel_path" == *".local."* ]]; then
continue
fi
Étapes suivantes : # Si le fichier n'est pas dans le manifeste validé, on le supprime
1. Configurez vos alertes : if ! grep -qW "$rel_path" "${TMP_DIR}/manifest-valid.txt"; then
cp ${CONF_DIR}/alert-engine.conf.php ${CONF_DIR}/alert-engine.local.conf.php warn "Suppression : $rel_path (absent du dépôt)"
nano ${CONF_DIR}/alert-engine.local.conf.php rm -f "$local_file"
fi
2. Initialisez la configuration globale : done
cp ${CONF_DIR}/monitoring.conf.php ${CONF_DIR}/monitoring.local.conf.php
3. Lancez un audit des configurations :
php ${BASE_DIR}/bin/monitoring-update-config.php
4. Planifiez les tâches (cron) :
*/5 * * * * php ${BASE_DIR}/bin/alert-engine.php
10 3 * * * php ${BASE_DIR}/bin/monitoring-update.php
EOF
} }
# --- Main --- # --- Main ---
@@ -142,16 +130,29 @@ main() {
fetch_manifest fetch_manifest
if ! validate_manifest > "${TMP_DIR}/manifest-valid.txt"; then if ! validate_manifest > "${TMP_DIR}/manifest-valid.txt"; then
echo "ERREUR: Le manifeste est invalide ou corrompu." >&2 err "Le manifeste est invalide ou corrompu."
exit 1 exit 1
fi fi
install_from_manifest 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"
purge_obsolete_files
# Nettoyage
rm -rf "${TMP_DIR}" rm -rf "${TMP_DIR}"
echo "--------------------------------------------------"
ok "Opération terminée avec succès."
show_next_steps # Rappel final
if [ ! -f "${CONF_DIR}/monitoring.local.conf.php" ]; then
warn "Pensez à créer votre fichier ${CONF_DIR}/monitoring.local.conf.php"
fi
} }
main "$@" main "$@"

View File

@@ -1,268 +1,58 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
/** /**
* Moteur de mise à jour des programmes * Moteur de mise à jour (Wrapper pour le script Bash)
* Copyright (C) 2026 Cédric Abonnel
* License: GNU Affero General Public License v3
*/ */
require_once __DIR__ . '/../lib/monitoring-lib.php'; require_once __DIR__ . '/../lib/monitoring-lib.php';
// --- Chargement de la configuration spécifique --- // Sécurité : Un seul update à la fois
foreach (["/opt/monitoring/conf/autoupdate.conf.php", "/opt/monitoring/conf/autoupdate.local.conf.php"] as $conf) {
if (file_exists($conf)) {
$extra_conf = include $conf;
if (is_array($extra_conf)) {
$CONFIG = array_replace_recursive($CONFIG, $extra_conf);
}
}
}
// Variables par défaut
$UPDATE_ENABLED = $CONFIG['UPDATE_ENABLED'] ?? true;
$UPDATE_TMP_DIR = $CONFIG['UPDATE_TMP_DIR'] ?? '/tmp/monitoring-update';
$UPDATE_TIMEOUT_CONNECT = $CONFIG['UPDATE_TIMEOUT_CONNECT'] ?? 3;
$UPDATE_TIMEOUT_TOTAL = $CONFIG['UPDATE_TIMEOUT_TOTAL'] ?? 15;
$UPDATE_MANIFEST_URL = $CONFIG['UPDATE_MANIFEST_URL'] ?? '';
$UPDATE_BASE_URL = $CONFIG['UPDATE_BASE_URL'] ?? '';
$UPDATE_ALLOW_DELETE = $CONFIG['UPDATE_ALLOW_DELETE'] ?? false;
// Sécurité
lock_or_exit("monitoring-update"); lock_or_exit("monitoring-update");
if (!$UPDATE_ENABLED) { echo "\e[1m--- Début de la mise à jour système ---\e[0m\n";
log_notice("update_disabled", "Mise à jour désactivée par configuration");
exit(0); $install_script = __DIR__ . '/install-monitoring.sh';
if (!file_exists($install_script)) {
log_error("update_script_missing", "Script d'installation introuvable", ["path" => $install_script]);
echo "\e[31m[ERR]\e[0m Script d'installation introuvable : $install_script\n";
exit(1);
} }
if (!is_dir($UPDATE_TMP_DIR)) { // On ouvre un pipe pour lire la sortie du Bash en temps réel
mkdir($UPDATE_TMP_DIR, 0755, true); $command = "bash " . escapeshellarg($install_script) . " --auto 2>&1";
} $handle = popen($command, 'r');
/** if ($handle) {
* Télécharge et valide le manifeste while (!feof($handle)) {
*/ $line = fgets($handle);
function fetch_manifest() { if ($line) {
global $UPDATE_MANIFEST_URL, $UPDATE_TIMEOUT_TOTAL; // 1. On affiche à l'écran en temps réel
echo $line;
$content = @file_get_contents($UPDATE_MANIFEST_URL, false, stream_context_create([ // 2. On loggue les erreurs importantes dans le JSONL
'http' => ['timeout' => $UPDATE_TIMEOUT_TOTAL] if (strpos($line, '[ERR]') !== false) {
])); log_error("update_process_error", trim($line));
if ($content === false) {
log_error("manifest_download_failed", "Impossible de télécharger le manifeste", ["url=$UPDATE_MANIFEST_URL"]);
return false;
}
$lines = explode("\n", trim($content));
$manifest_data = [];
foreach ($lines as $line) {
if (empty(trim($line))) continue;
$parts = preg_split('/\s+/', trim($line));
if (count($parts) !== 3) continue;
list($hash, $mode, $path) = $parts;
// Validation stricte (Regex identique au Bash)
if (preg_match('/^[0-9a-f]{64}$/i', $hash) &&
preg_match('/^(644|755)$/', $mode) &&
preg_match('/^(bin|lib|conf)\/[A-Za-z0-9._\/-]+$/', $path) &&
strpos($path, '..') === false) {
$manifest_data[] = ['hash' => strtolower($hash), 'mode' => $mode, 'path' => $path];
}
}
if (empty($manifest_data)) {
log_error("manifest_invalid", "Le manifeste distant est vide ou invalide");
return false;
}
log_info("manifest_downloaded", "Manifeste téléchargé", ["url=$UPDATE_MANIFEST_URL"]);
return $manifest_data;
}
/**
* Mise à jour d'un fichier unique
*/
function update_one_file($expected_hash, $mode, $rel_path) {
global $MONITORING_BASE_DIR, $UPDATE_BASE_URL, $UPDATE_TMP_DIR, $UPDATE_TIMEOUT_TOTAL;
$local_file = $MONITORING_BASE_DIR . '/' . $rel_path;
$remote_url = rtrim($UPDATE_BASE_URL, '/') . '/' . $rel_path;
$tmp_file = $UPDATE_TMP_DIR . '/' . basename($rel_path) . '.' . bin2hex(random_bytes(4));
$local_hash = file_exists($local_file) ? hash_file('sha256', $local_file) : null;
if ($local_hash === $expected_hash) {
log_debug("update_not_needed", "Fichier déjà à jour", ["file=$rel_path"]);
return true;
}
// Téléchargement via cURL pour gérer les timeouts proprement
$ch = curl_init($remote_url);
$fp = fopen($tmp_file, 'w');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_TIMEOUT, $UPDATE_TIMEOUT_TOTAL);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$success = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
fclose($fp);
if (!$success || $http_code !== 200) {
log_error("update_download_failed", "Téléchargement impossible", ["file=$rel_path", "url=$remote_url"]);
@unlink($tmp_file);
return false;
}
$downloaded_hash = hash_file('sha256', $tmp_file);
if ($downloaded_hash !== $expected_hash) {
log_error("update_hash_mismatch", "Hash téléchargé invalide", ["file=$rel_path", "expected=$expected_hash", "got=$downloaded_hash"]);
@unlink($tmp_file);
return false;
}
// Installation
ensure_parent_dir($local_file);
chmod($tmp_file, ($mode == '755' ? 0755 : 0644));
if (rename($tmp_file, $local_file)) {
if (!$local_hash) {
log_notice("file_created", "Fichier créé", ["file=$rel_path", "mode=$mode"]);
} else {
log_notice("update_applied", "Mise à jour appliquée", ["file=$rel_path", "new_hash=$expected_hash"]);
}
return true;
}
return false;
}
/**
* Vérifie et ajoute les tâches cron si elles sont absentes
*/
function ensure_crontab_entries() {
global $MONITORING_BASE_DIR;
// Définition des tâches souhaitées (Format: "cron_schedule command")
$required_jobs = [
"*/5 * * * * php {$MONITORING_BASE_DIR}/bin/check_disk.php > /dev/null 2>&1",
"*/5 * * * * php {$MONITORING_BASE_DIR}/bin/check_ram.php > /dev/null 2>&1",
"10 3 * * * php {$MONITORING_BASE_DIR}/bin/monitoring-update.php > /dev/null 2>&1",
"* * * * * php {$MONITORING_BASE_DIR}/bin/alert-engine.php > /dev/null 2>&1"
];
// Récupération du crontab actuel de root
$current_cron = shell_exec("crontab -l 2>/dev/null") ?: "";
$lines = explode("\n", trim($current_cron));
$updated = false;
foreach ($required_jobs as $job) {
// On extrait la commande sans les arguments de temps pour la recherche
// On cherche si le chemin du script est déjà présent
$script_path = explode(' ', $job)[5];
$found = false;
foreach ($lines as $line) {
if (strpos($line, $script_path) !== false) {
$found = true;
break;
}
}
if (!$found) {
log_notice("cron_added", "Ajout d'une tâche au crontab", ["job" => $job]);
$lines[] = $job;
$updated = true;
}
}
if ($updated) {
// Réécriture du crontab
$tmp_cron = tempnam(sys_get_temp_dir(), 'cron');
file_put_contents($tmp_cron, implode("\n", $lines) . "\n");
exec("crontab " . escapeshellarg($tmp_cron));
unlink($tmp_cron);
}
}
/**
* Suppression des fichiers obsolètes
*/
function delete_extra_files($manifest_paths) {
global $UPDATE_ALLOW_DELETE, $MONITORING_BASE_DIR;
if (!$UPDATE_ALLOW_DELETE) return;
$dirs = ['bin', 'lib', 'conf'];
foreach ($dirs as $dir) {
$full_dir = $MONITORING_BASE_DIR . '/' . $dir;
if (!is_dir($full_dir)) continue;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($full_dir));
foreach ($iterator as $file) {
if ($file->isDir()) continue;
$rel_path = str_replace($MONITORING_BASE_DIR . '/', '', $file->getPathname());
// Protection des .local.conf
if (str_ends_with($rel_path, '.local.conf') || str_ends_with($rel_path, '.local.php')) {
continue;
}
if (!in_array($rel_path, $manifest_paths)) {
if (@unlink($file->getPathname())) {
log_notice("file_deleted", "Fichier obsolète supprimé", ["file=$rel_path"]);
} }
} }
} }
} $exit_code = pclose($handle);
}
/**
* Lancement du script de synchronisation local
*/
function run_local_conf_sync() {
global $MONITORING_BASE_DIR;
$sync_script = $MONITORING_BASE_DIR . '/bin/monitoring-update-config.php'; // On cherche la version PHP
if (file_exists($sync_script)) {
log_info("local_conf_sync_start", "Synchronisation des configs locales");
passthru("php " . escapeshellarg($sync_script), $return_var);
if ($return_var !== 0) {
log_warning("local_conf_sync_failed", "Échec de synchronisation");
}
}
}
// --- Exécution principale ---
$manifest = fetch_manifest();
if (!$manifest) exit(2);
$total = count($manifest);
$updated = 0;
$failed = 0;
$remote_paths = [];
foreach ($manifest as $item) {
$remote_paths[] = $item['path'];
if (update_one_file($item['hash'], $item['mode'], $item['path'])) {
$updated++;
} else {
$failed++;
}
}
delete_extra_files($remote_paths);
run_local_conf_sync();
ensure_crontab_entries();
if ($failed > 0) {
log_warning("update_finished_with_errors", "Mise à jour terminée avec erreurs", ["total=$total", "failed=$failed"]);
} else { } else {
log_info("update_finished", "Mise à jour terminée", ["total=$total", "checked=$updated"]); $exit_code = 1;
}
if ($exit_code === 0) {
// Tâches de finalisation PHP
echo "\e[1m--- Finalisation des configurations ---\e[0m\n";
run_local_conf_sync(); // Audit des fichiers .local
ensure_crontab_entries(); // Vérification du cron
log_info("update_finished", "Mise à jour réussie");
echo "\e[32m[OK]\e[0m Mise à jour terminée avec succès.\n";
} else {
log_error("update_failed", "Le script de synchronisation a échoué", ["code" => $exit_code]);
echo "\e[31m[ERR]\e[0m Échec de la mise à jour (Code: $exit_code).\n";
exit($exit_code);
} }
exit_with_status(); exit_with_status();