Compare commits
31 Commits
9e5d7456b6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e6263c1ae2 | |||
| 4e7119297c | |||
| 5e3b5ee741 | |||
| 95a6bb10b1 | |||
| 42626f2204 | |||
| ac6b80cb69 | |||
| 88b0dd4e77 | |||
| 365c8e543f | |||
| 61ebf5a92f | |||
| 12a7495447 | |||
| 7de4c86feb | |||
| d48cb923eb | |||
| 2540a96a1e | |||
| fba9bc89e2 | |||
| 312ba59343 | |||
| 78e2f5ea1e | |||
| 790052dfe5 | |||
| 8074151300 | |||
| b2c083eb8d | |||
| 8b52a02b55 | |||
| 0aebf47f6b | |||
| 6541cefea0 | |||
| 3b05390ec4 | |||
| 35f3c6f5f7 | |||
| 763bc0ba48 | |||
| c9d6fd48ed | |||
| 3fd1c70bd7 | |||
| c280e4d5ac | |||
| adfe0ed282 | |||
| ae0c8f95cb | |||
| d31e193954 |
@@ -1,20 +1,20 @@
|
||||
# Lightweight Bash Monitoring System by Cédrix
|
||||
# 🛡️ PHP Monitoring System by Cédrix
|
||||
|
||||
Ce projet est une solution de monitoring légère, modulaire et auto-hébergée pour serveurs Linux. Elle permet de surveiller l'état des ressources (disque, RAM, etc.), de centraliser les logs au format JSON et d'envoyer des alertes via **ntfy** ou **email** avec un système de déduplication intelligent.
|
||||
Ce projet est une solution de monitoring **légère**, **modulaire** et **auto-hébergée** pour serveurs Linux. Elle combine la simplicité de sondes Bash avec la puissance d'un moteur de traitement PHP pour centraliser les logs et envoyer des alertes intelligentes via **ntfy** ou **email**.
|
||||
|
||||
## 🚀 Ce que fait ce système
|
||||
## 🚀 Fonctionnalités clés
|
||||
|
||||
* **Sondes modulaires :** Scripts indépendants pour vérifier les ressources (ex: `check_disk.sh`).
|
||||
* **Logs JSONL :** Centralisation de tous les événements dans `/var/log/monitoring/events.jsonl` pour une analyse facile.
|
||||
* **Moteur d'alerte :** Un moteur (`alert-engine.sh`) qui lit les logs en continu, gère les seuils de priorité et évite le spam grâce à une fenêtre de déduplication.
|
||||
* **Auto-update :** Capacité de se mettre à jour automatiquement depuis un dépôt Git via un manifeste.
|
||||
* **Robuste :** Gestion des verrous (locks) pour éviter que deux instances d'un même script ne tournent en même temps.
|
||||
* **Moteur PHP & Sondes Hybrides :** Traitement performant des alertes en PHP, tout en gardant des sondes système simples (Bash ou PHP).
|
||||
* **Alertes Intelligentes :** Envoi via **ntfy** (avec tags et priorités) ou **email**, incluant un système de **déduplication** pour éviter le spam.
|
||||
* **Logs JSONL :** Centralisation au format standard `JSON Lines` dans `/var/log/monitoring/events.jsonl` pour une exploitation facile.
|
||||
* **Configuration en cascade :** Système de fichiers `.local.conf.php` pour protéger vos réglages lors des mises à jour.
|
||||
* **Auto-update & Audit :** Mise à jour automatique via manifeste et script d'audit pour détecter les nouvelles options de configuration manquantes.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
L'installation se fait via le script d'installation distant qui configure l'arborescence `/opt/monitoring`.
|
||||
L'installation se fait via un script Bash qui configure l'environnement et installe les dépendances nécessaires (PHP, curl).
|
||||
|
||||
```bash
|
||||
# Passer en root
|
||||
@@ -25,70 +25,65 @@ curl -sSL https://git.abonnel.fr/cedricAbonnel/scripts-bash/raw/branch/main/serv
|
||||
|
||||
```
|
||||
|
||||
### Arborescence créée :
|
||||
### Structure du système :
|
||||
|
||||
* `/opt/monitoring/bin/` : Scripts exécutables (sondes, moteur, updateur).
|
||||
* `/opt/monitoring/lib/` : Bibliothèque commune (`monitoring-lib.sh`).
|
||||
* `/opt/monitoring/conf/` : Fichiers de configuration.
|
||||
* `/var/log/monitoring/` : Logs des événements.
|
||||
* `/var/lib/monitoring/` : États (offsets de lecture, déduplication).
|
||||
* `/opt/monitoring/bin/` : Exécutables (sondes, moteur `alert-engine.php`, updater).
|
||||
* `/opt/monitoring/lib/` : Bibliothèque partagée (`monitoring-lib.php`).
|
||||
* `/opt/monitoring/conf/` : Fichiers de configuration PHP.
|
||||
* `/var/log/monitoring/` : Journal des événements (`events.jsonl`).
|
||||
* `/var/lib/monitoring/` : Index (offsets de lecture, états de déduplication).
|
||||
|
||||
---
|
||||
|
||||
## Configuration (Que modifier ?)
|
||||
## ⚙️ Configuration
|
||||
|
||||
Après l'installation, vous devez configurer vos accès pour recevoir les alertes.
|
||||
Le système utilise des fichiers PHP pour la configuration afin de permettre une logique dynamique.
|
||||
|
||||
### 1. Alertes (ntfy / Mail)
|
||||
|
||||
Éditez le fichier local (prioritaire sur la config par défaut) :
|
||||
`nano /opt/monitoring/conf/alert-engine.local.conf`
|
||||
Ne modifiez pas les fichiers `.conf.php` (risques d'écrasement). Créez vos fichiers locaux :
|
||||
`cp /opt/monitoring/conf/alert-engine.conf.php /opt/monitoring/conf/alert-engine.local.conf.php`
|
||||
|
||||
Modifiez les variables suivantes :
|
||||
Éditez le fichier local pour renseigner :
|
||||
|
||||
* `NTFY_TOKEN` : Votre jeton d'accès ntfy.
|
||||
* `NTFY_TOPIC` : Le nom de votre topic.
|
||||
* `DEST` : L'adresse email de réception.
|
||||
* `NTFY_TOKEN` & `NTFY_TOPIC`.
|
||||
* `DEST` (votre email de réception).
|
||||
|
||||
### 2. Seuils des sondes
|
||||
### 2. Audit de configuration
|
||||
|
||||
Vous pouvez modifier les variables `WARNING` et `CRITICAL` directement dans les scripts du dossier `bin/` ou, mieux, les définir dans `/opt/monitoring/conf/monitoring.local.conf`.
|
||||
Après une mise à jour, lancez l'outil d'audit pour vérifier si de nouvelles options sont disponibles :
|
||||
|
||||
---
|
||||
|
||||
## Programmation (Quand exécuter ?)
|
||||
|
||||
Le système repose sur `cron`. Voici la configuration recommandée à ajouter via `crontab -e` :
|
||||
|
||||
| Tâche | Fréquence | Commande |
|
||||
| --- | --- | --- |
|
||||
| **Check Disque** | Toutes les 5 min | `/opt/monitoring/bin/check_disk.sh` |
|
||||
| **Moteur d'alerte** | Toutes les 2 min | `/opt/monitoring/bin/alert-engine.sh` |
|
||||
| **Mise à jour** | Une fois par jour | `/opt/monitoring/bin/monitoring-update.sh` |
|
||||
|
||||
### Exemple de Crontab :
|
||||
|
||||
```cron
|
||||
*/5 * * * * /opt/monitoring/bin/check_disk.sh > /dev/null 2>&1
|
||||
*/2 * * * * /opt/monitoring/bin/alert-engine.sh > /dev/null 2>&1
|
||||
0 4 * * * /opt/monitoring/bin/monitoring-update.sh > /dev/null 2>&1
|
||||
```bash
|
||||
php /opt/monitoring/bin/monitoring-update-config.php
|
||||
|
||||
```
|
||||
|
||||
## Développer une nouvelle sonde
|
||||
---
|
||||
|
||||
Pour créer un nouveau check (ex: `check_ram.sh`) :
|
||||
## 🕒 Planification (Crontab)
|
||||
|
||||
1. Inclure la lib : `. /opt/monitoring/lib/monitoring-lib.sh`
|
||||
2. Effectuer votre mesure.
|
||||
3. Utiliser les fonctions de log : `log_info`, `log_warning` ou `log_critical`.
|
||||
4. Le moteur d'alerte détectera automatiquement le nouvel événement au prochain passage.
|
||||
Le système est conçu pour être piloté par `cron`. Voici la configuration recommandée :
|
||||
|
||||
| Tâche | Fréquence | Commande |
|
||||
| --- | --- | --- |
|
||||
| **Sondes (ex: Disque)** | Toutes les 5 min | `php /opt/monitoring/bin/check_disk.php` |
|
||||
| **Moteur d'alerte** | Chaque minute | `php /opt/monitoring/bin/alert-engine.php` |
|
||||
| **Mise à jour** | 1x par jour | `php /opt/monitoring/bin/monitoring-update.php` |
|
||||
|
||||
---
|
||||
|
||||
## Licence
|
||||
## 🛠️ Développer une nouvelle sonde
|
||||
|
||||
Ce projet est un logiciel libre : vous pouvez le redistribuer et le modifier selon les termes de la GNU Affero General Public License (AGPLv3) telle que publiée par la Free Software Foundation.
|
||||
Le système est agnostique. Pour ajouter un check :
|
||||
|
||||
Le code source modifié doit être mis à disposition si vous utilisez ce logiciel via un réseau (usage SaaS).
|
||||
1. Créez un script qui écrit une ligne JSON dans `/var/log/monitoring/events.jsonl`.
|
||||
2. Format attendu : `{"time":"...", "level":"ERROR", "app":"my_app", "event":"disk_full", "msg":"..."}`.
|
||||
3. Le moteur PHP `alert-engine.php` traitera l'alerte automatiquement au prochain passage.
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ Licence
|
||||
|
||||
Ce projet est distribué sous licence **GNU Affero General Public License (AGPLv3)**.
|
||||
|
||||
*Note : Si vous modifiez ce code pour l'utiliser via un réseau (SaaS), vous devez rendre vos modifications publiques.*
|
||||
|
||||
253
servers/linux/monitoring/bin/alert-engine.php
Executable file
253
servers/linux/monitoring/bin/alert-engine.php
Executable file
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Alert Engine - PHP Version
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
* License: GNU Affero General Public License v3
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||
|
||||
// --- Initialisation de la configuration spécifique ---
|
||||
// On charge les fichiers de conf s'ils existent
|
||||
foreach (["/opt/monitoring/conf/alert-engine.conf.php", "/opt/monitoring/conf/alert-engine.conf.local.php"] as $conf) {
|
||||
if (file_exists($conf)) {
|
||||
$extra_conf = include $conf;
|
||||
if (is_array($extra_conf)) {
|
||||
$CONFIG = array_replace_recursive($CONFIG, $extra_conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chemins des fichiers
|
||||
$LOG_SOURCE = $CONFIG['LOG_FILE'] ?? '/var/log/monitoring/events.jsonl';
|
||||
$STATE_FILE = $CONFIG['ALERT_STATE_FILE'] ?? '/var/lib/monitoring/alert-engine.offset';
|
||||
$DEDUP_FILE = $CONFIG['ALERT_DEDUP_FILE'] ?? '/var/lib/monitoring/alert-engine.dedup';
|
||||
$DEDUP_WINDOW = $CONFIG['ALERT_DEDUP_WINDOW'] ?? 3600;
|
||||
|
||||
// Création des répertoires si nécessaire
|
||||
ensure_parent_dir($STATE_FILE);
|
||||
ensure_parent_dir($DEDUP_FILE);
|
||||
|
||||
/**
|
||||
* Nettoyage du fichier de déduplication
|
||||
* Supprime les entrées plus vieilles que la fenêtre de déduplication ($DEDUP_WINDOW)
|
||||
*/
|
||||
function cleanup_dedup_file() {
|
||||
global $DEDUP_FILE, $DEDUP_WINDOW;
|
||||
|
||||
// Si le fichier n'existe pas, rien à nettoyer
|
||||
if (!file_exists($DEDUP_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$kept = [];
|
||||
$has_changed = false;
|
||||
|
||||
// Lecture du fichier
|
||||
$lines = file($DEDUP_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
if ($lines === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$parts = explode('|', $line);
|
||||
|
||||
// On vérifie si la ligne est valide et si le timestamp (index 1) est encore dans la fenêtre
|
||||
if (count($parts) >= 2) {
|
||||
$timestamp = (int)$parts[1];
|
||||
if (($now - $timestamp) <= (int)$DEDUP_WINDOW) {
|
||||
$kept[] = $line;
|
||||
} else {
|
||||
$has_changed = true; // On a trouvé au moins une ligne à supprimer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On ne réécrit le fichier que si des lignes ont été supprimées
|
||||
if ($has_changed) {
|
||||
$content = implode("\n", $kept);
|
||||
if (!empty($content)) {
|
||||
$content .= "\n";
|
||||
}
|
||||
|
||||
// LOCK_EX évite que deux instances n'écrivent en même temps
|
||||
file_put_contents($DEDUP_FILE, $content, LOCK_EX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une alerte doit être envoyée (Déduplication)
|
||||
* La clé attendue est : "hostname|app|level|event"
|
||||
*/
|
||||
function should_notify_dedup(string $key): bool {
|
||||
global $DEDUP_FILE, $DEDUP_WINDOW;
|
||||
|
||||
if (!file_exists($DEDUP_FILE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$last_ts = 0;
|
||||
|
||||
$handle = fopen($DEDUP_FILE, 'r');
|
||||
if (!$handle) {
|
||||
return true; // En cas d'erreur de lecture, on autorise l'alerte par sécurité
|
||||
}
|
||||
|
||||
// On parcourt le fichier
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) continue;
|
||||
|
||||
$p = explode('|', $line);
|
||||
|
||||
// Format du fichier : host|timestamp|app|level|event
|
||||
// On reconstruit la clé de comparaison (sans le timestamp index 1)
|
||||
if (count($p) >= 5) {
|
||||
$row_key = "{$p[0]}|{$p[2]}|{$p[3]}|{$p[4]}";
|
||||
|
||||
if ($row_key === $key) {
|
||||
$last_ts = (int)$p[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
|
||||
// Calcul de l'écart : vrai si on a dépassé la fenêtre ou si jamais vu (last_ts = 0)
|
||||
return ($now - $last_ts) >= (int)$DEDUP_WINDOW;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Envoi vers ntfy
|
||||
*/
|
||||
function send_ntfy($title, $body, $level) {
|
||||
global $CONFIG;
|
||||
if (!($CONFIG['ALERT_NTFY_ENABLED'] ?? true)) return true;
|
||||
if (empty($CONFIG['NTFY_SERVER']) || empty($CONFIG['NTFY_TOPIC'])) return false;
|
||||
|
||||
$priority = ['CRITICAL' => 'urgent', 'ERROR' => 'high', 'WARNING' => 'default'][$level] ?? 'default';
|
||||
$tags = ($CONFIG['NTFY_TAGS'][$level]) ?? 'warning';
|
||||
|
||||
$url = rtrim($CONFIG['NTFY_SERVER'], '/') . '/' . $CONFIG['NTFY_TOPIC'];
|
||||
$headers = ["Title: $title", "Priority: $priority", "Tags: $tags"];
|
||||
|
||||
if (!empty($CONFIG['NTFY_TOKEN'])) {
|
||||
$headers[] = "Authorization: Bearer " . $CONFIG['NTFY_TOKEN'];
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
return ($status >= 200 && $status < 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoi par mail
|
||||
*/
|
||||
function send_mail($subject, $body) {
|
||||
global $CONFIG;
|
||||
if (!($CONFIG['ALERT_MAIL_ENABLED'] ?? true)) return true;
|
||||
if (empty($CONFIG['DEST'])) return false;
|
||||
|
||||
$prefix = $CONFIG['ALERT_MAIL_SUBJECT_PREFIX'] ?? '[monitoring]';
|
||||
$headers = "From: monitoring@" . gethostname() . "\r\n" . "Content-Type: text/plain; charset=UTF-8";
|
||||
|
||||
return mail($CONFIG['DEST'], "$prefix $subject", $body, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traitement d'une ligne de log
|
||||
*/
|
||||
function process_line($line) {
|
||||
global $CONFIG, $DEDUP_FILE;
|
||||
$data = json_decode($line, true);
|
||||
if (!$data || !isset($data['level'], $data['event'])) return;
|
||||
|
||||
$level = strtoupper($data['level']);
|
||||
$event = $data['event'];
|
||||
|
||||
// On garde uniquement l'ignore list explicite pour les événements
|
||||
if (in_array($event, ($CONFIG['ALERT_IGNORE_EVENTS'] ?? []))) return;
|
||||
|
||||
// Déduplication
|
||||
$key = "{$data['host']}|{$data['app']}|{$level}|{$event}";
|
||||
if (!should_notify_dedup($key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Détermination des canaux
|
||||
$channels_str = $CONFIG['RULES'][$event] ?? $CONFIG['DEFAULT_CHANNELS'][$level] ?? '';
|
||||
|
||||
// Si aucun canal n'est défini pour ce niveau, ALORS on s'arrête
|
||||
if (empty($channels_str)) return;
|
||||
|
||||
$channels = explode(',', $channels_str);
|
||||
|
||||
$title = "{$data['host']} [{$data['app']}] $level $event";
|
||||
$body = sprintf(
|
||||
"Date: %s\nHôte: %s\nScript: %s\nNiveau: %s\nÉvénement: %s\n\nMessage:\n%s",
|
||||
$data['ts'] ?? 'N/A', $data['host'], $data['app'], $level, $event, $data['message'] ?? ''
|
||||
);
|
||||
|
||||
foreach ($channels as $ch) {
|
||||
$ch = trim($ch);
|
||||
$success = false;
|
||||
if ($ch === 'ntfy') {
|
||||
$success = send_ntfy($title, $body, $level);
|
||||
$success ? log_info("alert_sent_ntfy", "Notification ntfy envoyée", ["event=$event"])
|
||||
: log_error("alert_ntfy_failed", "Échec ntfy", ["event=$event"]);
|
||||
} elseif ($ch === 'mail') {
|
||||
$success = send_mail($title, $body);
|
||||
$success ? log_info("alert_sent_mail", "Mail envoyé", ["event=$event"])
|
||||
: log_error("alert_mail_failed", "Échec mail", ["event=$event"]);
|
||||
}
|
||||
}
|
||||
|
||||
// Enregistrement déduplication
|
||||
$entry = sprintf("%s|%s|%s|%s|%s\n", $data['host'], time(), $data['app'], $level, $event);
|
||||
file_put_contents($DEDUP_FILE, $entry, FILE_APPEND);
|
||||
}
|
||||
|
||||
// --- Main ---
|
||||
|
||||
lock_or_exit("alert-engine");
|
||||
|
||||
if (!file_exists($LOG_SOURCE)) {
|
||||
log_notice("alert_log_missing", "Fichier de log absent", ["file=$LOG_SOURCE"]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$last_offset = file_exists($STATE_FILE) ? (int)file_get_contents($STATE_FILE) : 0;
|
||||
$current_size = filesize($LOG_SOURCE);
|
||||
|
||||
// Gestion de la rotation de log
|
||||
if ($last_offset > $current_size) {
|
||||
log_notice("alert_offset_reset", "Rotation détectée", ["old=$last_offset", "new=0"]);
|
||||
$last_offset = 0;
|
||||
}
|
||||
|
||||
cleanup_dedup_file();
|
||||
|
||||
$fp = fopen($LOG_SOURCE, 'r');
|
||||
if ($last_offset > 0) fseek($fp, $last_offset);
|
||||
|
||||
while (($line = fgets($fp)) !== false) {
|
||||
if (trim($line) !== '') {
|
||||
process_line($line);
|
||||
}
|
||||
}
|
||||
fclose($fp);
|
||||
|
||||
file_put_contents($STATE_FILE, $current_size);
|
||||
exit_with_status();
|
||||
@@ -13,41 +13,49 @@
|
||||
|
||||
set -u
|
||||
|
||||
. /opt/monitoring/lib/monitoring-lib.sh || exit 3
|
||||
|
||||
# --- Configuration (Seuils par défaut) ---
|
||||
WARNING=80
|
||||
CRITICAL=95
|
||||
MOUNTS=("/" "/var" "/home")
|
||||
LOG_BIN="/opt/monitoring/bin/log-cli.php"
|
||||
|
||||
# --- Vérification ROOT ---
|
||||
if [ "${EUID}" -ne 0 ]; then
|
||||
echo "ERREUR : Ce script doit être exécuté en tant que root." >&2
|
||||
$LOG_BIN ERROR "internal_error" "Tentative d'exécution sans privilèges root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for mount in "${MOUNTS[@]}"; do
|
||||
# On vérifie si le point de montage existe avant de tester
|
||||
if ! mountpoint -q "$mount"; then
|
||||
continue
|
||||
fi
|
||||
if ! mountpoint -q "$mount"; then continue; fi
|
||||
|
||||
# --- 1. Espace Disque ---
|
||||
used_pct="$(df -P "$mount" 2>/dev/null | awk 'NR==2 {gsub("%","",$5); print $5}')"
|
||||
|
||||
if [[ ! "$used_pct" =~ ^[0-9]+$ ]]; then
|
||||
log_error "check_failed" "Impossible de lire l'utilisation disque" "mount=$mount"
|
||||
continue
|
||||
$LOG_BIN ERROR "check_failed" "Erreur lecture disque $mount."
|
||||
else
|
||||
if [ "$used_pct" -ge "$CRITICAL" ]; then
|
||||
$LOG_BIN CRITICAL "disk_usage_critical" "Disque $mount critique : $used_pct% utilisé."
|
||||
elif [ "$used_pct" -ge "$WARNING" ]; then
|
||||
$LOG_BIN WARNING "disk_usage_high" "Disque $mount élevé : $used_pct% utilisé."
|
||||
else
|
||||
$LOG_BIN INFO "disk_ok" "Disque $mount OK : $used_pct% utilisé."
|
||||
fi
|
||||
fi
|
||||
|
||||
level="$(threshold_level "$used_pct" "$WARNING" "$CRITICAL")"
|
||||
# --- 2. Inodes (Déplacé à l'intérieur de la boucle) ---
|
||||
inode_pct="$(df -iP "$mount" 2>/dev/null | awk 'NR==2 {gsub("%","",$5); print $5}')"
|
||||
|
||||
case "$level" in
|
||||
INFO)
|
||||
log_info "disk_ok" "Utilisation disque normale" \
|
||||
"mount=$mount" "used_pct=$used_pct" "warning=$WARNING" "critical=$CRITICAL"
|
||||
;;
|
||||
WARNING)
|
||||
log_warning "disk_usage_high" "Utilisation disque élevée" \
|
||||
"mount=$mount" "used_pct=$used_pct" "warning=$WARNING" "critical=$CRITICAL"
|
||||
;;
|
||||
CRITICAL)
|
||||
log_critical "disk_usage_critical" "Utilisation disque critique" \
|
||||
"mount=$mount" "used_pct=$used_pct" "warning=$WARNING" "critical=$CRITICAL"
|
||||
;;
|
||||
esac
|
||||
if [[ ! "$inode_pct" =~ ^[0-9]+$ ]]; then
|
||||
$LOG_BIN ERROR "check_failed" "Erreur lecture inodes $mount."
|
||||
else
|
||||
if [ "$inode_pct" -ge "$CRITICAL" ]; then
|
||||
$LOG_BIN CRITICAL "inode_usage_critical" "Inodes $mount critiques ($inode_pct%)."
|
||||
elif [ "$inode_pct" -ge "$WARNING" ]; then
|
||||
$LOG_BIN WARNING "inode_usage_high" "Inodes $mount élevés ($inode_pct%)."
|
||||
else
|
||||
$LOG_BIN INFO "inode_ok" "Inodes $mount OK ($inode_pct%)."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit_with_status
|
||||
66
servers/linux/monitoring/bin/check_smart.sh
Executable file
66
servers/linux/monitoring/bin/check_smart.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
LOG_BIN="/opt/monitoring/bin/log-cli.php"
|
||||
|
||||
# --- Vérification ROOT ---
|
||||
if [ "${EUID}" -ne 0 ]; then
|
||||
echo "ERREUR : Ce script doit être exécuté en tant que root." >&2
|
||||
$LOG_BIN ERROR "internal_error" "Tentative d'exécution sans privilèges root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Vérification et installation de smartctl ---
|
||||
if ! command -v smartctl >/dev/null 2>&1; then
|
||||
# On tente l'installation (nécessite root, ce qui est le cas via cron)
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update && apt-get install -y smartmontools
|
||||
fi
|
||||
|
||||
# Re-vérification après tentative
|
||||
if ! command -v smartctl >/dev/null 2>&1; then
|
||||
$LOG_BIN ERROR "internal_error" "smartctl non trouvé et installation impossible."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# On récupère les disques qui ont un transport physique (SATA, NVMe, USB)
|
||||
# Cela exclut d'office les /dev/mapper, /dev/dm-X, /dev/loopX
|
||||
DISKS=$(lsblk -dno NAME,TRAN | awk '$2!="" {print "/dev/"$1}')
|
||||
|
||||
for disk in $DISKS; do
|
||||
# Vérification : est-ce que smartctl peut lire ce périphérique ?
|
||||
# --scan-open vérifie si le disque est capable de répondre
|
||||
if ! smartctl -i "$disk" | grep -q "SMART support is: Enabled" 2>/dev/null; then
|
||||
# On peut logguer en INFO que le disque est ignoré car non-SMART (ex: clé USB basique)
|
||||
continue
|
||||
fi
|
||||
|
||||
# 1. État de santé global
|
||||
smart_output=$(smartctl -H "$disk" 2>/dev/null)
|
||||
exit_code=$?
|
||||
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
$LOG_BIN CRITICAL "smart_health_bad" "État de santé PHYSIQUE CRITIQUE sur $disk"
|
||||
else
|
||||
# 2. Température
|
||||
temp=$(smartctl -A "$disk" 2>/dev/null | awk '/Temperature_Celsius/ {print $10}' | head -n 1)
|
||||
[ -z "$temp" ] && temp=$(smartctl -a "$disk" 2>/dev/null | awk '/Temperature:/ {print $2}' | head -n 1)
|
||||
|
||||
if [ -n "$temp" ]; then
|
||||
if [ "$temp" -ge 60 ]; then
|
||||
$LOG_BIN WARNING "disk_temp_high" "Surchauffe physique sur $disk : ${temp}°C"
|
||||
fi
|
||||
fi
|
||||
$LOG_BIN INFO "smart_health_ok" "Disque physique $disk sain."
|
||||
fi
|
||||
done
|
||||
@@ -1,55 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2026 Cédric Abonnel
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
# License: GNU Affero General Public License v3
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Configuration ---
|
||||
BASE_DIR="/opt/monitoring"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LOG_DIR="/var/log/monitoring"
|
||||
STATE_DIR="/var/lib/monitoring"
|
||||
LOCK_DIR="/var/lock/monitoring"
|
||||
TMP_DIR="/tmp/monitoring-install"
|
||||
# Journal des fichiers installés pour déinstallation/état des lieux
|
||||
INSTALLED_LOG="${STATE_DIR}/installed-files.log"
|
||||
|
||||
UPDATE_BASE_URL="https://git.abonnel.fr/cedricAbonnel/scripts-bash/raw/branch/main/servers/linux/monitoring"
|
||||
MANIFEST_URL="${UPDATE_BASE_URL}/manifest.txt"
|
||||
|
||||
INSTALL_DEPS="${INSTALL_DEPS:-true}"
|
||||
CREATE_LOCAL_CONF="${CREATE_LOCAL_CONF:-true}"
|
||||
|
||||
# --- 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 "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
|
||||
|
||||
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
|
||||
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 smartmontools > /dev/null
|
||||
ok "Dépendances installées."
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_dirs() {
|
||||
mkdir -p "${BASE_DIR}" "${CONF_DIR}" "${LOG_DIR}" "${STATE_DIR}" "${LOCK_DIR}" "${TMP_DIR}"
|
||||
chmod 755 "${BASE_DIR}" "${CONF_DIR}" "${LOG_DIR}" "${STATE_DIR}" "${LOCK_DIR}"
|
||||
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}"
|
||||
touch "$INSTALLED_LOG"
|
||||
}
|
||||
|
||||
fetch_manifest() {
|
||||
info "Téléchargement du manifeste distant..."
|
||||
curl -fsS "${MANIFEST_URL}" -o "${TMP_DIR}/manifest.txt"
|
||||
}
|
||||
|
||||
@@ -60,89 +62,86 @@ validate_manifest() {
|
||||
$2 ~ /^(644|755|600)$/ &&
|
||||
$3 ~ /^(bin|lib|conf)\/[A-Za-z0-9._\/-]+$/ &&
|
||||
$3 !~ /\.\./
|
||||
' "${TMP_DIR}/manifest.txt" >/dev/null
|
||||
' "${TMP_DIR}/manifest.txt"
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
local mode="$1"
|
||||
local file="$2"
|
||||
chmod "$mode" "$file"
|
||||
}
|
||||
|
||||
download_one() {
|
||||
local expected_hash="$1"
|
||||
local mode="$2"
|
||||
local rel_path="$3"
|
||||
|
||||
local url="${UPDATE_BASE_URL}/${rel_path}"
|
||||
download_and_install() {
|
||||
local expected_hash=$1 mode=$2 rel_path=$3
|
||||
local dst="${BASE_DIR}/${rel_path}"
|
||||
|
||||
if [ -f "$dst" ]; then
|
||||
local current_hash
|
||||
current_hash=$(sha256sum "$dst" | awk '{print $1}')
|
||||
[ "$current_hash" == "$expected_hash" ] && return 0
|
||||
info "Mise à jour : $rel_path"
|
||||
else
|
||||
info "Installation : $rel_path"
|
||||
fi
|
||||
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp "${TMP_DIR}/file.XXXXXX")"
|
||||
|
||||
curl -fsS "$url" -o "$tmp_file"
|
||||
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 "Hash invalide pour ${rel_path}" >&2
|
||||
err "Hash invalide pour $rel_path"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
apply_mode "$mode" "$tmp_file"
|
||||
mv -f "$tmp_file" "$dst"
|
||||
chmod "$mode" "$dst"
|
||||
}
|
||||
|
||||
install_from_manifest() {
|
||||
while read -r hash mode rel_path; do
|
||||
[ -n "${hash:-}" ] || continue
|
||||
download_one "$hash" "$mode" "$rel_path"
|
||||
done < "${TMP_DIR}/manifest.txt"
|
||||
# --- NOUVEAUTÉ : Gestion du journal et purge propre ---
|
||||
|
||||
update_installed_log() {
|
||||
# On sauvegarde la liste des chemins relatifs du manifeste validé dans le journal permanent
|
||||
awk '{print $3}' "${TMP_DIR}/manifest-valid.txt" > "$INSTALLED_LOG"
|
||||
ok "Journal des fichiers déployés mis à jour ($INSTALLED_LOG)."
|
||||
}
|
||||
|
||||
create_local_conf_if_missing() {
|
||||
if [ "${CREATE_LOCAL_CONF}" != "true" ]; then
|
||||
return 0
|
||||
purge_obsolete_files() {
|
||||
info "Analyse des fichiers obsolètes (Synchronisation avec le journal)..."
|
||||
|
||||
# On compare ce qui était installé (journal) avec ce qui est dans le nouveau manifeste
|
||||
if [ ! -s "$INSTALLED_LOG" ]; then
|
||||
warn "Journal vide, passage en mode scan classique."
|
||||
# Fallback sur le scan de dossier si le journal n'existe pas encore
|
||||
find "${BASE_DIR}/bin" "${BASE_DIR}/lib" "${BASE_DIR}/conf" -type f 2>/dev/null | while read -r local_file; do
|
||||
local rel_path="${local_file#$BASE_DIR/}"
|
||||
[[ "$rel_path" == *".local."* ]] && continue
|
||||
if ! grep -qw "$rel_path" "${TMP_DIR}/manifest-valid.txt"; then
|
||||
warn "Suppression : $rel_path"
|
||||
rm -f "$local_file"
|
||||
fi
|
||||
done
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -f "${CONF_DIR}/alert-engine.local.conf" ]; then
|
||||
cat > "${CONF_DIR}/alert-engine.local.conf" <<'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
NTFY_SERVER="https://ntfy.sh"
|
||||
NTFY_TOPIC="FjdJ7qex2oGqZkV3OMaqNIxe"
|
||||
NTFY_TOKEN="A_REMPLACER"
|
||||
|
||||
DEST="root"
|
||||
EOF
|
||||
chmod 600 "${CONF_DIR}/alert-engine.local.conf"
|
||||
# Mode Journal : On lit l'ancien journal pour voir ce qui doit disparaître
|
||||
while read -r old_file; do
|
||||
# Si le fichier du journal n'est plus dans le nouveau manifeste
|
||||
if ! grep -qw "$old_file" "${TMP_DIR}/manifest-valid.txt"; then
|
||||
if [ -f "${BASE_DIR}/$old_file" ]; then
|
||||
# Protection ultime des fichiers .local (au cas où ils auraient été loggués par erreur)
|
||||
if [[ "$old_file" != *".local."* ]]; then
|
||||
warn "Suppression du fichier obsolète : $old_file"
|
||||
rm -f "${BASE_DIR}/$old_file"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done < "$INSTALLED_LOG"
|
||||
}
|
||||
|
||||
show_next_steps() {
|
||||
cat <<'EOF'
|
||||
|
||||
Installation terminée.
|
||||
|
||||
Étapes suivantes :
|
||||
1. Éditer /opt/monitoring/conf/alert-engine.local.conf
|
||||
2. Remplacer NTFY_TOKEN par le vrai token
|
||||
3. Tester :
|
||||
/opt/monitoring/bin/check_disk.sh
|
||||
/opt/monitoring/bin/alert-engine.sh
|
||||
4. Ajouter cron ou systemd timer
|
||||
|
||||
Exemple cron :
|
||||
*/5 * * * * /opt/monitoring/bin/check_disk.sh
|
||||
*/5 * * * * /opt/monitoring/bin/check_ram.sh
|
||||
15 */6 * * * /opt/monitoring/bin/check_cert.sh
|
||||
30 2 * * * /opt/monitoring/bin/check_backup.sh
|
||||
10 3 * * * /opt/monitoring/bin/monitoring-update.sh
|
||||
* * * * * /opt/monitoring/bin/alert-engine.sh
|
||||
EOF
|
||||
}
|
||||
# --- Main ---
|
||||
|
||||
main() {
|
||||
require_root
|
||||
@@ -150,14 +149,39 @@ main() {
|
||||
prepare_dirs
|
||||
fetch_manifest
|
||||
|
||||
if ! validate_manifest; then
|
||||
echo "Le manifeste est invalide." >&2
|
||||
if ! validate_manifest > "${TMP_DIR}/manifest-valid.txt"; then
|
||||
err "Le manifeste est invalide ou corrompu."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_from_manifest
|
||||
create_local_conf_if_missing
|
||||
show_next_steps
|
||||
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 et Journalisation"
|
||||
purge_obsolete_files
|
||||
update_installed_log
|
||||
|
||||
rm -rf "${TMP_DIR}"
|
||||
echo "--------------------------------------------------"
|
||||
ok "Opération terminée avec succès."
|
||||
|
||||
# --- Vérification de la configuration ---
|
||||
local_conf="${CONF_DIR}/monitoring.local.conf.php"
|
||||
orig_conf="${CONF_DIR}/monitoring.conf.php"
|
||||
|
||||
if [ -f "$local_conf" ] && [ -f "$orig_conf" ]; then
|
||||
if [ "$(sha256sum "$local_conf" | awk '{print $1}')" == "$(sha256sum "$orig_conf" | awk '{print $1}')" ]; then
|
||||
echo -e "\n\e[33m[ATTENTION]\e[0m Votre fichier de configuration est identique à l'original."
|
||||
warn "Pensez à éditer ${local_conf}."
|
||||
else
|
||||
ok "Configuration locale personnalisée détectée."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
23
servers/linux/monitoring/bin/log-cli.php
Executable file
23
servers/linux/monitoring/bin/log-cli.php
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Pont de logging pour scripts Bash
|
||||
*/
|
||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||
|
||||
if ($argc < 4) {
|
||||
fwrite(STDERR, "Usage: log-cli.php <LEVEL> <EVENT> <MESSAGE> [CONTEXT...]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$level = strtoupper($argv[1]);
|
||||
$event = $argv[2];
|
||||
$message = $argv[3];
|
||||
$context = [];
|
||||
|
||||
// On récupère les arguments restants comme contexte
|
||||
for ($i = 4; $i < $argc; $i++) {
|
||||
$context[] = $argv[$i];
|
||||
}
|
||||
|
||||
log_event($level, $event, $message, $context);
|
||||
104
servers/linux/monitoring/bin/monitoring-update-config.php
Executable file
104
servers/linux/monitoring/bin/monitoring-update-config.php
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Audit de dérive des configurations PHP
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
* License: GNU Affero General Public License v3
|
||||
*/
|
||||
|
||||
// --- Initialisation & Sécurité ---
|
||||
|
||||
if (posix_getuid() !== 0) {
|
||||
fwrite(STDERR, "Ce script doit être exécuté en root.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const CONF_DIR = '/opt/monitoring/conf';
|
||||
|
||||
/**
|
||||
* Extrait les clés d'un fichier de configuration PHP (tableau return [])
|
||||
* sans exécuter le fichier pour éviter les effets de bord, via Regex.
|
||||
*/
|
||||
function extract_keys($file) {
|
||||
$content = file_get_contents($file);
|
||||
// On cherche les patterns : 'KEY' => ou "KEY" =>
|
||||
preg_match_all('/[\'"]([A-Z0-9_]+)[\'"]\s*=>/i', $content, $matches);
|
||||
$keys = $matches[1] ?? [];
|
||||
sort($keys);
|
||||
return array_unique($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logique de logging
|
||||
*/
|
||||
function log_audit($level, $event, $msg) {
|
||||
echo sprintf("[%s] %s: %s\n", strtoupper($level), $event, $msg);
|
||||
}
|
||||
|
||||
function check_config_drift() {
|
||||
$found_issue = false;
|
||||
$reviewed_files = 0;
|
||||
$files_requiring_action = 0;
|
||||
|
||||
log_audit('info', 'audit_start', "Début de l'audit des configurations PHP");
|
||||
|
||||
// On cherche les fichiers .php qui ne sont pas des .local.php
|
||||
$base_files = glob(CONF_DIR . '/*.php');
|
||||
$base_files = array_filter($base_files, function($f) {
|
||||
return !str_ends_with($f, '.local.php');
|
||||
});
|
||||
|
||||
foreach ($base_files as $base_conf) {
|
||||
$reviewed_files++;
|
||||
$base_name = basename($base_conf);
|
||||
$local_conf = str_replace('.php', '.local.php', $base_conf);
|
||||
$local_name = basename($local_conf);
|
||||
|
||||
// 1. Si le fichier local n'existe pas
|
||||
if (!file_exists($local_conf)) {
|
||||
if (copy($base_conf, $local_conf)) {
|
||||
chmod($local_conf, 0600);
|
||||
log_audit('notice', 'audit_missing_local', "Le fichier $local_name a été créé par copie de $base_name");
|
||||
} else {
|
||||
log_audit('error', 'audit_create_local_failed', "Impossible de créer $local_name");
|
||||
$found_issue = true;
|
||||
$files_requiring_action++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Comparaison des clés
|
||||
$keys_base = extract_keys($base_conf);
|
||||
$keys_local = extract_keys($local_conf);
|
||||
|
||||
$missing = array_diff($keys_base, $keys_local); // Présent dans base mais pas local
|
||||
$obsolete = array_diff($keys_local, $keys_base); // Présent dans local mais plus dans base
|
||||
|
||||
if (!empty($missing) || !empty($obsolete)) {
|
||||
$found_issue = true;
|
||||
$files_requiring_action++;
|
||||
|
||||
log_audit('warning', 'audit_file_requires_action', "Vérification requise pour $local_name");
|
||||
|
||||
if (!empty($missing)) {
|
||||
log_audit('warning', 'audit_keys_missing', "Options absentes de $local_name : " . implode(', ', $missing));
|
||||
}
|
||||
|
||||
if (!empty($obsolete)) {
|
||||
log_audit('info', 'audit_keys_obsolete', "Options obsolètes ou personnalisées dans $local_name : " . implode(', ', $obsolete));
|
||||
}
|
||||
} else {
|
||||
log_audit('info', 'audit_file_ok', "Le fichier $local_name est synchronisé avec $base_name");
|
||||
}
|
||||
}
|
||||
|
||||
// Résumé final
|
||||
if (!$found_issue) {
|
||||
log_audit('info', 'audit_success', "Toutes les configurations sont à jour ($reviewed_files fichiers vérifiés)");
|
||||
} else {
|
||||
log_audit('warning', 'audit_requires_action', "Action requise sur $files_requiring_action fichier(s) sur $reviewed_files");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Main ---
|
||||
check_config_drift();
|
||||
179
servers/linux/monitoring/bin/monitoring-update.php
Executable file
179
servers/linux/monitoring/bin/monitoring-update.php
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Moteur de mise à jour
|
||||
* Pilotage du script Bash + Initialisation des Configs + Cron + Ménage
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||
|
||||
// Sécurité : Un seul update à la fois
|
||||
lock_or_exit("monitoring-update");
|
||||
|
||||
echo "\e[1m--- Début de la mise à jour système ---\e[0m\n";
|
||||
|
||||
// 1. Avant l'update, on mémorise la liste des fichiers actuellement installés (L'AVANT)
|
||||
$installed_log = $CONFIG['INSTALLED_LOG'] ?? '/var/lib/monitoring/installed-files.log';
|
||||
$old_installed_files = [];
|
||||
if (file_exists($installed_log)) {
|
||||
$old_installed_files = file($installed_log, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
}
|
||||
|
||||
// 2. Exécution du moteur de synchronisation Bash
|
||||
$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);
|
||||
}
|
||||
|
||||
// Exécution du moteur de synchronisation Bash
|
||||
$command = "bash " . escapeshellarg($install_script) . " --auto 2>&1";
|
||||
$handle = popen($command, 'r');
|
||||
|
||||
if ($handle) {
|
||||
while (!feof($handle)) {
|
||||
$line = fgets($handle);
|
||||
if ($line) echo $line;
|
||||
}
|
||||
$exit_code = pclose($handle);
|
||||
} else {
|
||||
$exit_code = 1;
|
||||
}
|
||||
|
||||
if ($exit_code !== 0) {
|
||||
log_error("update_failed", "Le script Bash a échoué");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// 3. Après l'update, on récupère la nouvelle liste (L'APRÈS)
|
||||
$new_installed_files = [];
|
||||
if (file_exists($installed_log)) {
|
||||
$new_installed_files = file($installed_log, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
}
|
||||
|
||||
// Détermination des fichiers qui ont été SUPPRIMÉS lors de cette mise à jour (dynamique)
|
||||
$deleted_files = array_diff($old_installed_files, $new_installed_files);
|
||||
|
||||
echo "\e[1m--- Finalisation des configurations ---\e[0m\n";
|
||||
ensure_local_configs();
|
||||
ensure_crontab_entries($deleted_files);
|
||||
|
||||
echo "\e[32m[OK]\e[0m Système à jour.\n";
|
||||
|
||||
/**
|
||||
* Nettoyage et Mise à jour du Crontab
|
||||
* @param array $deleted_files Liste des chemins de fichiers supprimés du déploiement
|
||||
*/
|
||||
function ensure_crontab_entries($deleted_files) {
|
||||
global $CONFIG, $MONITORING_BASE_DIR;
|
||||
|
||||
// --- CONFIGURATION DU MÉNAGE STATIQUE ---
|
||||
// On ajoute ici les vieux chemins orphelins à éjecter impérativement
|
||||
$static_cleanup_patterns = [
|
||||
'/usr/local/bin/sys_check.sh',
|
||||
'bin/check_disk.php', // Ancienne erreur de nommage
|
||||
'bin/check-disk.sh' // Ancienne erreur de séparateur
|
||||
];
|
||||
|
||||
// Préparation des jobs requis
|
||||
$required_jobs = array_map(function($job) use ($MONITORING_BASE_DIR) {
|
||||
return str_replace('{BASE_DIR}', $MONITORING_BASE_DIR, $job);
|
||||
}, $CONFIG['CRON_JOBS'] ?? [
|
||||
"*/5 * * * * bash {$MONITORING_BASE_DIR}/bin/check_disk.sh > /dev/null 2>&1",
|
||||
"*/15 * * * * bash {$MONITORING_BASE_DIR}/bin/check_smart.sh > /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"
|
||||
]);
|
||||
|
||||
$current_cron = shell_exec("crontab -l 2>/dev/null") ?: "";
|
||||
$lines = explode("\n", trim($current_cron));
|
||||
$new_lines = [];
|
||||
$has_changed = false;
|
||||
|
||||
// --- PHASE A : Nettoyage (Dynamique + Statique) ---
|
||||
foreach ($lines as $line) {
|
||||
$trim_line = trim($line);
|
||||
if (empty($trim_line)) continue;
|
||||
|
||||
$keep = true;
|
||||
|
||||
// 1. Nettoyage Dynamique (basé sur le log Git)
|
||||
foreach ($deleted_files as $deleted_path) {
|
||||
if (strpos($trim_line, $deleted_path) !== false) {
|
||||
echo "\e[33m[CLEAN]\e[0m Script supprimé du déploiement : " . basename($deleted_path) . "\n";
|
||||
$keep = false;
|
||||
$has_changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Nettoyage Statique (basé sur ta liste forcée)
|
||||
if ($keep) {
|
||||
foreach ($static_cleanup_patterns as $pattern) {
|
||||
if (strpos($trim_line, $pattern) !== false) {
|
||||
echo "\e[33m[CLEAN]\e[0m Suppression du résidu historique : $pattern\n";
|
||||
$keep = false;
|
||||
$has_changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($keep) $new_lines[] = $trim_line;
|
||||
}
|
||||
|
||||
// --- PHASE B : Ajout des nouveaux jobs ---
|
||||
foreach ($required_jobs as $job) {
|
||||
// Extraction du chemin du script pour éviter les doublons
|
||||
preg_match('/\/bin\/([a-z0-9_-]+\.(php|sh))/i', $job, $matches);
|
||||
$script_path = $matches[0] ?? "";
|
||||
|
||||
$found = false;
|
||||
foreach ($new_lines as $line) {
|
||||
if (strpos($line, $script_path) !== false) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found && !empty($script_path)) {
|
||||
$new_lines[] = $job;
|
||||
$has_changed = true;
|
||||
echo "\e[32m[OK]\e[0m Ajout au cron : " . basename($script_path) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// --- PHASE C : Application ---
|
||||
if ($has_changed) {
|
||||
$content = implode("\n", array_filter($new_lines, 'trim')) . "\n";
|
||||
$tmp_cron = tempnam(sys_get_temp_dir(), 'cron');
|
||||
file_put_contents($tmp_cron, $content);
|
||||
exec("crontab " . escapeshellarg($tmp_cron));
|
||||
unlink($tmp_cron);
|
||||
echo "\e[32m[OK]\e[0m Crontab synchronisé.\n";
|
||||
} else {
|
||||
echo "[INFO] Crontab déjà à jour.\n";
|
||||
}
|
||||
}
|
||||
|
||||
function ensure_local_configs() {
|
||||
global $MONITORING_CONF_DIR;
|
||||
$configs = [
|
||||
'monitoring.conf.php' => 'monitoring.local.conf.php',
|
||||
'alert-engine.conf.php' => 'alert-engine.conf.local.php',
|
||||
'autoupdate.conf.php' => 'autoupdate.local.conf.php'
|
||||
];
|
||||
foreach ($configs as $src => $dst) {
|
||||
$dst_path = $MONITORING_CONF_DIR . '/' . $dst;
|
||||
if (!file_exists($dst_path)) {
|
||||
$src_path = $MONITORING_CONF_DIR . '/' . $src;
|
||||
if (file_exists($src_path)) {
|
||||
copy($src_path, $dst_path);
|
||||
chmod($dst_path, 0600);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
168
servers/linux/monitoring/bin/monitoring.php
Executable file
168
servers/linux/monitoring/bin/monitoring.php
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Monitoring Update Engine - PHP Version
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||
|
||||
// --- Configuration ---
|
||||
// Note : La lib a déjà chargé $CONFIG['UPDATE_BASE_URL'] etc. depuis monitoring.local.conf.php
|
||||
// On ne charge les fichiers spécifiques que s'ils apportent des règles de mise à jour uniques.
|
||||
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 (fallback si absent de la config globale)
|
||||
$UPDATE_ENABLED = $CONFIG['UPDATE_ENABLED'] ?? true;
|
||||
$UPDATE_TMP_DIR = $CONFIG['UPDATE_TMP_DIR'] ?? '/tmp/monitoring-update';
|
||||
$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;
|
||||
$MONITORING_BASE_DIR = $MONITORING_BASE_DIR; // Provient de la lib
|
||||
|
||||
// --- Initialisation ---
|
||||
lock_or_exit("monitoring-update");
|
||||
|
||||
if (!$UPDATE_ENABLED) {
|
||||
log_notice("update_disabled", "Mise à jour désactivée");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (!is_dir($UPDATE_TMP_DIR)) {
|
||||
mkdir($UPDATE_TMP_DIR, 0755, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Télécharge et valide le manifeste
|
||||
*/
|
||||
function fetch_manifest($url) {
|
||||
global $UPDATE_TIMEOUT_TOTAL;
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => $UPDATE_TIMEOUT_TOTAL,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_FOLLOWLOCATION => true
|
||||
]);
|
||||
|
||||
$content = curl_exec($ch);
|
||||
if (curl_errno($ch)) {
|
||||
log_error("manifest_download_failed", "Échec téléchargement manifeste", ["url" => $url, "err" => curl_error($ch)]);
|
||||
return false;
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
$manifest_entries = [];
|
||||
$lines = explode("\n", trim($content));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^([0-9a-fA-F]{64})\s+(644|755)\s+((bin|lib|conf)\/[A-Za-z0-9._\/-]+)$/', trim($line), $matches)) {
|
||||
$manifest_entries[] = ['hash' => $matches[1], 'mode' => $matches[2], 'path' => $matches[3]];
|
||||
}
|
||||
}
|
||||
return $manifest_entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un fichier
|
||||
*/
|
||||
function update_one_file($entry) {
|
||||
global $MONITORING_BASE_DIR, $UPDATE_BASE_URL, $UPDATE_TMP_DIR, $UPDATE_TIMEOUT_TOTAL;
|
||||
|
||||
$rel_path = $entry['path'];
|
||||
$target_file = $MONITORING_BASE_DIR . '/' . $rel_path;
|
||||
$remote_url = rtrim($UPDATE_BASE_URL, '/') . '/' . $rel_path;
|
||||
$expected_hash = strtolower($entry['hash']);
|
||||
|
||||
if (file_exists($target_file) && hash_file('sha256', $target_file) === $expected_hash) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$tmp_file = $UPDATE_TMP_DIR . '/' . basename($rel_path) . '.' . uniqid();
|
||||
$ch = curl_init($remote_url);
|
||||
$fp = fopen($tmp_file, 'wb');
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_FILE => $fp,
|
||||
CURLOPT_TIMEOUT => $UPDATE_TIMEOUT_TOTAL,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_FOLLOWLOCATION => true
|
||||
]);
|
||||
|
||||
$success = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
fclose($fp);
|
||||
|
||||
if (!$success || hash_file('sha256', $tmp_file) !== $expected_hash) {
|
||||
log_error("update_failed", "Fichier invalide ou corrompu", ["file" => $rel_path]);
|
||||
@unlink($tmp_file);
|
||||
return false;
|
||||
}
|
||||
|
||||
ensure_parent_dir($target_file);
|
||||
chmod($tmp_file, octdec($entry['mode']));
|
||||
safe_mv($tmp_file, $target_file); // Utilise la fonction safe_mv de ta lib
|
||||
|
||||
log_notice("file_updated", "Mise à jour appliquée", ["file" => $rel_path]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoyage
|
||||
*/
|
||||
function delete_extra_files($remote_files) {
|
||||
global $UPDATE_ALLOW_DELETE, $MONITORING_BASE_DIR, $SCRIPT_PATH;
|
||||
if (!$UPDATE_ALLOW_DELETE) return;
|
||||
|
||||
foreach (['bin', 'lib', 'conf'] as $dir) {
|
||||
$full_path = $MONITORING_BASE_DIR . '/' . $dir;
|
||||
if (!is_dir($full_path)) continue;
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($full_path, RecursiveDirectoryIterator::SKIP_DOTS));
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$path = $file->getPathname();
|
||||
$rel_path = substr($path, strlen($MONITORING_BASE_DIR) + 1);
|
||||
|
||||
// PROTECTIONS
|
||||
if (in_array($rel_path, $remote_files)) continue;
|
||||
if (str_contains($rel_path, '.local.')) continue; // Protection fichiers locaux
|
||||
if ($path === $SCRIPT_PATH) continue; // Ne pas se suicider
|
||||
|
||||
if (@unlink($path)) {
|
||||
log_notice("file_deleted", "Fichier obsolète supprimé", ["file" => $rel_path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Main ---
|
||||
$manifest = fetch_manifest($UPDATE_MANIFEST_URL);
|
||||
if (!$manifest) exit(2);
|
||||
|
||||
$remote_paths = [];
|
||||
$updated = 0; $failed = 0;
|
||||
|
||||
foreach ($manifest as $entry) {
|
||||
$remote_paths[] = $entry['path'];
|
||||
update_one_file($entry) ? $updated++ : $failed++;
|
||||
}
|
||||
|
||||
delete_extra_files($remote_paths);
|
||||
|
||||
if ($failed > 0) {
|
||||
log_warning("update_partial", "Mise à jour terminée avec erreurs", ["failed" => $failed]);
|
||||
} else {
|
||||
log_info("update_ok", "Mise à jour terminée avec succès");
|
||||
}
|
||||
|
||||
exit_with_status();
|
||||
74
servers/linux/monitoring/conf/alert-engine.conf.php
Normal file
74
servers/linux/monitoring/conf/alert-engine.conf.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuration Alert Engine
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
* License: GNU Affero General Public License v3
|
||||
* NE PAS ÉDITER - Utilisez alert-engine.local.conf.php pour vos surcharges
|
||||
* RISQUE D'ECRASEMENT - RISQUE D'EFFACEMENT
|
||||
*/
|
||||
|
||||
// On définit les variables de base (équivalent de l'environnement)
|
||||
$monitoring_state_dir = getenv('MONITORING_STATE_DIR') ?: '/var/lib/monitoring';
|
||||
|
||||
return [
|
||||
// --- Fichiers d'état ---
|
||||
'ALERT_STATE_FILE' => $monitoring_state_dir . '/alert-engine.offset',
|
||||
'ALERT_DEDUP_FILE' => $monitoring_state_dir . '/alert-engine.dedup',
|
||||
|
||||
// --- Activation des canaux ---
|
||||
'ALERT_NTFY_ENABLED' => true,
|
||||
'ALERT_MAIL_ENABLED' => true,
|
||||
|
||||
// --- Configuration Mail ---
|
||||
'ALERT_MAIL_BIN' => '/usr/sbin/sendmail',
|
||||
'ALERT_MAIL_SUBJECT_PREFIX' => '[monitoring]',
|
||||
'DEST' => 'admin@example.com', // N'oubliez pas de définir le destinataire
|
||||
|
||||
// --- Déduplication ---
|
||||
'ALERT_DEDUP_WINDOW' => 3600, // en secondes
|
||||
|
||||
// --- Événements à ignorer ---
|
||||
'ALERT_IGNORE_EVENTS' => [
|
||||
'update_not_needed',
|
||||
'alert_sent_ntfy',
|
||||
'alert_sent_mail'
|
||||
],
|
||||
|
||||
// --- Canaux par défaut selon le niveau ---
|
||||
'DEFAULT_CHANNELS' => [
|
||||
'INFO' => 'ntfy',
|
||||
'NOTICE' => 'ntfy',
|
||||
'WARNING' => 'ntfy',
|
||||
'ERROR' => 'ntfy,mail',
|
||||
'CRITICAL' => 'ntfy,mail',
|
||||
],
|
||||
|
||||
// --- Tags : Une icône pour chaque état possible ---
|
||||
'NTFY_TAGS' => [
|
||||
'DEBUG' => 'gear', // ⚙️
|
||||
'INFO' => 'information_source', // ℹ️
|
||||
'NOTICE' => 'bell', // 🔔
|
||||
'SUCCESS' => 'white_check_mark', // ✅
|
||||
'WARNING' => 'warning', // ⚠️
|
||||
'ERROR' => 'rotating_light,warning', // 🚨
|
||||
'CRITICAL' => 'skull,warning', // 💀
|
||||
'ALERT' => 'ambulance,rotating_light',// 🚑
|
||||
'EMERGENCY' => 'fire,sos,skull', // 🔥
|
||||
'AUDIT' => 'mag', // 🔍
|
||||
],
|
||||
|
||||
// --- Règles spécifiques par événement ---
|
||||
// Si un événement est listé ici, il outrepasse les DEFAULT_CHANNELS
|
||||
'RULES' => [
|
||||
'disk_usage_high' => 'ntfy',
|
||||
'disk_usage_critical' => 'ntfy,mail',
|
||||
'check_failed' => 'ntfy,mail',
|
||||
'internal_error' => 'ntfy,mail',
|
||||
'update_hash_unavailable' => 'ntfy',
|
||||
'update_download_failed' => 'ntfy,mail',
|
||||
'update_hash_mismatch' => 'ntfy,mail',
|
||||
'manifest_download_failed' => 'ntfy,mail',
|
||||
'manifest_invalid' => 'ntfy,mail',
|
||||
'update_finished_with_errors' => 'ntfy,mail',
|
||||
],
|
||||
];
|
||||
63
servers/linux/monitoring/conf/monitoring.conf.php
Normal file
63
servers/linux/monitoring/conf/monitoring.conf.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuration globale par défaut
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
* License: GNU Affero General Public License v3
|
||||
* NE PAS ÉDITER - Utilisez monitoring.local.conf.php pour vos surcharges
|
||||
* RISQUE D'ECRASEMENT - RISQUE D'EFFACEMENT
|
||||
*/
|
||||
|
||||
// Détection dynamique du hostname (équivalent de $(hostname -f))
|
||||
$hostname_fqdn = getenv('HOSTNAME_FQDN') ?: (gethostname() ?: 'localhost');
|
||||
|
||||
// On définit les répertoires de base
|
||||
$monitoring_base = '/opt/monitoring';
|
||||
$monitoring_log_dir = '/var/log/monitoring';
|
||||
$monitoring_state_dir = '/var/lib/monitoring';
|
||||
$monitoring_lock_dir = '/var/lock/monitoring';
|
||||
|
||||
// Initialisation automatique des répertoires (équivalent du mkdir -p à la fin du bash)
|
||||
foreach ([$monitoring_log_dir, $monitoring_state_dir, $monitoring_lock_dir] as $dir) {
|
||||
if (!is_dir($dir)) {
|
||||
@mkdir($dir, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
// --- Chemins ---
|
||||
'MONITORING_BASE' => $monitoring_base,
|
||||
'MONITORING_LOG_DIR' => $monitoring_log_dir,
|
||||
'MONITORING_STATE_DIR' => $monitoring_state_dir,
|
||||
'MONITORING_LOCK_DIR' => $monitoring_lock_dir,
|
||||
'LOG_FILE' => $monitoring_log_dir . '/events.jsonl',
|
||||
|
||||
// --- Identification ---
|
||||
'HOSTNAME_FQDN' => $hostname_fqdn,
|
||||
'DEST' => 'root',
|
||||
|
||||
// --- ntfy ---
|
||||
'NTFY_SERVER' => 'https://ntfy.sh', // Correction du nfy.sh en ntfy.sh
|
||||
'NTFY_TOPIC' => 'TPOSOB84sBJ6HTZ7',
|
||||
'NTFY_TOKEN' => '',
|
||||
|
||||
// --- Mises à jour (Update) ---
|
||||
'UPDATE_ENABLED' => true,
|
||||
'UPDATE_BASE_URL' => 'https://git.abonnel.fr/cedricAbonnel/scripts-bash/raw/branch/main/servers/linux/monitoring',
|
||||
'UPDATE_MANIFEST_URL' => 'https://git.abonnel.fr/cedricAbonnel/scripts-bash/raw/branch/main/servers/linux/monitoring/manifest.txt',
|
||||
'UPDATE_TIMEOUT_CONNECT' => 3,
|
||||
'UPDATE_TIMEOUT_TOTAL' => 15,
|
||||
'UPDATE_TMP_DIR' => '/tmp/monitoring-update',
|
||||
'UPDATE_ALLOW_DELETE' => false,
|
||||
|
||||
// --- Logs ---
|
||||
'LOG_LEVEL' => 'INFO', // DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL
|
||||
|
||||
'STATE_DIR' => '/var/lib/monitoring',
|
||||
'INSTALLED_LOG' => '/var/lib/monitoring/installed-files.log',
|
||||
'CRON_JOBS' => [
|
||||
"*/5 * * * * bash {BASE_DIR}/bin/check_disk.sh > /dev/null 2>&1",
|
||||
"*/15 * * * * bash {BASE_DIR}/bin/check_smart.sh > /dev/null 2>&1",
|
||||
"10 3 * * * php {BASE_DIR}/bin/monitoring-update.php > /dev/null 2>&1",
|
||||
"* * * * * php {BASE_DIR}/bin/alert-engine.php > /dev/null 2>&1"
|
||||
],
|
||||
];
|
||||
166
servers/linux/monitoring/lib/monitoring-lib.php
Normal file
166
servers/linux/monitoring/lib/monitoring-lib.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Monitoring Library - PHP Version
|
||||
* Copyright (C) 2026 Cédric Abonnel
|
||||
* License: GNU Affero General Public License v3
|
||||
*/
|
||||
|
||||
// --- Chemins et Constantes ---
|
||||
$MONITORING_LIB_DIR = __DIR__;
|
||||
$MONITORING_BASE_DIR = dirname($MONITORING_LIB_DIR);
|
||||
$MONITORING_CONF_DIR = $MONITORING_BASE_DIR . '/conf';
|
||||
|
||||
// États globaux
|
||||
$STATUS_OK = 0;
|
||||
$STATUS_WARNING = 1;
|
||||
$STATUS_ERROR = 2;
|
||||
$STATUS_INTERNAL = 3;
|
||||
|
||||
$CURRENT_STATUS = $STATUS_OK;
|
||||
|
||||
// --- Chargement de la Config ---
|
||||
$CONFIG = [
|
||||
'LOG_FILE' => '/var/log/monitoring/events.jsonl',
|
||||
'MONITORING_LOCK_DIR' => '/var/lock/monitoring',
|
||||
'LOG_LEVEL' => 'INFO'
|
||||
];
|
||||
|
||||
// 1. On charge la configuration GLOBALE (La vérité est ici)
|
||||
$global_conf = $MONITORING_CONF_DIR . "/monitoring.local.conf.php";
|
||||
if (file_exists($global_conf)) {
|
||||
$CONFIG = array_replace_recursive($CONFIG, include $global_conf);
|
||||
}
|
||||
|
||||
// 2. On charge ensuite la config spécifique au script (si besoin de surcharger)
|
||||
// $specific_conf est défini par le script qui appelle la lib
|
||||
if (isset($specific_conf) && file_exists($specific_conf)) {
|
||||
$CONFIG = array_replace_recursive($CONFIG, include $specific_conf);
|
||||
}
|
||||
|
||||
// Variables d'exécution
|
||||
$SCRIPT_NAME = basename($_SERVER['SCRIPT_FILENAME'] ?? $argv[0]);
|
||||
$SCRIPT_PATH = realpath($_SERVER['SCRIPT_FILENAME'] ?? $argv[0]);
|
||||
|
||||
// --- Fonctions de Log ---
|
||||
|
||||
/**
|
||||
* Log un événement au format JSONL
|
||||
*/
|
||||
function log_event(string $level, string $event, string $message, array $extra_kv = []) {
|
||||
global $CONFIG, $SCRIPT_NAME;
|
||||
|
||||
$ts = date('c'); // ISO-8601
|
||||
|
||||
// Détection Hostname
|
||||
$host = getenv('HOSTNAME_FQDN') ?: (gethostname() ?: 'unknown');
|
||||
|
||||
$log_data = [
|
||||
"ts" => $ts,
|
||||
"host" => $host,
|
||||
"app" => $SCRIPT_NAME,
|
||||
"level" => $level,
|
||||
"event" => $event,
|
||||
"message" => $message
|
||||
];
|
||||
|
||||
// Fusion des paires clé=valeur supplémentaires
|
||||
foreach ($extra_kv as $kv) {
|
||||
if (strpos($kv, '=') !== false) {
|
||||
list($k, $v) = explode('=', $kv, 2);
|
||||
$log_data[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$json_line = json_encode($log_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$log_file = $CONFIG['LOG_FILE'];
|
||||
ensure_parent_dir($log_file);
|
||||
|
||||
file_put_contents($log_file, $json_line . "\n", FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
function set_status(int $new_status) {
|
||||
global $CURRENT_STATUS;
|
||||
if ($new_status > $CURRENT_STATUS) {
|
||||
$CURRENT_STATUS = $new_status;
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers de logs
|
||||
function log_debug($e, $m, $x = []) { log_event("DEBUG", $e, $m, $x); }
|
||||
function log_info($e, $m, $x = []) { log_event("INFO", $e, $m, $x); }
|
||||
function log_notice($e, $m, $x = []) { log_event("NOTICE", $e, $m, $x); }
|
||||
function log_warning($e, $m, $x = []) { log_event("WARNING", $e, $m, $x); set_status(1); }
|
||||
function log_error($e, $m, $x = []) { log_event("ERROR", $e, $m, $x); set_status(2); }
|
||||
function log_critical($e, $m, $x = []) { log_event("CRITICAL", $e, $m, $x); set_status(2); }
|
||||
|
||||
function fail_internal(string $msg) {
|
||||
log_event("ERROR", "internal_error", $msg);
|
||||
exit(3); // STATUS_INTERNAL
|
||||
}
|
||||
|
||||
function exit_with_status() {
|
||||
global $CURRENT_STATUS;
|
||||
exit($CURRENT_STATUS);
|
||||
}
|
||||
|
||||
// --- Utilitaires Système ---
|
||||
|
||||
/**
|
||||
* Vérifie la présence de commandes système
|
||||
*/
|
||||
function require_cmd(...$cmds) {
|
||||
foreach ($cmds as $cmd) {
|
||||
$output = [];
|
||||
$res = 0;
|
||||
exec("command -v " . escapeshellarg($cmd), $output, $res);
|
||||
if ($res !== 0) {
|
||||
fail_internal("Commande requise absente: $cmd");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestion du verrouillage (Lock)
|
||||
*/
|
||||
function lock_or_exit(?string $lock_name = null) {
|
||||
global $CONFIG, $SCRIPT_NAME;
|
||||
$name = $lock_name ?: $SCRIPT_NAME;
|
||||
$lock_file = ($CONFIG['MONITORING_LOCK_DIR'] ?? '/var/lock/monitoring') . "/{$name}.lock";
|
||||
|
||||
ensure_parent_dir($lock_file);
|
||||
$fp = fopen($lock_file, "w+");
|
||||
|
||||
if (!$fp || !flock($fp, LOCK_EX | LOCK_NB)) {
|
||||
log_notice("already_running", "Une autre instance est déjà en cours", ["lock=$lock_file"]);
|
||||
exit(0);
|
||||
}
|
||||
// On garde le descripteur ouvert pour maintenir le lock
|
||||
return $fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Évalue un niveau selon des seuils
|
||||
*/
|
||||
function threshold_level($value, $warning, $critical) {
|
||||
if ($value >= $critical) return 'CRITICAL';
|
||||
if ($value >= $warning) return 'WARNING';
|
||||
return 'INFO';
|
||||
}
|
||||
|
||||
function ensure_parent_dir(string $file) {
|
||||
$dir = dirname($file);
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
// Ici on ne peut pas appeler log_event si c'est le répertoire de log qui échoue
|
||||
error_log("Impossible de créer le répertoire : $dir");
|
||||
exit(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safe_mv(string $src, string $dst) {
|
||||
if (!rename($src, $dst)) {
|
||||
fail_internal("Échec du déplacement de $src vers $dst");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
f433b3e2ca25c76cccebf971072255dae64169a8ae162d6baa10776904d733e9 755 bin/alert-engine.sh
|
||||
7ff2eb1163ca8b9aa3927ac7f0ebbcc1f90c944e51afbc880d57359b83a0c73f 755 bin/check_disk.sh
|
||||
4fae83b48dc25c5e2a59bba944d8c3f2c6dff89bf2adb932d4dd9201f6305ca4 755 bin/install-monitoring.sh
|
||||
36528963f2e78a160738a2cf3b8da67b9d12dbe495d9d01ca6c1ba97956288fa 755 bin/monitoring.sh
|
||||
78ccebfd1da7cf885fddb8d5a967c23e379c495d8f43490584ace7133690ec55 755 bin/monitoring-update.sh
|
||||
54eb520360c80b3146c5cdb846330a8743cbeb9fe6de0559357114b92d090c29 755 bin/monitor-update-config.sh
|
||||
83db39c8d0cfd6f6e9d3cc5b961a67db29dc73666304a91e0d4a6d5831c623cb 644 conf/alert-engine.conf
|
||||
caaa8f6031d66bc43a897ac2804124ce2050a64523734195d5505ae863836bf4 644 conf/monitoring.conf
|
||||
654cd98ecda1c485a0ea1224f160a3c4d7396ab95a491603574e2ad1981fe010 644 lib/monitoring-lib.sh
|
||||
5b4ea784d2cbe73f6e829e35f23b0b4dbe12df55cc1abc8eba6602da36c724ef 755 bin/alert-engine.php
|
||||
fdcea6720186795538f48c08b99103b320273dbdd0ea5246a2da9d81a1eecc6c 755 bin/check_disk.sh
|
||||
ead10d3be3aac48c6406a734dee1bddf9a8abb1e21de102ce72fa92fdecbaf22 755 bin/check_smart.sh
|
||||
8f95824b568b5de7dbdc2d6ab87fc6fd8076dcb8ad20de3e72a53391e97f8484 755 bin/install-monitoring.sh
|
||||
97a91b13b0776acb3326010821ffcc163e96a97e3c326ea77f11efdb7baf159a 755 bin/log-cli.php
|
||||
02bd43ed2a9b92acc013274c716e6bc50120a8103ccf3d9c4e6f345a0b22d6a0 755 bin/monitoring.php
|
||||
97d407d75a26bd2ebbb86a2e5f8dab8b24639e8a9164f42bd554ba7728ab8cb5 755 bin/monitoring-update-config.php
|
||||
910a7c3a4423fb5456233d0c6cbcfc7f511c94947580972c42286762142e5ce6 755 bin/monitoring-update.php
|
||||
dc70c1184da4aa32eebdeaee57cfed23e91397c94a6243e0ac8664968078f0c7 644 conf/alert-engine.conf.php
|
||||
324038d28f24f3f4d1f6def73752ff703d4ce8b532a663c6628611923748b1f5 644 conf/monitoring.conf.php
|
||||
9bb7f5438edc5fb6a5b899ee21be2a5a559eb0697a028a4e991fc82362eaa460 644 lib/monitoring-lib.php
|
||||
|
||||
@@ -250,8 +250,6 @@ Niveau: $level
|
||||
Message:
|
||||
$message
|
||||
|
||||
Ligne brute:
|
||||
$line
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# Moteur de mise à jour des programmes et fichiers connexes
|
||||
|
||||
set -u
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# lit le fichier log
|
||||
|
||||
set -u
|
||||
|
||||
Reference in New Issue
Block a user