Wer einen Server aus der Ping-Ueberwachung nehmen wollte, musste sich bisher auf den Rechner mit check_fds.sh einloggen und die hosts.conf editieren - und die Kachel blieb danach trotzdem im System-Monitor stehen, weil ein einmal gemeldetes System nirgends wieder geloescht werden konnte. Die Liste steht jetzt in der Datenbank und ist ueber ein Zahnrad neben der Ueberschrift "Systeme" pflegbar, analog zum Zahnrad am PO-Platz (Issue #27). Jeder Host gehoert dem OPs-Agenten (Issue #66), der ihn anpingt: API und Ping-Check laufen nicht zwangslaeufig auf demselben Rechner, die API kann die hosts.conf also nicht selbst schreiben. check_fds.sh holt sie stattdessen bei jedem Lauf mit seinem Agenten-Key ab und schreibt daraus die lokale Datei; ist die API nicht erreichbar, pingt es unveraendert nach der zuletzt geschriebenen Liste weiter. Beim ersten Lauf laedt der Check seine bestehende hosts.conf hoch, damit die bereits ueberwachten Server nicht von Hand nachgetragen werden muessen. Dass die API das nur einmal annimmt (Marker in host_config_imports) und "noch nie konfiguriert" per 204 von "im Frontend geleert" per 200 unterscheidet, ist noetig, damit ein Ping-Rechner mit alter lokaler Datei die dort geloeschten Hosts nicht wieder anlegt. Loeschen raeumt mit auf: Ein entfernter Host nimmt seine Kachel mit - beide Ids, denn der status.txt-Import benennt ihn nach dem Hostnamen, die REST-Meldung nach dessen Slug. Kacheln ohne zugehoerigen Host (Selbstmelder oder Reste laengst entfernter Hosts) stehen im selben Dialog unter "Weitere Kacheln im System-Monitor" und sind dort einzeln loeschbar - dafuer gibt es neu DELETE /api/v1/systems/{id}. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vNjbfr8M3HSfWMee92GTE
214 lines
7.5 KiB
Bash
Executable file
214 lines
7.5 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
|
|
|
|
# Issue #70: Die Liste der ueberwachten Hosts wird im Frontend gepflegt (Zahnrad am
|
|
# Wandmonitor "Systeme"). Dieser Aufruf holt sie vor jedem Durchlauf ab und schreibt daraus
|
|
# die lokale hosts.conf - deshalb muss auf diesem Rechner nichts mehr von Hand editiert
|
|
# werden. Die Ueberwachung darf dabei nie stehenbleiben: Ist die API nicht erreichbar oder
|
|
# antwortet sie mit einem Fehler, bleibt die zuletzt geschriebene hosts.conf in Kraft.
|
|
sync_hosts_from_api() {
|
|
if [ -z "$MONITOR_API_BASE" ] || [ -z "$MONITOR_API_KEY" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local tmp code count
|
|
tmp=$(mktemp) || return 0
|
|
|
|
if ! code=$(curl -s -o "$tmp" -w '%{http_code}' --max-time 10 \
|
|
-H "X-Api-Key: ${MONITOR_API_KEY}" \
|
|
"${MONITOR_API_BASE%/}/api/v1/hosts/conf"); then
|
|
echo " Warnung: API unter $MONITOR_API_BASE nicht erreichbar, benutze die lokale $HOST_FILE."
|
|
rm -f "$tmp"
|
|
return 0
|
|
fi
|
|
|
|
# 204 heisst: fuer diesen Rechner ist serverseitig noch nichts konfiguriert. Dann geht
|
|
# die bestehende lokale Datei einmalig an die API, damit die bereits ueberwachten Hosts
|
|
# nicht von Hand im Frontend nachgetragen werden muessen. Ein spaeterer Aufruf wird mit
|
|
# 200 beantwortet - auch bei leerer Liste; eine im Frontend geleerte Liste fuellt dieser
|
|
# Zweig also nicht wieder auf.
|
|
if [ "$code" = "204" ]; then
|
|
if [ -s "$HOST_FILE" ]; then
|
|
local imported
|
|
imported=$(curl -s --max-time 10 -X POST \
|
|
"${MONITOR_API_BASE%/}/api/v1/hosts/import" \
|
|
-H "Content-Type: text/plain" \
|
|
-H "X-Api-Key: ${MONITOR_API_KEY}" \
|
|
--data-binary "@$HOST_FILE")
|
|
case "$imported" in
|
|
*'"imported":0'*) : ;;
|
|
*'"imported"'*) echo " Bestehende $HOST_FILE an die API uebergeben." ;;
|
|
*) echo " Warnung: Uebergabe der $HOST_FILE an die API fehlgeschlagen." ;;
|
|
esac
|
|
fi
|
|
rm -f "$tmp"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$code" != "200" ]; then
|
|
echo " Warnung: API antwortet mit HTTP $code auf die Host-Liste, benutze die lokale $HOST_FILE."
|
|
rm -f "$tmp"
|
|
return 0
|
|
fi
|
|
|
|
# Zeilen, die check_fds.sh spaeter auch wirklich pingt (ohne Kommentare/Leerzeilen).
|
|
count=$(grep -cvE '^[[:space:]]*(#|$)' "$tmp")
|
|
|
|
if ! cmp -s "$tmp" "$HOST_FILE"; then
|
|
# cat statt mv: so behalten Besitzer und Rechte der bestehenden Datei ihren Wert,
|
|
# und ein abgebrochener Schreibvorgang trifft keine Datei aus /tmp mit 600/root.
|
|
if cat "$tmp" > "$HOST_FILE"; then
|
|
echo " Host-Liste aus der API uebernommen ($count Hosts)."
|
|
else
|
|
echo " Warnung: $HOST_FILE ist nicht schreibbar, benutze die bisherige Liste."
|
|
fi
|
|
fi
|
|
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
# 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)"
|
|
sync_hosts_from_api
|
|
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"
|