268 lines
8.6 KiB
PHP
Executable File
268 lines
8.6 KiB
PHP
Executable File
#!/usr/bin/env php
|
|
<?php
|
|
/**
|
|
* Moteur de mise à jour des programmes
|
|
* Copyright (C) 2026 Cédric Abonnel
|
|
* License: GNU Affero General Public License v3
|
|
*/
|
|
|
|
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é
|
|
lock_or_exit("monitoring-update");
|
|
|
|
if (!$UPDATE_ENABLED) {
|
|
log_notice("update_disabled", "Mise à jour désactivée par configuration");
|
|
exit(0);
|
|
}
|
|
|
|
if (!is_dir($UPDATE_TMP_DIR)) {
|
|
mkdir($UPDATE_TMP_DIR, 0755, true);
|
|
}
|
|
|
|
/**
|
|
* Télécharge et valide le manifeste
|
|
*/
|
|
function fetch_manifest() {
|
|
global $UPDATE_MANIFEST_URL, $UPDATE_TIMEOUT_TOTAL;
|
|
|
|
$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"]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
log_info("update_finished", "Mise à jour terminée", ["total=$total", "checked=$updated"]);
|
|
}
|
|
|
|
exit_with_status(); |