agrégat (= check=all)
* check.php?check=swap&key=... -> une sonde précise
* Sondes : all | disk | load | mem | swap | procs | users | file-age | uptime | http | tcp
* Sortie JSON : {"status":"ok|warn|crit|unknown","value":...,"message":"...","at":"..."}
*
* Mises à jour : pas d'auto-update. Activez la newsletter « mises à jour outils CLI »
* dans votre compte En Service ; quand une nouvelle version sort, retéléchargez ce fichier.
*/
/* ===== Configuration (modifiée par ?setup ; préservée par ?update) ===== */
const ENSERVICE_KEY = 'COLLEZ_VOTRE_CLE_PARTAGEE_ICI';
const ENSERVICE_ALLOW_IPS = []; // optionnel : ['51.158.0.10','51.158.0.11']
const ENSERVICE_PROBE_VERSION = '2.0.0';
/* ===== Bibliothèque ===== */
function es_qf($k, $def) { return isset($_GET[$k]) ? (float)$_GET[$k] : $def; }
function es_qs($k, $def) { return isset($_GET[$k]) ? (string)$_GET[$k] : $def; }
/* Seuils façon Nagios. $higher_is_bad : true => alerte quand v >= seuil. */
function es_thr($v, $warn, $crit, $higher_is_bad = true) {
if ($higher_is_bad) return $v >= $crit ? 'crit' : ($v >= $warn ? 'warn' : 'ok');
return $v <= $crit ? 'crit' : ($v <= $warn ? 'warn' : 'ok');
}
function es_meminfo() {
$m = [];
foreach (@file('/proc/meminfo') ?: [] as $line) {
if (preg_match('/^(\w+):\s+(\d+)/', $line, $x)) $m[$x[1]] = (int)$x[2] * 1024; // kB -> octets
}
return $m;
}
function es_human_bytes($b) {
$u = ['o', 'Ko', 'Mo', 'Go', 'To']; $i = 0;
while ($b >= 1024 && $i < 4) { $b /= 1024; $i++; }
return round($b, 1) . ' ' . $u[$i];
}
function es_out($status, $value, $message, array $extra = []) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(array_merge([
'status' => $status,
'value' => $value,
'message' => $message,
'at' => date('c'),
], $extra), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
function es_guard() {
$key = isset($_GET['key']) ? $_GET['key'] : (isset($_SERVER['HTTP_X_ENSERVICE_KEY']) ? $_SERVER['HTTP_X_ENSERVICE_KEY'] : '');
$ok = ENSERVICE_KEY !== 'COLLEZ_VOTRE_CLE_PARTAGEE_ICI' && hash_equals(ENSERVICE_KEY, (string)$key);
if ($ok && ENSERVICE_ALLOW_IPS) {
$ok = in_array(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '', ENSERVICE_ALLOW_IPS, true);
}
if (!$ok) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['status' => 'forbidden']);
exit;
}
}
/* Réécrit les lignes de config (clé + IP) dans un contenu PHP, sans toucher au reste. */
function es_inject_config($content, $key, array $ips) {
$content = preg_replace("/const ENSERVICE_KEY\\s*=\\s*'(?:[^'\\\\]|\\\\.)*';/",
'const ENSERVICE_KEY = ' . var_export($key, true) . ';', $content, 1);
$q = [];
foreach ($ips as $ip) $q[] = "'" . addslashes($ip) . "'";
$ipsLit = $q ? '[' . implode(', ', $q) . ']' : '[]';
$content = preg_replace("/const ENSERVICE_ALLOW_IPS\\s*=\\s*\\[[^\\]]*\\];/",
'const ENSERVICE_ALLOW_IPS = ' . $ipsLit . ';', $content, 1);
return $content;
}
/* ===== Installeur web : check.php?setup ===== */
function es_setup() {
$placeholder = 'COLLEZ_VOTRE_CLE_PARTAGEE_ICI';
$configured = ENSERVICE_KEY !== $placeholder;
$writable = is_writable(__FILE__);
$msg = ''; $ok = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$new = trim(isset($_POST['key']) ? $_POST['key'] : '');
$cur = trim(isset($_POST['current']) ? $_POST['current'] : '');
if ($configured && !hash_equals(ENSERVICE_KEY, $cur)) {
$msg = 'Clé actuelle incorrecte.';
} elseif (strlen($new) < 16) {
$msg = 'Clé invalide (collez la clé depuis votre tableau de bord En Service).';
} elseif (!$writable) {
$msg = "Ce fichier n'est pas inscriptible. Éditez la ligne ENSERVICE_KEY à la main, ou : chmod u+w check.php.";
} else {
$content = es_inject_config(file_get_contents(__FILE__), $new, ENSERVICE_ALLOW_IPS);
$tmp = __FILE__ . '.tmp';
if (@file_put_contents($tmp, $content) !== false && @rename($tmp, __FILE__)) {
$ok = true; $configured = true; $msg = 'Configuration enregistrée. La sonde est prête.';
} else { @unlink($tmp); $msg = "Échec d'écriture du fichier."; }
}
}
header('Content-Type: text/html; charset=utf-8');
$keyInput = isset($_POST['key']) ? $_POST['key'] : '';
?>
Sonde En Service - configuration
Sonde En Service
Surveillance de la santé serveur. Collez votre clé partagée (tableau de bord En Service).
= htmlspecialchars($msg) ?>
Fichier non inscriptible : éditez ENSERVICE_KEY dans check.php, ou chmod u+w.
Test : check.php?check=all doit renvoyer du JSON.
(int)$free, 'total_bytes' => (int)$total]);
}
case 'load': {
$warn = es_qf('warn', 1.0); $crit = es_qf('crit', 2.0);
$la = function_exists('sys_getloadavg') ? sys_getloadavg() : [0, 0, 0];
$cores = (int)(@shell_exec('nproc 2>/dev/null') ?: 1) ?: 1;
$per = round($la[0] / $cores, 2);
es_out(es_thr($per, $warn, $crit), $per, "Charge " . round($la[0], 2) . " sur $cores coeur(s) = $per/coeur",
['load1' => round($la[0], 2), 'load5' => round($la[1], 2), 'load15' => round($la[2], 2), 'cores' => $cores]);
}
case 'mem': {
$warn = es_qf('warn', 85); $crit = es_qf('crit', 95); $m = es_meminfo();
$total = isset($m['MemTotal']) ? $m['MemTotal'] : 0;
$avail = isset($m['MemAvailable']) ? $m['MemAvailable'] : (isset($m['MemFree']) ? $m['MemFree'] : 0);
if (!$total) es_out('unknown', null, '/proc/meminfo illisible');
$used = round(($total - $avail) / $total * 100, 1);
es_out(es_thr($used, $warn, $crit), $used,
"Memoire : $used% utilisee (" . es_human_bytes((int)$avail) . " dispo / " . es_human_bytes((int)$total) . ")",
['available_bytes' => (int)$avail, 'total_bytes' => (int)$total]);
}
case 'swap': {
$warn = es_qf('warn', 40); $crit = es_qf('crit', 70); $m = es_meminfo();
$total = isset($m['SwapTotal']) ? $m['SwapTotal'] : 0; $free = isset($m['SwapFree']) ? $m['SwapFree'] : 0;
if (!$total) es_out('ok', 0, 'Pas de swap configure', ['total_bytes' => 0]);
$used = round(($total - $free) / $total * 100, 1);
es_out(es_thr($used, $warn, $crit), $used, "Swap : $used% utilise",
['free_bytes' => (int)$free, 'total_bytes' => (int)$total]);
}
case 'procs': {
$warn = es_qf('warn', 400); $crit = es_qf('crit', 600); $match = es_qs('match', '');
$out = @shell_exec('ps -e -o comm= 2>/dev/null') ?: '';
$lines = array_filter(explode("\n", trim($out)));
if ($match !== '') $lines = array_filter($lines, function ($l) use ($match) { return stripos($l, $match) !== false; });
$n = count($lines);
if ($match !== '' && $n === 0) es_out('crit', 0, "Aucun processus « $match »");
$status = $match !== '' ? 'ok' : es_thr($n, $warn, $crit);
es_out($status, $n, $match !== '' ? "$n processus « $match »" : "$n processus");
}
case 'users': {
$warn = es_qf('warn', 5); $crit = es_qf('crit', 10);
$out = trim((string)@shell_exec('who 2>/dev/null'));
$n = $out === '' ? 0 : count(array_filter(explode("\n", $out)));
es_out(es_thr($n, $warn, $crit), $n, "$n utilisateur(s) connecte(s)");
}
case 'file-age': {
$file = es_qs('file', ''); $warn = es_qf('warn', 90000); $crit = es_qf('crit', 172800);
if ($file === '' || !@is_file($file)) es_out('crit', null, "Fichier introuvable : $file");
$age = time() - (int)@filemtime($file);
es_out(es_thr($age, $warn, $crit), $age, "Modifie il y a " . round($age / 3600, 1) . " h", ['mtime' => (int)@filemtime($file)]);
}
case 'uptime': {
$warn = es_qf('warn', 600); $crit = es_qf('crit', 120); // secondes : trop bas = reboot recent
$up = (float)(@file_get_contents('/proc/uptime') ?: 0);
es_out(es_thr($up, $warn, $crit, false), (int)$up, "Uptime : " . round($up / 86400, 1) . " j");
}
case 'http': {
$url = es_qs('url', ''); $expect = es_qs('expect', ''); $warn = (int)es_qf('warn_ms', 0);
if ($url === '' || !preg_match('#^https?://#i', $url)) es_out('unknown', null, 'URL manquante/invalide');
$t0 = microtime(true);
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false]);
$body = curl_exec($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
$lat = (int)round((microtime(true) - $t0) * 1000);
if ($code === 0 || $code >= 500) es_out('crit', $lat, "Injoignable / 5xx ($code)");
if ($code >= 400) es_out('crit', $lat, "HTTP $code");
if ($expect !== '' && stripos((string)$body, $expect) === false) es_out('crit', $lat, "Mot-cle « $expect » absent");
if ($warn > 0 && $lat > $warn) es_out('warn', $lat, "Lent ($lat ms)");
es_out('ok', $lat, "HTTP $code", ['latency_ms' => $lat]);
}
case 'tcp': {
$host = es_qs('host', '127.0.0.1'); $port = (int)es_qf('port', 0); $to = (int)es_qf('timeout', 5);
if (!$port) es_out('unknown', null, 'Port manquant (?port=)');
$t0 = microtime(true);
$fp = @fsockopen($host, $port, $errno, $errstr, $to);
$lat = (int)round((microtime(true) - $t0) * 1000);
if (!$fp) es_out('crit', null, "$host:$port injoignable : " . ($errstr ?: 'timeout'));
fclose($fp);
es_out('ok', $lat, "$host:$port ouvert", ['latency_ms' => $lat]);
}
case 'all':
default: {
$checks = [];
$t = @disk_total_space('/'); $f = @disk_free_space('/');
$checks['disk'] = $t ? es_thr(round(($t - $f) / $t * 100, 1), 80, 90) : 'unknown';
$la = function_exists('sys_getloadavg') ? sys_getloadavg() : [0, 0, 0];
$cores = (int)(@shell_exec('nproc 2>/dev/null') ?: 1) ?: 1;
$checks['load'] = es_thr($la[0] / $cores, 1.0, 2.0);
$m = es_meminfo();
if (isset($m['MemTotal']) && $m['MemTotal'] > 0) {
$avail = isset($m['MemAvailable']) ? $m['MemAvailable'] : (isset($m['MemFree']) ? $m['MemFree'] : 0);
$checks['mem'] = es_thr(round(($m['MemTotal'] - $avail) / $m['MemTotal'] * 100, 1), 85, 95);
}
if (isset($m['SwapTotal']) && $m['SwapTotal'] > 0) {
$checks['swap'] = es_thr(round(($m['SwapTotal'] - (isset($m['SwapFree']) ? $m['SwapFree'] : 0)) / $m['SwapTotal'] * 100, 1), 40, 70);
}
$rank = ['ok' => 0, 'warn' => 1, 'crit' => 2, 'unknown' => 1]; $worst = 'ok';
foreach ($checks as $s) if ($rank[$s] > $rank[$worst]) $worst = $s;
es_out($worst, null,
$worst === 'ok' ? 'Tous les indicateurs serveur sont au vert' : 'Indicateur(s) serveur en anomalie',
['checks' => $checks, 'version' => ENSERVICE_PROBE_VERSION]);
}
}