Compare commits
29 Commits
ae0c8f95cb
...
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 |
@@ -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`).
|
* **Moteur PHP & Sondes Hybrides :** Traitement performant des alertes en PHP, tout en gardant des sondes système simples (Bash ou PHP).
|
||||||
* **Logs JSONL :** Centralisation de tous les événements dans `/var/log/monitoring/events.jsonl` pour une analyse facile.
|
* **Alertes Intelligentes :** Envoi via **ntfy** (avec tags et priorités) ou **email**, incluant un système de **déduplication** pour éviter le spam.
|
||||||
* **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.
|
* **Logs JSONL :** Centralisation au format standard `JSON Lines` dans `/var/log/monitoring/events.jsonl` pour une exploitation facile.
|
||||||
* **Auto-update :** Capacité de se mettre à jour automatiquement depuis un dépôt Git via un manifeste.
|
* **Configuration en cascade :** Système de fichiers `.local.conf.php` pour protéger vos réglages lors des mises à jour.
|
||||||
* **Robuste :** Gestion des verrous (locks) pour éviter que deux instances d'un même script ne tournent en même temps.
|
* **Auto-update & Audit :** Mise à jour automatique via manifeste et script d'audit pour détecter les nouvelles options de configuration manquantes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 Installation
|
## 📦 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
|
```bash
|
||||||
# Passer en root
|
# 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/bin/` : Exécutables (sondes, moteur `alert-engine.php`, updater).
|
||||||
* `/opt/monitoring/lib/` : Bibliothèque commune (`monitoring-lib.sh`).
|
* `/opt/monitoring/lib/` : Bibliothèque partagée (`monitoring-lib.php`).
|
||||||
* `/opt/monitoring/conf/` : Fichiers de configuration.
|
* `/opt/monitoring/conf/` : Fichiers de configuration PHP.
|
||||||
* `/var/log/monitoring/` : Logs des événements.
|
* `/var/log/monitoring/` : Journal des événements (`events.jsonl`).
|
||||||
* `/var/lib/monitoring/` : États (offsets de lecture, déduplication).
|
* `/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)
|
### 1. Alertes (ntfy / Mail)
|
||||||
|
|
||||||
Éditez le fichier local (prioritaire sur la config par défaut) :
|
Ne modifiez pas les fichiers `.conf.php` (risques d'écrasement). Créez vos fichiers locaux :
|
||||||
`nano /opt/monitoring/conf/alert-engine.local.conf`
|
`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_TOKEN` & `NTFY_TOPIC`.
|
||||||
* `NTFY_TOPIC` : Le nom de votre topic.
|
* `DEST` (votre email de réception).
|
||||||
* `DEST` : L'adresse 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 :
|
||||||
|
|
||||||
---
|
```bash
|
||||||
|
php /opt/monitoring/bin/monitoring-update-config.php
|
||||||
## 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
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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`
|
Le système est conçu pour être piloté par `cron`. Voici la configuration recommandée :
|
||||||
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.
|
|
||||||
|
|
||||||
|
| 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.*
|
||||||
|
|||||||
87
servers/linux/monitoring/bin/alert-engine.php
Normal file → Executable file
87
servers/linux/monitoring/bin/alert-engine.php
Normal file → Executable file
@@ -9,7 +9,7 @@
|
|||||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||||
|
|
||||||
// --- Initialisation de la configuration spécifique ---
|
// --- Initialisation de la configuration spécifique ---
|
||||||
// On charge les fichiers de conf s'ils existent (format PHP attendu)
|
// 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) {
|
foreach (["/opt/monitoring/conf/alert-engine.conf.php", "/opt/monitoring/conf/alert-engine.conf.local.php"] as $conf) {
|
||||||
if (file_exists($conf)) {
|
if (file_exists($conf)) {
|
||||||
$extra_conf = include $conf;
|
$extra_conf = include $conf;
|
||||||
@@ -30,50 +30,97 @@ ensure_parent_dir($STATE_FILE);
|
|||||||
ensure_parent_dir($DEDUP_FILE);
|
ensure_parent_dir($DEDUP_FILE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nettoyage du fichier de déduplication (entrées expirées)
|
* 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() {
|
function cleanup_dedup_file() {
|
||||||
global $DEDUP_FILE, $DEDUP_WINDOW;
|
global $DEDUP_FILE, $DEDUP_WINDOW;
|
||||||
if (!file_exists($DEDUP_FILE)) return;
|
|
||||||
|
// Si le fichier n'existe pas, rien à nettoyer
|
||||||
|
if (!file_exists($DEDUP_FILE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$now = time();
|
$now = time();
|
||||||
$lines = file($DEDUP_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
||||||
$kept = [];
|
$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) {
|
foreach ($lines as $line) {
|
||||||
$parts = explode('|', $line);
|
$parts = explode('|', $line);
|
||||||
if (count($parts) >= 2 && ($now - (int)$parts[1]) <= $DEDUP_WINDOW) {
|
|
||||||
$kept[] = $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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_put_contents($DEDUP_FILE, implode("\n", $kept) . (empty($kept) ? "" : "\n"));
|
|
||||||
|
// 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)
|
* Vérifie si une alerte doit être envoyée (Déduplication)
|
||||||
|
* La clé attendue est : "hostname|app|level|event"
|
||||||
*/
|
*/
|
||||||
function should_notify_dedup($key) {
|
function should_notify_dedup(string $key): bool {
|
||||||
global $DEDUP_FILE, $DEDUP_WINDOW;
|
global $DEDUP_FILE, $DEDUP_WINDOW;
|
||||||
if (!file_exists($DEDUP_FILE)) return true;
|
|
||||||
|
if (!file_exists($DEDUP_FILE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
$now = time();
|
$now = time();
|
||||||
$last_ts = 0;
|
$last_ts = 0;
|
||||||
|
|
||||||
$handle = fopen($DEDUP_FILE, 'r');
|
$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) {
|
while (($line = fgets($handle)) !== false) {
|
||||||
$p = explode('|', trim($line));
|
$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) {
|
if (count($p) >= 5) {
|
||||||
$current_key = "{$p[0]}|{$p[2]}|{$p[3]}|{$p[4]}";
|
$row_key = "{$p[0]}|{$p[2]}|{$p[3]}|{$p[4]}";
|
||||||
if ($current_key === $key) {
|
|
||||||
|
if ($row_key === $key) {
|
||||||
$last_ts = (int)$p[1];
|
$last_ts = (int)$p[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
|
||||||
return ($now - $last_ts) >= $DEDUP_WINDOW;
|
// 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
|
* Envoi vers ntfy
|
||||||
*/
|
*/
|
||||||
@@ -123,26 +170,28 @@ function send_mail($subject, $body) {
|
|||||||
* Traitement d'une ligne de log
|
* Traitement d'une ligne de log
|
||||||
*/
|
*/
|
||||||
function process_line($line) {
|
function process_line($line) {
|
||||||
global $CONFIG, $DEDUP_FILE;
|
global $CONFIG, $DEDUP_FILE;
|
||||||
$data = json_decode($line, true);
|
$data = json_decode($line, true);
|
||||||
if (!$data || !isset($data['level'], $data['event'])) return;
|
if (!$data || !isset($data['level'], $data['event'])) return;
|
||||||
|
|
||||||
$level = strtoupper($data['level']);
|
$level = strtoupper($data['level']);
|
||||||
$event = $data['event'];
|
$event = $data['event'];
|
||||||
|
|
||||||
if (in_array($level, ['DEBUG', 'INFO', 'NOTICE'])) return;
|
// On garde uniquement l'ignore list explicite pour les événements
|
||||||
if (in_array($event, ($CONFIG['ALERT_IGNORE_EVENTS'] ?? []))) return;
|
if (in_array($event, ($CONFIG['ALERT_IGNORE_EVENTS'] ?? []))) return;
|
||||||
|
|
||||||
// Déduplication
|
// Déduplication
|
||||||
$key = "{$data['host']}|{$data['app']}|{$level}|{$event}";
|
$key = "{$data['host']}|{$data['app']}|{$level}|{$event}";
|
||||||
if (!should_notify_dedup($key)) {
|
if (!should_notify_dedup($key)) {
|
||||||
log_debug("alert_suppressed_dedup", "Alerte dédupliquée", ["event=$event", "host={$data['host']}"]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Détermination des canaux (Règle spécifique puis défaut)
|
// Détermination des canaux
|
||||||
$channels_str = $CONFIG['RULES'][$event] ?? $CONFIG['DEFAULT_CHANNELS'][$level] ?? '';
|
$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;
|
if (empty($channels_str)) return;
|
||||||
|
|
||||||
$channels = explode(',', $channels_str);
|
$channels = explode(',', $channels_str);
|
||||||
|
|
||||||
$title = "{$data['host']} [{$data['app']}] $level $event";
|
$title = "{$data['host']} [{$data['app']}] $level $event";
|
||||||
|
|||||||
@@ -11,43 +11,51 @@
|
|||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU Affero General Public License for more details.
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
. /opt/monitoring/lib/monitoring-lib.sh || exit 3
|
# --- Configuration (Seuils par défaut) ---
|
||||||
|
WARNING=80
|
||||||
WARNING=80
|
CRITICAL=95
|
||||||
CRITICAL=95
|
MOUNTS=("/" "/var" "/home")
|
||||||
MOUNTS=("/" "/var" "/home")
|
LOG_BIN="/opt/monitoring/bin/log-cli.php"
|
||||||
|
|
||||||
for mount in "${MOUNTS[@]}"; do
|
# --- Vérification ROOT ---
|
||||||
# On vérifie si le point de montage existe avant de tester
|
if [ "${EUID}" -ne 0 ]; then
|
||||||
if ! mountpoint -q "$mount"; then
|
echo "ERREUR : Ce script doit être exécuté en tant que root." >&2
|
||||||
continue
|
$LOG_BIN ERROR "internal_error" "Tentative d'exécution sans privilèges root."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for mount in "${MOUNTS[@]}"; do
|
||||||
|
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_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
|
fi
|
||||||
|
|
||||||
used_pct="$(df -P "$mount" 2>/dev/null | awk 'NR==2 {gsub("%","",$5); print $5}')"
|
# --- 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}')"
|
||||||
if [[ ! "$used_pct" =~ ^[0-9]+$ ]]; then
|
|
||||||
log_error "check_failed" "Impossible de lire l'utilisation disque" "mount=$mount"
|
if [[ ! "$inode_pct" =~ ^[0-9]+$ ]]; then
|
||||||
continue
|
$LOG_BIN ERROR "check_failed" "Erreur lecture inodes $mount."
|
||||||
fi
|
else
|
||||||
|
if [ "$inode_pct" -ge "$CRITICAL" ]; then
|
||||||
level="$(threshold_level "$used_pct" "$WARNING" "$CRITICAL")"
|
$LOG_BIN CRITICAL "inode_usage_critical" "Inodes $mount critiques ($inode_pct%)."
|
||||||
|
elif [ "$inode_pct" -ge "$WARNING" ]; then
|
||||||
case "$level" in
|
$LOG_BIN WARNING "inode_usage_high" "Inodes $mount élevés ($inode_pct%)."
|
||||||
INFO)
|
else
|
||||||
log_info "disk_ok" "Utilisation disque normale" \
|
$LOG_BIN INFO "inode_ok" "Inodes $mount OK ($inode_pct%)."
|
||||||
"mount=$mount" "used_pct=$used_pct" "warning=$WARNING" "critical=$CRITICAL"
|
fi
|
||||||
;;
|
fi
|
||||||
WARNING)
|
done
|
||||||
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
|
|
||||||
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
|
||||||
@@ -11,51 +11,51 @@ LOG_DIR="/var/log/monitoring"
|
|||||||
STATE_DIR="/var/lib/monitoring"
|
STATE_DIR="/var/lib/monitoring"
|
||||||
LOCK_DIR="/var/lock/monitoring"
|
LOCK_DIR="/var/lock/monitoring"
|
||||||
TMP_DIR="/tmp/monitoring-install"
|
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"
|
UPDATE_BASE_URL="https://git.abonnel.fr/cedricAbonnel/scripts-bash/raw/branch/main/servers/linux/monitoring"
|
||||||
MANIFEST_URL="${UPDATE_BASE_URL}/manifest.txt"
|
MANIFEST_URL="${UPDATE_BASE_URL}/manifest.txt"
|
||||||
|
|
||||||
INSTALL_DEPS="${INSTALL_DEPS:-true}"
|
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() {
|
require_root() {
|
||||||
if [ "${EUID}" -ne 0 ]; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_deps() {
|
install_deps() {
|
||||||
if [ "${INSTALL_DEPS}" != "true" ]; then
|
if [ "${INSTALL_DEPS}" != "true" ]; then return 0; fi
|
||||||
return 0
|
info "Vérification des dépendances système..."
|
||||||
fi
|
|
||||||
|
|
||||||
echo "--- Installation des dépendances ---"
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
apt-get update
|
apt-get update -qq
|
||||||
apt-get install -y curl coreutils findutils grep sed gawk util-linux ca-certificates
|
apt-get install -y -qq curl coreutils findutils grep sed gawk util-linux ca-certificates php-cli php-curl php-common smartmontools > /dev/null
|
||||||
# Ajout des modules PHP nécessaires pour vos scripts (curl pour ntfy)
|
ok "Dépendances installées."
|
||||||
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."
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare_dirs() {
|
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}"
|
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}"
|
touch "$INSTALLED_LOG"
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_manifest() {
|
fetch_manifest() {
|
||||||
echo "--- Récupération du manifeste ---"
|
info "Téléchargement du manifeste distant..."
|
||||||
curl -fsS "${MANIFEST_URL}" -o "${TMP_DIR}/manifest.txt"
|
curl -fsS "${MANIFEST_URL}" -o "${TMP_DIR}/manifest.txt"
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_manifest() {
|
validate_manifest() {
|
||||||
# Validation du format : Hash Mode Chemin
|
|
||||||
# Exemple : a1b2... 755 bin/script.php
|
|
||||||
awk '
|
awk '
|
||||||
NF == 3 &&
|
NF == 3 &&
|
||||||
$1 ~ /^[0-9a-fA-F]{64}$/ &&
|
$1 ~ /^[0-9a-fA-F]{64}$/ &&
|
||||||
@@ -65,35 +65,32 @@ validate_manifest() {
|
|||||||
' "${TMP_DIR}/manifest.txt"
|
' "${TMP_DIR}/manifest.txt"
|
||||||
}
|
}
|
||||||
|
|
||||||
download_one() {
|
download_and_install() {
|
||||||
local expected_hash="$1"
|
local expected_hash=$1 mode=$2 rel_path=$3
|
||||||
local mode="$2"
|
|
||||||
local rel_path="$3"
|
|
||||||
|
|
||||||
local url="${UPDATE_BASE_URL}/${rel_path}"
|
|
||||||
local dst="${BASE_DIR}/${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 [ -f "$dst" ]; then
|
||||||
if [[ "$rel_path" == conf/* ]] && [ -f "$dst" ]; then
|
local current_hash
|
||||||
echo "Skip: $rel_path (existe déjà)"
|
current_hash=$(sha256sum "$dst" | awk '{print $1}')
|
||||||
return 0
|
[ "$current_hash" == "$expected_hash" ] && return 0
|
||||||
|
info "Mise à jour : $rel_path"
|
||||||
|
else
|
||||||
|
info "Installation : $rel_path"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Téléchargement: $rel_path"
|
|
||||||
local tmp_file
|
local tmp_file
|
||||||
tmp_file="$(mktemp "${TMP_DIR}/file.XXXXXX")"
|
tmp_file="$(mktemp "${TMP_DIR}/file.XXXXXX")"
|
||||||
|
|
||||||
if ! curl -fsS "$url" -o "$tmp_file"; then
|
if ! curl -fsS "${UPDATE_BASE_URL}/${rel_path}" -o "$tmp_file"; then
|
||||||
echo "ERREUR: Échec du téléchargement de ${url}" >&2
|
err "Échec du téléchargement pour $rel_path"
|
||||||
rm -f "$tmp_file"
|
rm -f "$tmp_file"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local got_hash
|
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
|
if [ "$got_hash" != "$expected_hash" ]; then
|
||||||
echo "ERREUR: Hash invalide pour ${rel_path}" >&2
|
err "Hash invalide pour $rel_path"
|
||||||
rm -f "$tmp_file"
|
rm -f "$tmp_file"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -103,34 +100,45 @@ download_one() {
|
|||||||
chmod "$mode" "$dst"
|
chmod "$mode" "$dst"
|
||||||
}
|
}
|
||||||
|
|
||||||
install_from_manifest() {
|
# --- NOUVEAUTÉ : Gestion du journal et purge propre ---
|
||||||
echo "--- Installation des fichiers ---"
|
|
||||||
while read -r hash mode rel_path; do
|
update_installed_log() {
|
||||||
[ -n "${hash:-}" ] || continue
|
# On sauvegarde la liste des chemins relatifs du manifeste validé dans le journal permanent
|
||||||
download_one "$hash" "$mode" "$rel_path"
|
awk '{print $3}' "${TMP_DIR}/manifest-valid.txt" > "$INSTALLED_LOG"
|
||||||
done < "${TMP_DIR}/manifest-valid.txt"
|
ok "Journal des fichiers déployés mis à jour ($INSTALLED_LOG)."
|
||||||
}
|
}
|
||||||
|
|
||||||
show_next_steps() {
|
purge_obsolete_files() {
|
||||||
cat <<EOF
|
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
|
||||||
|
|
||||||
Installation terminée avec succès dans ${BASE_DIR}.
|
# Mode Journal : On lit l'ancien journal pour voir ce qui doit disparaître
|
||||||
|
while read -r old_file; do
|
||||||
Étapes suivantes :
|
# Si le fichier du journal n'est plus dans le nouveau manifeste
|
||||||
1. Configurez vos alertes :
|
if ! grep -qw "$old_file" "${TMP_DIR}/manifest-valid.txt"; then
|
||||||
cp ${CONF_DIR}/alert-engine.conf.php ${CONF_DIR}/alert-engine.local.conf.php
|
if [ -f "${BASE_DIR}/$old_file" ]; then
|
||||||
nano ${CONF_DIR}/alert-engine.local.conf.php
|
# Protection ultime des fichiers .local (au cas où ils auraient été loggués par erreur)
|
||||||
|
if [[ "$old_file" != *".local."* ]]; then
|
||||||
2. Initialisez la configuration globale :
|
warn "Suppression du fichier obsolète : $old_file"
|
||||||
cp ${CONF_DIR}/monitoring.conf.php ${CONF_DIR}/monitoring.local.conf.php
|
rm -f "${BASE_DIR}/$old_file"
|
||||||
|
fi
|
||||||
3. Lancez un audit des configurations :
|
fi
|
||||||
php ${BASE_DIR}/bin/monitoring-update-config.php
|
fi
|
||||||
|
done < "$INSTALLED_LOG"
|
||||||
4. Planifiez les tâches (cron) :
|
|
||||||
*/5 * * * * php ${BASE_DIR}/bin/alert-engine.php
|
|
||||||
10 3 * * * php ${BASE_DIR}/bin/monitoring-update.php
|
|
||||||
EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Main ---
|
# --- Main ---
|
||||||
@@ -142,16 +150,38 @@ main() {
|
|||||||
fetch_manifest
|
fetch_manifest
|
||||||
|
|
||||||
if ! validate_manifest > "${TMP_DIR}/manifest-valid.txt"; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install_from_manifest
|
echo "--------------------------------------------------"
|
||||||
|
info "Phase 1 : Installation et mises à jour"
|
||||||
# Nettoyage
|
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}"
|
rm -rf "${TMP_DIR}"
|
||||||
|
echo "--------------------------------------------------"
|
||||||
|
ok "Opération terminée avec succès."
|
||||||
|
|
||||||
show_next_steps
|
# --- 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 "$@"
|
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);
|
||||||
0
servers/linux/monitoring/bin/monitoring-update-config.php
Normal file → Executable file
0
servers/linux/monitoring/bin/monitoring-update-config.php
Normal file → Executable file
324
servers/linux/monitoring/bin/monitoring-update.php
Normal file → Executable file
324
servers/linux/monitoring/bin/monitoring-update.php
Normal file → Executable file
@@ -1,219 +1,179 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Moteur de mise à jour des programmes
|
* Moteur de mise à jour
|
||||||
|
* Pilotage du script Bash + Initialisation des Configs + Cron + Ménage
|
||||||
* Copyright (C) 2026 Cédric Abonnel
|
* Copyright (C) 2026 Cédric Abonnel
|
||||||
* License: GNU Affero General Public License v3
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||||
|
|
||||||
// --- Chargement de la configuration spécifique ---
|
// Sécurité : Un seul update à la fois
|
||||||
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");
|
lock_or_exit("monitoring-update");
|
||||||
|
|
||||||
if (!$UPDATE_ENABLED) {
|
echo "\e[1m--- Début de la mise à jour système ---\e[0m\n";
|
||||||
log_notice("update_disabled", "Mise à jour désactivée par configuration");
|
|
||||||
exit(0);
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_dir($UPDATE_TMP_DIR)) {
|
// 2. Exécution du moteur de synchronisation Bash
|
||||||
mkdir($UPDATE_TMP_DIR, 0755, true);
|
$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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Télécharge et valide le manifeste
|
* Nettoyage et Mise à jour du Crontab
|
||||||
|
* @param array $deleted_files Liste des chemins de fichiers supprimés du déploiement
|
||||||
*/
|
*/
|
||||||
function fetch_manifest() {
|
function ensure_crontab_entries($deleted_files) {
|
||||||
global $UPDATE_MANIFEST_URL, $UPDATE_TIMEOUT_TOTAL;
|
global $CONFIG, $MONITORING_BASE_DIR;
|
||||||
|
|
||||||
$content = @file_get_contents($UPDATE_MANIFEST_URL, false, stream_context_create([
|
// --- CONFIGURATION DU MÉNAGE STATIQUE ---
|
||||||
'http' => ['timeout' => $UPDATE_TIMEOUT_TOTAL]
|
// 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
|
||||||
|
];
|
||||||
|
|
||||||
if ($content === false) {
|
// Préparation des jobs requis
|
||||||
log_error("manifest_download_failed", "Impossible de télécharger le manifeste", ["url=$UPDATE_MANIFEST_URL"]);
|
$required_jobs = array_map(function($job) use ($MONITORING_BASE_DIR) {
|
||||||
return false;
|
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"
|
||||||
|
]);
|
||||||
|
|
||||||
$lines = explode("\n", trim($content));
|
$current_cron = shell_exec("crontab -l 2>/dev/null") ?: "";
|
||||||
$manifest_data = [];
|
$lines = explode("\n", trim($current_cron));
|
||||||
|
$new_lines = [];
|
||||||
|
$has_changed = false;
|
||||||
|
|
||||||
|
// --- PHASE A : Nettoyage (Dynamique + Statique) ---
|
||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
if (empty(trim($line))) continue;
|
$trim_line = trim($line);
|
||||||
|
if (empty($trim_line)) continue;
|
||||||
$parts = preg_split('/\s+/', trim($line));
|
|
||||||
if (count($parts) !== 3) continue;
|
|
||||||
|
|
||||||
list($hash, $mode, $path) = $parts;
|
$keep = true;
|
||||||
|
|
||||||
// Validation stricte (Regex identique au Bash)
|
// 1. Nettoyage Dynamique (basé sur le log Git)
|
||||||
if (preg_match('/^[0-9a-f]{64}$/i', $hash) &&
|
foreach ($deleted_files as $deleted_path) {
|
||||||
preg_match('/^(644|755)$/', $mode) &&
|
if (strpos($trim_line, $deleted_path) !== false) {
|
||||||
preg_match('/^(bin|lib|conf)\/[A-Za-z0-9._\/-]+$/', $path) &&
|
echo "\e[33m[CLEAN]\e[0m Script supprimé du déploiement : " . basename($deleted_path) . "\n";
|
||||||
strpos($path, '..') === false) {
|
$keep = false;
|
||||||
$manifest_data[] = ['hash' => strtolower($hash), 'mode' => $mode, 'path' => $path];
|
$has_changed = true;
|
||||||
}
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
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())) {
|
// 2. Nettoyage Statique (basé sur ta liste forcée)
|
||||||
log_notice("file_deleted", "Fichier obsolète supprimé", ["file=$rel_path"]);
|
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 ---
|
||||||
* Lancement du script de synchronisation local
|
foreach ($required_jobs as $job) {
|
||||||
*/
|
// Extraction du chemin du script pour éviter les doublons
|
||||||
function run_local_conf_sync() {
|
preg_match('/\/bin\/([a-z0-9_-]+\.(php|sh))/i', $job, $matches);
|
||||||
global $MONITORING_BASE_DIR;
|
$script_path = $matches[0] ?? "";
|
||||||
$sync_script = $MONITORING_BASE_DIR . '/bin/monitoring-update-config.sh'; // On cherche la version PHP
|
|
||||||
|
|
||||||
if (file_exists($sync_script)) {
|
$found = false;
|
||||||
log_info("local_conf_sync_start", "Synchronisation des configs locales");
|
foreach ($new_lines as $line) {
|
||||||
passthru("php " . escapeshellarg($sync_script), $return_var);
|
if (strpos($line, $script_path) !== false) {
|
||||||
if ($return_var !== 0) {
|
$found = true;
|
||||||
log_warning("local_conf_sync_failed", "Échec de synchronisation");
|
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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// --- Exécution principale ---
|
// --- PHASE C : Application ---
|
||||||
|
if ($has_changed) {
|
||||||
$manifest = fetch_manifest();
|
$content = implode("\n", array_filter($new_lines, 'trim')) . "\n";
|
||||||
if (!$manifest) exit(2);
|
$tmp_cron = tempnam(sys_get_temp_dir(), 'cron');
|
||||||
|
file_put_contents($tmp_cron, $content);
|
||||||
$total = count($manifest);
|
exec("crontab " . escapeshellarg($tmp_cron));
|
||||||
$updated = 0;
|
unlink($tmp_cron);
|
||||||
$failed = 0;
|
echo "\e[32m[OK]\e[0m Crontab synchronisé.\n";
|
||||||
$remote_paths = [];
|
|
||||||
|
|
||||||
foreach ($manifest as $item) {
|
|
||||||
$remote_paths[] = $item['path'];
|
|
||||||
if (update_one_file($item['hash'], $item['mode'], $item['path'])) {
|
|
||||||
$updated++;
|
|
||||||
} else {
|
} else {
|
||||||
$failed++;
|
echo "[INFO] Crontab déjà à jour.\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_extra_files($remote_paths);
|
function ensure_local_configs() {
|
||||||
run_local_conf_sync();
|
global $MONITORING_CONF_DIR;
|
||||||
|
$configs = [
|
||||||
if ($failed > 0) {
|
'monitoring.conf.php' => 'monitoring.local.conf.php',
|
||||||
log_warning("update_finished_with_errors", "Mise à jour terminée avec erreurs", ["total=$total", "failed=$failed"]);
|
'alert-engine.conf.php' => 'alert-engine.conf.local.php',
|
||||||
} else {
|
'autoupdate.conf.php' => 'autoupdate.local.conf.php'
|
||||||
log_info("update_finished", "Mise à jour terminée", ["total=$total", "checked=$updated"]);
|
];
|
||||||
}
|
foreach ($configs as $src => $dst) {
|
||||||
|
$dst_path = $MONITORING_CONF_DIR . '/' . $dst;
|
||||||
exit_with_status();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
177
servers/linux/monitoring/bin/monitoring.php
Normal file → Executable file
177
servers/linux/monitoring/bin/monitoring.php
Normal file → Executable file
@@ -8,40 +8,36 @@
|
|||||||
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
require_once __DIR__ . '/../lib/monitoring-lib.php';
|
||||||
|
|
||||||
// --- Configuration ---
|
// --- Configuration ---
|
||||||
// On s'appuie sur le chargement de la lib, mais on surcharge si nécessaire
|
// Note : La lib a déjà chargé $CONFIG['UPDATE_BASE_URL'] etc. depuis monitoring.local.conf.php
|
||||||
$conf_file = "/opt/monitoring/conf/autoupdate.conf.php"; // Format PHP recommandé
|
// On ne charge les fichiers spécifiques que s'ils apportent des règles de mise à jour uniques.
|
||||||
// --- Chargement de la configuration spécifique ---
|
|
||||||
foreach (["/opt/monitoring/conf/autoupdate.conf.php", "/opt/monitoring/conf/autoupdate.local.conf.php"] as $conf) {
|
foreach (["/opt/monitoring/conf/autoupdate.conf.php", "/opt/monitoring/conf/autoupdate.local.conf.php"] as $conf) {
|
||||||
if (file_exists($conf)) {
|
if (file_exists($conf)) {
|
||||||
$extra_conf = include $conf;
|
$extra_conf = include $conf;
|
||||||
if (is_array($extra_conf)) {
|
if (is_array($extra_conf)) {
|
||||||
$CONFIG = array_replace_recursive($CONFIG, $extra_conf);
|
$CONFIG = array_replace_recursive($CONFIG, $extra_conf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variables par défaut
|
// Variables par défaut (fallback si absent de la config globale)
|
||||||
$UPDATE_ENABLED = $CONFIG['UPDATE_ENABLED'] ?? true;
|
$UPDATE_ENABLED = $CONFIG['UPDATE_ENABLED'] ?? true;
|
||||||
$UPDATE_TMP_DIR = $CONFIG['UPDATE_TMP_DIR'] ?? '/tmp/monitoring-update';
|
$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_TIMEOUT_TOTAL = $CONFIG['UPDATE_TIMEOUT_TOTAL'] ?? 15;
|
||||||
$UPDATE_MANIFEST_URL = $CONFIG['UPDATE_MANIFEST_URL'] ?? '';
|
$UPDATE_MANIFEST_URL = $CONFIG['UPDATE_MANIFEST_URL'] ?? '';
|
||||||
$UPDATE_BASE_URL = $CONFIG['UPDATE_BASE_URL'] ?? '';
|
$UPDATE_BASE_URL = $CONFIG['UPDATE_BASE_URL'] ?? '';
|
||||||
$UPDATE_ALLOW_DELETE = $CONFIG['UPDATE_ALLOW_DELETE'] ?? false;
|
$UPDATE_ALLOW_DELETE = $CONFIG['UPDATE_ALLOW_DELETE'] ?? false;
|
||||||
$MONITORING_BASE_DIR = $CONFIG['MONITORING_BASE_DIR'] ?? '/opt/monitoring';
|
$MONITORING_BASE_DIR = $MONITORING_BASE_DIR; // Provient de la lib
|
||||||
|
|
||||||
// --- Initialisation ---
|
// --- Initialisation ---
|
||||||
lock_or_exit("monitoring-update");
|
lock_or_exit("monitoring-update");
|
||||||
|
|
||||||
if (!$UPDATE_ENABLED) {
|
if (!$UPDATE_ENABLED) {
|
||||||
log_notice("update_disabled", "Mise à jour désactivée par configuration");
|
log_notice("update_disabled", "Mise à jour désactivée");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_dir($UPDATE_TMP_DIR)) {
|
if (!is_dir($UPDATE_TMP_DIR)) {
|
||||||
if (!mkdir($UPDATE_TMP_DIR, 0755, true)) {
|
mkdir($UPDATE_TMP_DIR, 0755, true);
|
||||||
fail_internal("Impossible de créer le répertoire temporaire: $UPDATE_TMP_DIR");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,13 +47,16 @@ function fetch_manifest($url) {
|
|||||||
global $UPDATE_TIMEOUT_TOTAL;
|
global $UPDATE_TIMEOUT_TOTAL;
|
||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt_array($ch, [
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, $UPDATE_TIMEOUT_TOTAL);
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
curl_setopt($ch, CURLOPT_FAILONERROR, true);
|
CURLOPT_TIMEOUT => $UPDATE_TIMEOUT_TOTAL,
|
||||||
|
CURLOPT_FAILONERROR => true,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true
|
||||||
|
]);
|
||||||
|
|
||||||
$content = curl_exec($ch);
|
$content = curl_exec($ch);
|
||||||
if (curl_errno($ch)) {
|
if (curl_errno($ch)) {
|
||||||
log_error("manifest_download_failed", "Impossible de télécharger le manifeste", ["url" => $url, "error" => curl_error($ch)]);
|
log_error("manifest_download_failed", "Échec téléchargement manifeste", ["url" => $url, "err" => curl_error($ch)]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
@@ -66,30 +65,15 @@ function fetch_manifest($url) {
|
|||||||
$lines = explode("\n", trim($content));
|
$lines = explode("\n", trim($content));
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
$line = trim($line);
|
if (preg_match('/^([0-9a-fA-F]{64})\s+(644|755)\s+((bin|lib|conf)\/[A-Za-z0-9._\/-]+)$/', trim($line), $matches)) {
|
||||||
if (empty($line)) continue;
|
$manifest_entries[] = ['hash' => $matches[1], 'mode' => $matches[2], 'path' => $matches[3]];
|
||||||
|
|
||||||
// Validation format: hash(64) mode(3) path
|
|
||||||
if (preg_match('/^([0-9a-fA-F]{64})\s+(644|755)\s+((bin|lib|conf)\/[A-Za-z0-9._\/-]+)$/', $line, $matches)) {
|
|
||||||
$manifest_entries[] = [
|
|
||||||
'hash' => $matches[1],
|
|
||||||
'mode' => $matches[2],
|
|
||||||
'path' => $matches[3]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($manifest_entries)) {
|
|
||||||
log_error("manifest_invalid", "Le manifeste distant est invalide ou vide", ["url" => $url]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_info("manifest_downloaded", "Manifeste téléchargé", ["url" => $url]);
|
|
||||||
return $manifest_entries;
|
return $manifest_entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Met à jour un fichier spécifique
|
* Met à jour un fichier
|
||||||
*/
|
*/
|
||||||
function update_one_file($entry) {
|
function update_one_file($entry) {
|
||||||
global $MONITORING_BASE_DIR, $UPDATE_BASE_URL, $UPDATE_TMP_DIR, $UPDATE_TIMEOUT_TOTAL;
|
global $MONITORING_BASE_DIR, $UPDATE_BASE_URL, $UPDATE_TMP_DIR, $UPDATE_TIMEOUT_TOTAL;
|
||||||
@@ -99,129 +83,86 @@ function update_one_file($entry) {
|
|||||||
$remote_url = rtrim($UPDATE_BASE_URL, '/') . '/' . $rel_path;
|
$remote_url = rtrim($UPDATE_BASE_URL, '/') . '/' . $rel_path;
|
||||||
$expected_hash = strtolower($entry['hash']);
|
$expected_hash = strtolower($entry['hash']);
|
||||||
|
|
||||||
// Calcul du hash local actuel
|
if (file_exists($target_file) && hash_file('sha256', $target_file) === $expected_hash) {
|
||||||
$local_hash = file_exists($target_file) ? hash_file('sha256', $target_file) : "";
|
return true;
|
||||||
|
|
||||||
if ($local_hash === $expected_hash) {
|
|
||||||
log_debug("update_not_needed", "Fichier déjà à jour", ["file" => $rel_path]);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Téléchargement
|
|
||||||
$tmp_file = $UPDATE_TMP_DIR . '/' . basename($rel_path) . '.' . uniqid();
|
$tmp_file = $UPDATE_TMP_DIR . '/' . basename($rel_path) . '.' . uniqid();
|
||||||
$ch = curl_init($remote_url);
|
$ch = curl_init($remote_url);
|
||||||
$fp = fopen($tmp_file, 'wb');
|
$fp = fopen($tmp_file, 'wb');
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_FILE, $fp);
|
curl_setopt_array($ch, [
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, $UPDATE_TIMEOUT_TOTAL);
|
CURLOPT_FILE => $fp,
|
||||||
curl_setopt($ch, CURLOPT_FAILONERROR, true);
|
CURLOPT_TIMEOUT => $UPDATE_TIMEOUT_TOTAL,
|
||||||
|
CURLOPT_FAILONERROR => true,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true
|
||||||
|
]);
|
||||||
|
|
||||||
$success = curl_exec($ch);
|
$success = curl_exec($ch);
|
||||||
$error = curl_error($ch);
|
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
fclose($fp);
|
fclose($fp);
|
||||||
|
|
||||||
if (!$success) {
|
if (!$success || hash_file('sha256', $tmp_file) !== $expected_hash) {
|
||||||
log_error("update_download_failed", "Téléchargement impossible", ["file" => $rel_path, "url" => $remote_url, "error" => $error]);
|
log_error("update_failed", "Fichier invalide ou corrompu", ["file" => $rel_path]);
|
||||||
@unlink($tmp_file);
|
@unlink($tmp_file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérification Hash
|
|
||||||
$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($target_file);
|
ensure_parent_dir($target_file);
|
||||||
chmod($tmp_file, octdec($entry['mode']));
|
chmod($tmp_file, octdec($entry['mode']));
|
||||||
|
safe_mv($tmp_file, $target_file); // Utilise la fonction safe_mv de ta lib
|
||||||
if (!rename($tmp_file, $target_file)) {
|
|
||||||
fail_internal("Échec du déplacement de $tmp_file vers $target_file");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($local_hash === "") {
|
|
||||||
log_notice("file_created", "Fichier créé depuis le manifeste", ["file" => $rel_path, "mode" => $entry['mode']]);
|
|
||||||
} else {
|
|
||||||
log_notice("update_applied", "Mise à jour appliquée", ["file" => $rel_path, "old_hash" => $local_hash, "new_hash" => $expected_hash]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
log_notice("file_updated", "Mise à jour appliquée", ["file" => $rel_path]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supprime les fichiers locaux absents du manifeste
|
* Nettoyage
|
||||||
*/
|
*/
|
||||||
function delete_extra_files($remote_files) {
|
function delete_extra_files($remote_files) {
|
||||||
global $UPDATE_ALLOW_DELETE, $MONITORING_BASE_DIR;
|
global $UPDATE_ALLOW_DELETE, $MONITORING_BASE_DIR, $SCRIPT_PATH;
|
||||||
if (!$UPDATE_ALLOW_DELETE) return;
|
if (!$UPDATE_ALLOW_DELETE) return;
|
||||||
|
|
||||||
$directories = ['bin', 'lib', 'conf'];
|
foreach (['bin', 'lib', 'conf'] as $dir) {
|
||||||
foreach ($directories as $dir) {
|
$full_path = $MONITORING_BASE_DIR . '/' . $dir;
|
||||||
$full_path = $MONITORING_BASE_DIR . '/' . $dir;
|
if (!is_dir($full_path)) continue;
|
||||||
if (!is_dir($full_path)) continue;
|
|
||||||
|
|
||||||
$iterator = new RecursiveIteratorIterator(
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($full_path, RecursiveDirectoryIterator::SKIP_DOTS));
|
||||||
new RecursiveDirectoryIterator($full_path, RecursiveDirectoryIterator::SKIP_DOTS)
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($iterator as $file) {
|
foreach ($iterator as $file) {
|
||||||
// On récupère le chemin relatif par rapport à la racine du monitoring
|
$path = $file->getPathname();
|
||||||
$rel_path = substr($file->getPathname(), strlen($MONITORING_BASE_DIR) + 1);
|
$rel_path = substr($path, strlen($MONITORING_BASE_DIR) + 1);
|
||||||
|
|
||||||
// 1. Protection : Si c'est dans le manifeste distant, on ne touche à rien
|
// PROTECTIONS
|
||||||
if (in_array($rel_path, $remote_files)) {
|
if (in_array($rel_path, $remote_files)) continue;
|
||||||
continue;
|
if (str_contains($rel_path, '.local.')) continue; // Protection fichiers locaux
|
||||||
}
|
if ($path === $SCRIPT_PATH) continue; // Ne pas se suicider
|
||||||
|
|
||||||
// 2. Protection générique : On n'efface JAMAIS les fichiers de configuration locale
|
if (@unlink($path)) {
|
||||||
// Cela couvre : *.local.conf.php, *.local.conf, et même *.local.php par sécurité
|
log_notice("file_deleted", "Fichier obsolète supprimé", ["file" => $rel_path]);
|
||||||
if (str_ends_with($rel_path, '.local.conf.php') ||
|
}
|
||||||
str_ends_with($rel_path, '.local.conf') ||
|
}
|
||||||
str_ends_with($rel_path, '.local.php')) {
|
}
|
||||||
|
|
||||||
log_debug("delete_skipped", "Fichier local protégé (ignoré)", ["file" => $rel_path]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Suppression si le fichier est obsolète et non protégé
|
|
||||||
if (@unlink($file->getPathname())) {
|
|
||||||
log_notice("file_deleted", "Fichier obsolète supprimé", ["file" => $rel_path]);
|
|
||||||
} else {
|
|
||||||
log_error("delete_failed", "Impossible de supprimer le fichier local", ["file" => $rel_path]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Main ---
|
// --- Main ---
|
||||||
|
|
||||||
$manifest = fetch_manifest($UPDATE_MANIFEST_URL);
|
$manifest = fetch_manifest($UPDATE_MANIFEST_URL);
|
||||||
if ($manifest === false) exit(2);
|
if (!$manifest) exit(2);
|
||||||
|
|
||||||
$total = count($manifest);
|
|
||||||
$updated = 0;
|
|
||||||
$failed = 0;
|
|
||||||
$remote_paths = [];
|
$remote_paths = [];
|
||||||
|
$updated = 0; $failed = 0;
|
||||||
|
|
||||||
foreach ($manifest as $entry) {
|
foreach ($manifest as $entry) {
|
||||||
$remote_paths[] = $entry['path'];
|
$remote_paths[] = $entry['path'];
|
||||||
if (update_one_file($entry)) {
|
update_one_file($entry) ? $updated++ : $failed++;
|
||||||
$updated++;
|
|
||||||
} else {
|
|
||||||
$failed++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_extra_files($remote_paths);
|
delete_extra_files($remote_paths);
|
||||||
|
|
||||||
if ($failed > 0) {
|
if ($failed > 0) {
|
||||||
log_warning("update_finished_with_errors", "Mise à jour terminée avec erreurs", ["total" => $total, "updated" => $updated, "failed" => $failed]);
|
log_warning("update_partial", "Mise à jour terminée avec erreurs", ["failed" => $failed]);
|
||||||
} else {
|
} else {
|
||||||
log_info("update_finished", "Mise à jour terminée", ["total" => $total, "updated" => $updated]);
|
log_info("update_ok", "Mise à jour terminée avec succès");
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_with_status();
|
exit_with_status();
|
||||||
@@ -24,12 +24,6 @@ return [
|
|||||||
'ALERT_MAIL_SUBJECT_PREFIX' => '[monitoring]',
|
'ALERT_MAIL_SUBJECT_PREFIX' => '[monitoring]',
|
||||||
'DEST' => 'admin@example.com', // N'oubliez pas de définir le destinataire
|
'DEST' => 'admin@example.com', // N'oubliez pas de définir le destinataire
|
||||||
|
|
||||||
// --- Configuration ntfy ---
|
|
||||||
'NTFY_SERVER' => 'https://ntfy.sh',
|
|
||||||
'NTFY_TOPIC' => 'TPOSOB84sBJ6HTZ7',
|
|
||||||
'NTFY_TOKEN' => '',
|
|
||||||
'NTFY_CLICK_URL' => '',
|
|
||||||
|
|
||||||
// --- Déduplication ---
|
// --- Déduplication ---
|
||||||
'ALERT_DEDUP_WINDOW' => 3600, // en secondes
|
'ALERT_DEDUP_WINDOW' => 3600, // en secondes
|
||||||
|
|
||||||
@@ -42,16 +36,25 @@ return [
|
|||||||
|
|
||||||
// --- Canaux par défaut selon le niveau ---
|
// --- Canaux par défaut selon le niveau ---
|
||||||
'DEFAULT_CHANNELS' => [
|
'DEFAULT_CHANNELS' => [
|
||||||
'WARNING' => 'ntfy',
|
'INFO' => 'ntfy',
|
||||||
'ERROR' => 'ntfy,mail',
|
'NOTICE' => 'ntfy',
|
||||||
'CRITICAL' => 'ntfy,mail',
|
'WARNING' => 'ntfy',
|
||||||
|
'ERROR' => 'ntfy,mail',
|
||||||
|
'CRITICAL' => 'ntfy,mail',
|
||||||
],
|
],
|
||||||
|
|
||||||
// --- Tags ntfy par niveau ---
|
// --- Tags : Une icône pour chaque état possible ---
|
||||||
'NTFY_TAGS' => [
|
'NTFY_TAGS' => [
|
||||||
'WARNING' => 'warning',
|
'DEBUG' => 'gear', // ⚙️
|
||||||
'ERROR' => 'warning,rotating_light',
|
'INFO' => 'information_source', // ℹ️
|
||||||
'CRITICAL' => 'skull,warning',
|
'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 ---
|
// --- Règles spécifiques par événement ---
|
||||||
|
|||||||
@@ -51,4 +51,13 @@ return [
|
|||||||
|
|
||||||
// --- Logs ---
|
// --- Logs ---
|
||||||
'LOG_LEVEL' => 'INFO', // DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL
|
'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"
|
||||||
|
],
|
||||||
];
|
];
|
||||||
@@ -25,11 +25,16 @@ $CONFIG = [
|
|||||||
'LOG_LEVEL' => 'INFO'
|
'LOG_LEVEL' => 'INFO'
|
||||||
];
|
];
|
||||||
|
|
||||||
if (file_exists($MONITORING_CONF_DIR . '/monitoring.conf.php')) {
|
// 1. On charge la configuration GLOBALE (La vérité est ici)
|
||||||
$global_conf = include $MONITORING_CONF_DIR . '/monitoring.conf.php';
|
$global_conf = $MONITORING_CONF_DIR . "/monitoring.local.conf.php";
|
||||||
if (is_array($global_conf)) {
|
if (file_exists($global_conf)) {
|
||||||
$CONFIG = array_merge($CONFIG, $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
|
// Variables d'exécution
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
f433b3e2ca25c76cccebf971072255dae64169a8ae162d6baa10776904d733e9 755 bin/alert-engine.sh
|
5b4ea784d2cbe73f6e829e35f23b0b4dbe12df55cc1abc8eba6602da36c724ef 755 bin/alert-engine.php
|
||||||
7ff2eb1163ca8b9aa3927ac7f0ebbcc1f90c944e51afbc880d57359b83a0c73f 755 bin/check_disk.sh
|
fdcea6720186795538f48c08b99103b320273dbdd0ea5246a2da9d81a1eecc6c 755 bin/check_disk.sh
|
||||||
4fae83b48dc25c5e2a59bba944d8c3f2c6dff89bf2adb932d4dd9201f6305ca4 755 bin/install-monitoring.sh
|
ead10d3be3aac48c6406a734dee1bddf9a8abb1e21de102ce72fa92fdecbaf22 755 bin/check_smart.sh
|
||||||
36528963f2e78a160738a2cf3b8da67b9d12dbe495d9d01ca6c1ba97956288fa 755 bin/monitoring.sh
|
8f95824b568b5de7dbdc2d6ab87fc6fd8076dcb8ad20de3e72a53391e97f8484 755 bin/install-monitoring.sh
|
||||||
78ccebfd1da7cf885fddb8d5a967c23e379c495d8f43490584ace7133690ec55 755 bin/monitoring-update.sh
|
97a91b13b0776acb3326010821ffcc163e96a97e3c326ea77f11efdb7baf159a 755 bin/log-cli.php
|
||||||
54eb520360c80b3146c5cdb846330a8743cbeb9fe6de0559357114b92d090c29 755 bin/monitor-update-config.sh
|
02bd43ed2a9b92acc013274c716e6bc50120a8103ccf3d9c4e6f345a0b22d6a0 755 bin/monitoring.php
|
||||||
83db39c8d0cfd6f6e9d3cc5b961a67db29dc73666304a91e0d4a6d5831c623cb 644 conf/alert-engine.conf
|
97d407d75a26bd2ebbb86a2e5f8dab8b24639e8a9164f42bd554ba7728ab8cb5 755 bin/monitoring-update-config.php
|
||||||
caaa8f6031d66bc43a897ac2804124ce2050a64523734195d5505ae863836bf4 644 conf/monitoring.conf
|
910a7c3a4423fb5456233d0c6cbcfc7f511c94947580972c42286762142e5ce6 755 bin/monitoring-update.php
|
||||||
654cd98ecda1c485a0ea1224f160a3c4d7396ab95a491603574e2ad1981fe010 644 lib/monitoring-lib.sh
|
dc70c1184da4aa32eebdeaee57cfed23e91397c94a6243e0ac8664968078f0c7 644 conf/alert-engine.conf.php
|
||||||
|
324038d28f24f3f4d1f6def73752ff703d4ce8b532a663c6628611923748b1f5 644 conf/monitoring.conf.php
|
||||||
|
9bb7f5438edc5fb6a5b899ee21be2a5a559eb0697a028a4e991fc82362eaa460 644 lib/monitoring-lib.php
|
||||||
|
|||||||
Reference in New Issue
Block a user