mise à jour de la logique d'install et update
This commit is contained in:
@@ -17,45 +17,42 @@ MANIFEST_URL="${UPDATE_BASE_URL}/manifest.txt"
|
||||
|
||||
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() {
|
||||
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
|
||||
fi
|
||||
}
|
||||
|
||||
install_deps() {
|
||||
if [ "${INSTALL_DEPS}" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "--- Installation des dépendances ---"
|
||||
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
|
||||
apt-get install -y curl coreutils findutils grep sed gawk util-linux ca-certificates
|
||||
# Ajout des modules PHP nécessaires pour vos scripts (curl pour ntfy)
|
||||
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."
|
||||
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 > /dev/null
|
||||
ok "Dépendances installées."
|
||||
fi
|
||||
}
|
||||
|
||||
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}"
|
||||
chmod 755 "${BASE_DIR}" "${CONF_DIR}" "${LOG_DIR}" "${STATE_DIR}" "${LOCK_DIR}"
|
||||
}
|
||||
|
||||
fetch_manifest() {
|
||||
echo "--- Récupération du manifeste ---"
|
||||
info "Téléchargement du manifeste distant..."
|
||||
curl -fsS "${MANIFEST_URL}" -o "${TMP_DIR}/manifest.txt"
|
||||
}
|
||||
|
||||
validate_manifest() {
|
||||
# Validation du format : Hash Mode Chemin
|
||||
# Exemple : a1b2... 755 bin/script.php
|
||||
awk '
|
||||
NF == 3 &&
|
||||
$1 ~ /^[0-9a-fA-F]{64}$/ &&
|
||||
@@ -65,35 +62,36 @@ validate_manifest() {
|
||||
' "${TMP_DIR}/manifest.txt"
|
||||
}
|
||||
|
||||
download_one() {
|
||||
download_and_install() {
|
||||
local expected_hash="$1"
|
||||
local mode="$2"
|
||||
local rel_path="$3"
|
||||
|
||||
local url="${UPDATE_BASE_URL}/${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 [[ "$rel_path" == conf/* ]] && [ -f "$dst" ]; then
|
||||
echo "Skip: $rel_path (existe déjà)"
|
||||
return 0
|
||||
if [ -f "$dst" ]; then
|
||||
local current_hash
|
||||
current_hash=$(sha256sum "$dst" | awk '{print $1}')
|
||||
if [ "$current_hash" == "$expected_hash" ]; then
|
||||
return 0 # Déjà à jour
|
||||
fi
|
||||
info "Mise à jour : $rel_path"
|
||||
else
|
||||
info "Installation : $rel_path"
|
||||
fi
|
||||
|
||||
echo "Téléchargement: $rel_path"
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp "${TMP_DIR}/file.XXXXXX")"
|
||||
|
||||
if ! curl -fsS "$url" -o "$tmp_file"; then
|
||||
echo "ERREUR: Échec du téléchargement de ${url}" >&2
|
||||
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}')"
|
||||
|
||||
got_hash=$(sha256sum "$tmp_file" | awk '{print $1}')
|
||||
if [ "$got_hash" != "$expected_hash" ]; then
|
||||
echo "ERREUR: Hash invalide pour ${rel_path}" >&2
|
||||
err "Hash invalide pour $rel_path"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
@@ -103,34 +101,24 @@ download_one() {
|
||||
chmod "$mode" "$dst"
|
||||
}
|
||||
|
||||
install_from_manifest() {
|
||||
echo "--- Installation des fichiers ---"
|
||||
while read -r hash mode rel_path; do
|
||||
[ -n "${hash:-}" ] || continue
|
||||
download_one "$hash" "$mode" "$rel_path"
|
||||
done < "${TMP_DIR}/manifest-valid.txt"
|
||||
}
|
||||
purge_obsolete_files() {
|
||||
info "Analyse des fichiers obsolètes (Synchronisation avec Git)..."
|
||||
# On scanne bin, lib et conf
|
||||
find "${BASE_DIR}/bin" "${BASE_DIR}/lib" "${BASE_DIR}/conf" -type f | while read -r local_file; do
|
||||
|
||||
show_next_steps() {
|
||||
cat <<EOF
|
||||
local rel_path="${local_file#$BASE_DIR/}"
|
||||
|
||||
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 :
|
||||
1. Configurez vos alertes :
|
||||
cp ${CONF_DIR}/alert-engine.conf.php ${CONF_DIR}/alert-engine.local.conf.php
|
||||
nano ${CONF_DIR}/alert-engine.local.conf.php
|
||||
|
||||
2. Initialisez la configuration globale :
|
||||
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
|
||||
# Si le fichier n'est pas dans le manifeste validé, on le supprime
|
||||
if ! grep -qW "$rel_path" "${TMP_DIR}/manifest-valid.txt"; then
|
||||
warn "Suppression : $rel_path (absent du dépôt)"
|
||||
rm -f "$local_file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
@@ -142,16 +130,29 @@ main() {
|
||||
fetch_manifest
|
||||
|
||||
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
|
||||
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}"
|
||||
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 "$@"
|
||||
@@ -1,268 +1,58 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Moteur de mise à jour des programmes
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
* License: GNU Affero General Public License v3
|
||||
* Moteur de mise à jour (Wrapper pour le script Bash)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||
|
||||
// --- Chargement de la configuration spécifique ---
|
||||
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é
|
||||
// Sécurité : Un seul update à la fois
|
||||
lock_or_exit("monitoring-update");
|
||||
|
||||
if (!$UPDATE_ENABLED) {
|
||||
log_notice("update_disabled", "Mise à jour désactivée par configuration");
|
||||
exit(0);
|
||||
echo "\e[1m--- Début de la mise à jour système ---\e[0m\n";
|
||||
|
||||
$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)) {
|
||||
mkdir($UPDATE_TMP_DIR, 0755, true);
|
||||
}
|
||||
// On ouvre un pipe pour lire la sortie du Bash en temps réel
|
||||
$command = "bash " . escapeshellarg($install_script) . " --auto 2>&1";
|
||||
$handle = popen($command, 'r');
|
||||
|
||||
/**
|
||||
* Télécharge et valide le manifeste
|
||||
*/
|
||||
function fetch_manifest() {
|
||||
global $UPDATE_MANIFEST_URL, $UPDATE_TIMEOUT_TOTAL;
|
||||
if ($handle) {
|
||||
while (!feof($handle)) {
|
||||
$line = fgets($handle);
|
||||
if ($line) {
|
||||
// 1. On affiche à l'écran en temps réel
|
||||
echo $line;
|
||||
|
||||
$content = @file_get_contents($UPDATE_MANIFEST_URL, false, stream_context_create([
|
||||
'http' => ['timeout' => $UPDATE_TIMEOUT_TOTAL]
|
||||
]));
|
||||
|
||||
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"]);
|
||||
}
|
||||
// 2. On loggue les erreurs importantes dans le JSONL
|
||||
if (strpos($line, '[ERR]') !== false) {
|
||||
log_error("update_process_error", trim($line));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"]);
|
||||
$exit_code = pclose($handle);
|
||||
} 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();
|
||||
Reference in New Issue
Block a user