#!/usr/bin/env php ['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; } /** * 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.sh'; // 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(); 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();