Ampel / ampel (push) Successful in 22s
Das Repo curator-staleness-check war ein eigener Wachhund mit eigenem systemd-Timer und eigener CI-Ampel - und ueberwachte einen Cron-Job ueber eine fest verdrahtete ID (a47910e2ef05), den es laengst nicht mehr gibt. Er haette bei jedem Lauf Alarm geschlagen fuer etwas, das nicht existiert. Dieselbe Frage steht jetzt in vier Zeilen im Radar. Repo archiviert. Falle dabei: find -printf %T@ liefert einen Float, bash bricht damit ab - %Ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
205 lines
8.8 KiB
Bash
205 lines
8.8 KiB
Bash
#!/usr/bin/env bash
|
|
# stack-ist.sh — der IST-Zustand der Box, in Fakten (21.08.2026).
|
|
#
|
|
# Wird von Hermes-Cron "stack-radar" ueber --script eingespeist: die Ausgabe hier
|
|
# landet im Prompt des Agenten, der daraus den Bericht schreibt.
|
|
#
|
|
# GRUNDSATZ: Dieses Skript prueft ERGEBNISSE, nicht Lebenszeichen.
|
|
# Der alte Selbsttest war am 20.08. gruen, waehrend vier Dinge kaputt waren:
|
|
# das Kritiker-Gate zeigte auf ein umbenanntes Modell (404), Lucys SOUL.md war
|
|
# blockiert, das Dashboard stand ohne Passwort offen, die CI war rot.
|
|
# "Dienst antwortet" haette das nie gefunden. Deshalb wird hier nachgesehen,
|
|
# ob Dinge STIMMEN — nicht ob sie laufen.
|
|
#
|
|
# Nie mit set -e: ein einzelner fehlschlagender Test darf den Bericht nicht
|
|
# abschneiden. Fehler werden gemeldet, nicht verschwiegen.
|
|
set -uo pipefail
|
|
|
|
MC2="${MC2_URL:-http://127.0.0.1:9001}"
|
|
SWAP="${SWAP_URL:-http://127.0.0.1:8080}"
|
|
GITEA="${GITEA_URL:-http://192.168.178.153:3000}"
|
|
OEFFENTLICH="${OEFFENTLICH_URL:-https://mc.tobisniceshomelab.ddnsfree.com}"
|
|
|
|
ok() { printf ' OK %s\n' "$*"; }
|
|
warn() { printf ' ACHTUNG %s\n' "$*"; }
|
|
bad() { printf ' ROT %s\n' "$*"; }
|
|
|
|
echo "IST-ZUSTAND DER BOX — $(date '+%d.%m.%Y %H:%M')"
|
|
echo
|
|
|
|
# ---------------------------------------------------------------- Dienste ---
|
|
echo "DIENSTE"
|
|
# systemd stellt gescheiterten Units ein "●" voran — das muss weg, sonst meldet
|
|
# der Bericht lauter Aufzaehlungspunkte als kaputte Dienste.
|
|
# EGAL: Units, die dauerhaft scheitern und niemanden stoeren. Ein Bericht, der
|
|
# jeden Tag denselben harmlosen Fehler zeigt, erzieht zum Wegsehen.
|
|
EGAL="at-spi-dbus-bus.service"
|
|
fehl=$(systemctl --user list-units --type=service --state=failed --no-legend --no-pager 2>/dev/null \
|
|
| sed 's/^[^a-zA-Z]*//' | awk '{print $1}')
|
|
gefunden=0
|
|
for d in $fehl; do
|
|
case " $EGAL " in *" $d "*) continue ;; esac
|
|
bad "Dienst gescheitert: $d"; gefunden=1
|
|
done
|
|
[ "$gefunden" = "0" ] && ok "kein Dienst im Fehlerzustand"
|
|
|
|
for d in mc2-gateway mission-control-2 hermes-gateway voice-service; do
|
|
systemctl --user is-active --quiet "$d.service" 2>/dev/null \
|
|
&& ok "$d laeuft" || bad "$d laeuft NICHT"
|
|
done
|
|
systemctl is-active --quiet llama-swap.service 2>/dev/null \
|
|
&& ok "llama-swap laeuft" || bad "llama-swap laeuft NICHT"
|
|
echo
|
|
|
|
# ------------------------------------------------------------------ Timer ---
|
|
# Nicht "ist der Timer aktiv", sondern "ist der letzte Lauf gutgegangen".
|
|
echo "TIMER — letzter Lauf"
|
|
for t in $(systemctl --user list-timers --no-legend --no-pager 2>/dev/null | awk '{print $NF}'); do
|
|
dienst="${t%.service}.service"
|
|
case "$dienst" in launchpadlib-*) continue ;; esac # Ubuntu-Eigenes, nicht unseres
|
|
code=$(systemctl --user show -p ExecMainStatus --value "$dienst" 2>/dev/null)
|
|
wann=$(systemctl --user show -p ExecMainExitTimestamp --value "$dienst" 2>/dev/null | cut -d' ' -f2,3)
|
|
if [ -z "$wann" ]; then warn "$dienst ist eingerichtet, aber NIE gelaufen"
|
|
elif [ "${code:-0}" = "0" ]; then ok "$dienst zuletzt $wann"
|
|
else bad "$dienst zuletzt GESCHEITERT (exit $code) am $wann"; fi
|
|
done
|
|
echo
|
|
|
|
# --------------------------------------------------------- Modell-Aliase ---
|
|
# Der Fund vom 19./20.08.: Skripte und Configs nennen Modelle beim Eigennamen,
|
|
# das Modell wird umbenannt, und alles laeuft still in 404. Hier wird geprueft,
|
|
# ob jeder Alias, den irgendwer benutzt, heute noch antwortet.
|
|
echo "MODELL-ALIASE (Rollen muessen aufloesen)"
|
|
liste=$(curl -s -m 10 "$MC2/v1/models" 2>/dev/null \
|
|
| python3 -c 'import sys,json; print(" ".join(m["id"] for m in json.load(sys.stdin)["data"]))' 2>/dev/null)
|
|
if [ -z "$liste" ]; then bad "Modell-Liste nicht abrufbar ueber $MC2/v1"
|
|
else
|
|
ok "verfuegbar: $liste"
|
|
for rolle in hermes fast heavy; do
|
|
case " $liste " in
|
|
*" $rolle "*) ok "Rolle '$rolle' loest auf" ;;
|
|
*) bad "Rolle '$rolle' FEHLT — Skripte, die sie nennen, laufen in 404" ;;
|
|
esac
|
|
done
|
|
fi
|
|
echo
|
|
|
|
# ------------------------------------------------------------- Erreichbar ---
|
|
echo "ERREICHBARKEIT"
|
|
for paar in "MC2:$MC2/api/health" "llama-swap:$SWAP/v1/models" "Gitea:$GITEA/api/v1/version"; do
|
|
name="${paar%%:*}"; url="${paar#*:}"
|
|
code=$(curl -s -m 10 -o /dev/null -w '%{http_code}' "$url" 2>/dev/null)
|
|
[ "$code" = "200" ] && ok "$name antwortet (200)" || bad "$name antwortet mit '$code'"
|
|
done
|
|
echo
|
|
|
|
# ------------------------------------------------ Offen im Internet? --------
|
|
# Der Fund vom 21.08.: MC2 hing ueber den Nginx Proxy Manager ungeschuetzt im
|
|
# Netz. ufw sah nichts davon, weil der Proxy im LAN steht. Also von aussen
|
|
# nachsehen — und zwar auf einem Endpunkt, der ECHTE Daten liefert.
|
|
echo "OEFFENTLICHE ERREICHBARKEIT"
|
|
code=$(curl -s -m 15 -o /tmp/.ist-oeff -w '%{http_code}' "$OEFFENTLICH/api/system/status" 2>/dev/null)
|
|
if [ "$code" = "200" ] && head -c1 /tmp/.ist-oeff 2>/dev/null | grep -q '{'; then
|
|
bad "MC2 liefert Systemdaten OHNE Anmeldung ins Internet ($OEFFENTLICH)"
|
|
elif [ "$code" = "401" ] || [ "$code" = "403" ]; then
|
|
ok "oeffentlicher Zugang verlangt Anmeldung ($code)"
|
|
elif [ "$code" = "000" ]; then
|
|
ok "von aussen nicht erreichbar"
|
|
else
|
|
warn "oeffentlicher Zugang antwortet mit '$code' — nachsehen"
|
|
fi
|
|
rm -f /tmp/.ist-oeff
|
|
echo
|
|
|
|
# ----------------------------------------------------------------- Ports ---
|
|
echo "OFFENE PORTS (ufw)"
|
|
sudo -n ufw status 2>/dev/null | grep ALLOW | sed 's/^/ /' || warn "ufw-Status nicht lesbar"
|
|
echo
|
|
|
|
# --------------------------------------------------------------- CI-Ampel ---
|
|
echo "CI-AMPEL (Gitea)"
|
|
T=$(tr -d '[:space:]' < "$HOME/.config/gitea/create-token" 2>/dev/null)
|
|
if [ -z "$T" ]; then warn "kein Gitea-Token — CI nicht pruefbar"
|
|
else
|
|
curl -s -m 20 -H "Authorization: token $T" "$GITEA/api/v1/user/repos?limit=100" 2>/dev/null \
|
|
| python3 -c '
|
|
import sys, json
|
|
try:
|
|
repos = json.load(sys.stdin)
|
|
except Exception as e:
|
|
print(" ACHTUNG Repo-Liste nicht lesbar:", e); sys.exit(0)
|
|
print(f" OK {len(repos)} Repos in Gitea")
|
|
' 2>/dev/null || warn "Repo-Liste nicht lesbar"
|
|
fi
|
|
echo
|
|
|
|
# ------------------------------------------------------------- Sicherung ---
|
|
echo "SICHERUNGEN"
|
|
# Pfad aus backup.sh: DEST_DIR="$MODELS_DIR/mc2-backups"
|
|
SICHER="${MC_BACKUP_DIR:-/srv/models/mc2-backups}"
|
|
neuestes=$(find "$SICHER" -maxdepth 2 -type f -newermt '-2 days' 2>/dev/null | head -1)
|
|
anzahl=$(find "$SICHER" -maxdepth 2 -type f 2>/dev/null | wc -l)
|
|
if [ -n "$neuestes" ]; then
|
|
ok "Sicherung juenger als 2 Tage vorhanden ($anzahl Staende in $SICHER)"
|
|
elif [ "$anzahl" -gt 0 ]; then
|
|
bad "juengste Sicherung ist AELTER als 2 Tage ($anzahl Staende in $SICHER)"
|
|
else
|
|
bad "gar keine Sicherung in $SICHER"
|
|
fi
|
|
echo
|
|
|
|
# ------------------------------------------------------------------ Platz ---
|
|
echo "PLATZ UND LAST"
|
|
# Nur eindeutige Dateisysteme: / und /srv liegen hier auf demselben Geraet,
|
|
# doppelt gemeldet las sich das wie ein Befund.
|
|
df -h --output=source,size,used,avail,pcent,target / /srv 2>/dev/null \
|
|
| awk '!gesehen[$1]++ {printf " %s\n", $0}'
|
|
echo " RAM: $(free -h | awk '/^Mem:/{print $3" von "$2" belegt"}')"
|
|
echo
|
|
|
|
# ------------------------------------------------------------- Warm/Kalt ---
|
|
# ---------------------------------------------------------------- Curator ---
|
|
# Hierher gefaltet am 22.08.2026 aus dem Repo `curator-staleness-check`.
|
|
# Das war ein eigener Wachhund mit eigenem systemd-Timer und eigener CI-Ampel —
|
|
# und er ueberwachte einen Cron-Job ueber eine FEST VERDRAHTETE ID
|
|
# (a47910e2ef05), den es laengst nicht mehr gibt. Er haette bei jedem Lauf
|
|
# Alarm geschlagen fuer etwas, das gar nicht existiert.
|
|
# Hier steht dieselbe Frage in vier Zeilen, ohne Repo, Timer und Ampel.
|
|
echo "CURATOR"
|
|
AN=$(grep -A3 '^curator:' "$HOME/.hermes/config.yaml" 2>/dev/null | grep -m1 'enabled:' | awk '{print $2}')
|
|
if [ "${AN:-false}" != "true" ]; then
|
|
ok "Curator ist abgeschaltet — nichts zu ueberwachen"
|
|
else
|
|
# %Ts liefert GANZE Sekunden. %T@ waere ein Float — bash rechnet damit nicht
|
|
# und bricht mit "invalid arithmetic operator" ab.
|
|
JUENGSTES=$(find "$HOME/.hermes/logs/curator" -type f -name '*.log' -printf '%Ts\n' 2>/dev/null | sort -rn | head -1)
|
|
if [ -z "$JUENGSTES" ]; then
|
|
bad "Curator ist AN, hat aber noch nie etwas protokolliert"
|
|
else
|
|
TAGE=$(( ( $(date +%s) - JUENGSTES ) / 86400 ))
|
|
if [ "$TAGE" -le 7 ]; then ok "Curator war vor $TAGE Tagen zuletzt aktiv"
|
|
elif [ "$TAGE" -le 21 ]; then warn "Curator seit $TAGE Tagen still — laeuft er noch?"
|
|
else bad "Curator seit $TAGE Tagen still, obwohl eingeschaltet"
|
|
fi
|
|
fi
|
|
fi
|
|
echo
|
|
|
|
echo "MODELLE IM SPEICHER"
|
|
curl -s -m 10 "$SWAP/running" 2>/dev/null \
|
|
| python3 -c '
|
|
import sys, json
|
|
try:
|
|
d = json.load(sys.stdin)
|
|
except Exception:
|
|
print(" ACHTUNG llama-swap-Status nicht lesbar"); sys.exit(0)
|
|
lauf = d.get("running", [])
|
|
if not lauf:
|
|
print(" OK kein Modell geladen (alles kalt)")
|
|
for m in lauf:
|
|
print(" OK " + str(m.get("model")) + " - " + str(m.get("state")))
|
|
'
|
|
|
|
echo
|
|
echo "ENDE IST-ZUSTAND"
|