monitor/check_fds.sh
fds2610-bot 2a552342f5 Ping-Check meldet den Systemstatus zusaetzlich per REST an die API
Laufen check_fds.sh und die API auf verschiedenen Rechnern, erreicht der
Legacy-Import die API nie: SystemController::index() liest MONITOR_STATUS_FILE
als lokale Datei, waehrend status.txt auf dem Ping-Host liegt. Der
LegacyStatusFileReader liefert dann stillschweigend ein leeres Array und der
System-Monitor zeigt dauerhaft "Keine Systeme gemeldet" - ohne Fehlermeldung,
die auf die Ursache hinweisen wuerde.

check_fds.sh meldet die Ping-Ergebnisse jetzt zusaetzlich per
POST /api/v1/systems/{id}/status (online -> ok, offline -> error) und schreibt
status.txt unveraendert weiter. Die Zugangsdaten stehen in monitor-api.conf
(Vorlage: monitor-api-sample.conf); fehlt die Datei, bleibt das Verhalten
unveraendert. Eine unerreichbare API bricht den Check nicht ab, sondern
protokolliert nur eine Warnung.

Die System-ID wird aus dem Hostnamen slugifiziert, weil der Router auf dem
rohen, nicht dekodierten Pfad matcht - "Nextcloud(intern)" landete sonst
prozentkodiert in der Datenbank. Der lesbare Name geht im Feld "name" mit.

Das README beschreibt den kombinierten Weg als "Weg 3" und weist bei Weg 1
darauf hin, dass der Legacy-Import eine lokale Datei erwartet.

Ausserdem zwei Fehler im selben Skript behoben:
- Pfade werden relativ zum Skript statt zum Arbeitsverzeichnis aufgeloest.
- Die letzte Zeile der hosts.conf wurde ohne abschliessenden Zeilenumbruch
  stillschweigend verschluckt, ein zuletzt angehaengter Host also nie geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 20:28:11 +02:00

147 lines
4.7 KiB
Bash
Executable file

#!/bin/bash
# Pfade relativ zum Skript aufloesen, nicht zum Arbeitsverzeichnis: der Cronjob startet
# das Skript zwar mit "cd", ein Aufruf von aussen laese sonst aber die falsche hosts.conf
# (oder gar keine).
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Pfad zur Textdatei mit Hostname-IP-Paaren (pro Zeile: hostname ip)
HOST_FILE="$SCRIPT_DIR/hosts.conf"
# Pfad zum mail_fds.py-Skript
MAIL_SCRIPT="$SCRIPT_DIR/mail_fds.py"
# Pfad zur Statusdatei
STATUS_FILE="/var/www/html/status.txt"
# Zugangsdaten fuer die Statusmeldung per REST (siehe monitor-api-sample.conf)
API_CONF="$SCRIPT_DIR/monitor-api.conf"
# Die Meldung per REST ist optional: fehlt monitor-api.conf oder ist sie leer, schreibt der
# Check wie bisher nur status.txt und mailt bei Ausfall. Noetig ist sie dort, wo Ping-Check
# und API auf verschiedenen Rechnern laufen - der Legacy-Import aus status.txt (F6) liest
# eine lokale Datei und erreicht die API dann nicht.
MONITOR_API_BASE=""
MONITOR_API_KEY=""
if [ -f "$API_CONF" ]; then
# shellcheck source=/dev/null
. "$API_CONF"
fi
# Erzeugt aus dem Hostnamen eine URL-sichere System-ID. Der Router der API matcht auf dem
# rohen, nicht dekodierten Pfad (api/src/Http/Router.php) - eine prozentkodierte ID landete
# also kodiert in der Datenbank. Der lesbare Originalname geht im Feld "name" mit.
system_id() {
local slug
slug=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
| sed -e 's/[^a-z0-9]\+/-/g' -e 's/^-//' -e 's/-$//')
printf '%s' "${slug:-unbekannt}"
}
json_escape() {
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
}
# Meldet einen Host per POST /api/v1/systems/{id}/status. Fehler werden nur protokolliert:
# eine unerreichbare API darf die Ping-Ueberwachung nicht anhalten.
report_to_api() {
local hostname="$1"
local ip="$2"
local status="$3"
if [ -z "$MONITOR_API_BASE" ] || [ -z "$MONITOR_API_KEY" ]; then
return 0
fi
local api_status="error"
if [ "$status" = "online" ]; then
api_status="ok"
fi
local payload
payload=$(printf '{"status":"%s","name":"%s","type":"host","detail":"%s"}' \
"$api_status" "$(json_escape "$hostname")" "$(json_escape "$ip")")
local code
if ! code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 \
-X POST "${MONITOR_API_BASE%/}/api/v1/systems/$(system_id "$hostname")/status" \
-H "Content-Type: application/json" \
-H "X-Api-Key: ${MONITOR_API_KEY}" \
-d "$payload"); then
echo " Warnung: API unter $MONITOR_API_BASE nicht erreichbar, $hostname nicht gemeldet."
return 0
fi
if [ "$code" != "200" ]; then
echo " Warnung: API antwortet mit HTTP $code auf die Meldung fuer $hostname."
fi
}
# Funktion zum Prüfen einer IP und Schreiben des Status
check_ip() {
local hostname="$1"
local ip="$2"
local success_count=0
# echo "Prüfe $hostname ($ip) ..."
for i in {1..3}; do
if ping -c 1 -W 2 "$ip" &> /dev/null; then
((success_count++))
else
echo " Ping $i an $ip fehlgeschlagen."
fi
done
local status="online"
if [ "$success_count" -lt 2 ]; then
status="offline"
echo " Mehr als ein Ping fehlgeschlagen für $hostname ($ip). Führe $MAIL_SCRIPT aus."
"$MAIL_SCRIPT" --subject "Ping-Fehler bei $hostname $ip" --credentials "$SCRIPT_DIR/credentials.conf"
else
echo " $hostname ($ip) ist erreichbar."
fi
report_to_api "$hostname" "$ip" "$status"
# Status in status.conf schreiben/aktualisieren
local now=$(date +"%Y-%m-%d %H:%M")
awk -v h="$hostname" -v i="$ip" -v s="$status" -v d="$now" '
BEGIN { found=0 }
{
if ($1 == h && $2 == i) {
print h, i, s, d;
found=1;
} else {
print $0;
}
}
END {
if (!found && h != "" && i != "") {
print h, i, s, d;
}
}' "$STATUS_FILE" > "$STATUS_FILE.tmp" && mv "$STATUS_FILE.tmp" "$STATUS_FILE"
}
# Hauptskript
echo "Pruefung: $(date)"
if [ ! -f "$HOST_FILE" ]; then
echo "Fehler: Datei $HOST_FILE nicht gefunden."
exit 1
fi
if [ ! -f "$MAIL_SCRIPT" ]; then
echo "Fehler: Skript $MAIL_SCRIPT nicht gefunden."
exit 1
fi
# Statusdatei anlegen, falls nicht vorhanden
touch "$STATUS_FILE"
# "|| [ -n "$line" ]" nimmt auch eine letzte Zeile ohne abschliessenden Zeilenumbruch mit -
# ohne sie verschluckt read den zuletzt angehaengten Host stillschweigend.
while read -r line || [ -n "$line" ]; do
case "$line" in
\#*|"") continue ;;
esac
hostname=$(echo "$line" | awk '{print $1}')
ip=$(echo "$line" | awk '{print $2}')
if [ -n "$hostname" ] && [ -n "$ip" ]; then
check_ip "$hostname" "$ip"
fi
done < "$HOST_FILE"