#!/bin/bash # check-stack.sh – Statusprüfung DIESES Stacks (MT5, MCP, Trader, Telegram) # Läuft im Stack-Ordner auf dem Server; Werte kommen aus deploy.conf. # # scripts/check-stack.sh ausführlich # scripts/check-stack.sh -q nur Fehler/Warnungen (cron/Deploy) # Exit-Code: 0 = ok, 1 = mindestens ein Fehler (Warnungen zählen nicht) cd "$(dirname "$0")/.." || exit 1 source ./deploy.conf QUIET=0; [ "${1:-}" = "-q" ] && QUIET=1 FAIL=0 SYMBOL="${CHECK_SYMBOL:-XAUUSD}" BASE="http://127.0.0.1:$MCP_PORT" if [ -t 1 ]; then G=$'\e[32m'; R=$'\e[31m'; Y=$'\e[33m'; B=$'\e[1m'; N=$'\e[0m'; else G=; R=; Y=; B=; N=; fi pfx() { [ $QUIET -eq 1 ] && echo "[$STACK_KEY] "; } ok() { [ $QUIET -eq 1 ] || echo " ${G}OK${N} $*"; } warn() { echo " ${Y}WARN${N} $(pfx)$*"; } fail() { echo " ${R}FAIL${N} $(pfx)$*"; FAIL=1; } jget() { python3 -c "import json,sys try: v=json.load(sys.stdin) except Exception: print('${2:-}'); sys.exit() for k in '$1'.split('.'): v = v.get(k) if isinstance(v, dict) else None print('${2:-}' if v is None else v)"; } cid() { docker compose ps -q "$1" 2>/dev/null | head -1; } [ $QUIET -eq 1 ] || echo "${B}== $STACK_NAME ==${N}" # 1. Container for svc in "$MT5_SVC" "$MCP_SVC" "$TRADER_SVC"; do id=$(cid "$svc") if [ -z "$id" ]; then fail "Dienst $svc: kein Container"; continue; fi st=$(docker inspect -f '{{.State.Status}}{{if .State.Health}}/{{.State.Health.Status}}{{end}}' "$id") case "$st" in running/healthy|running) ok "Dienst $svc: $st" ;; *) fail "Dienst $svc: $st" ;; esac done # 2. Bridge + Konto H=$(curl -s -m 10 "$BASE/health") [ "$(echo "$H" | jget connected)" = "True" ] && ok "MCP $BASE: Bridge verbunden" \ || fail "MCP $BASE: keine Verbindung zu MT5 (${H:-keine Antwort})" A=$(curl -s -m 15 "$BASE/account") if [ -n "$(echo "$A" | jget login)" ]; then ok "Konto $(echo "$A" | jget login) @ $(echo "$A" | jget server): Balance $(echo "$A" | jget balance)" \ "Equity $(echo "$A" | jget equity) $(echo "$A" | jget currency)" else fail "Konto: $(echo "$A" | jget detail "${A:-keine Antwort}")" fi # 3. Kurs T=$(curl -s -m 15 "$BASE/tick/$SYMBOL") if [ -n "$(echo "$T" | jget bid)" ]; then ok "$SYMBOL → $(echo "$T" | jget symbol): bid $(echo "$T" | jget bid) ask $(echo "$T" | jget ask)" else fail "Kurs $SYMBOL: $(echo "$T" | jget detail "${T:-keine Antwort}")" fi # 4. Positionen P=$(curl -s -m 15 "$BASE/positions" | jget count) if [ -z "$P" ]; then fail "Positionen: keine Antwort" elif [ "$P" -gt 0 ]; then warn "Offene Positionen: $P" else ok "Offene Positionen: 0"; fi # 5. Trader-Lebenszeichen (Docker-Zeitstempel = UTC, unabhängig von Zeitzonen) TID=$(cid "$TRADER_SVC") if [ -n "$TID" ]; then LAST=$(docker logs --timestamps --since 10m "$TID" 2>&1 | grep -E "\[(LIVE|DRY)\] Balance" | tail -1) if [ -n "$LAST" ]; then AGE=$(( $(date +%s) - $(date -d "$(echo "$LAST" | awk '{print $1}')" +%s 2>/dev/null || echo 0) )) MODE=$(echo "$LAST" | grep -oE "LIVE|DRY" | head -1) [ "$AGE" -le 120 ] && ok "Trader lebt ($MODE), letztes Lebenszeichen vor ${AGE}s" \ || fail "Trader: letztes Lebenszeichen vor ${AGE}s" else fail "Trader: keine Balance-Zeile in den letzten 10 min" fi # 6. Telegram START=$(docker inspect -f '{{.State.StartedAt}}' "$TID") CHAN=$(docker logs --since "$START" "$TID" 2>&1 | grep -oE "Active channels: [^|]*" | tail -1) [ -n "$CHAN" ] && ok "Telegram: $CHAN" || fail "Telegram: keine 'Active channels'-Meldung seit Start" ERRS=$(docker logs --since 60m "$TID" 2>&1 \ | grep -ciE "AUTH_KEY|EOFError|SessionPasswordNeeded|PhoneNumber|Traceback|disconnected|server closed") [ "$ERRS" -gt 0 ] && warn "Telegram/Trader: $ERRS Fehlerzeile(n) in der letzten Stunde" fi if [ -f "$DIAG_FILE" ]; then DAGE=$(( $(date +%s) - $(stat -c %Y "$DIAG_FILE") )) [ "$DAGE" -le 900 ] && ok "Diagnose: vor ${DAGE}s geschrieben, $(jget total_messages < "$DIAG_FILE") Nachricht(en)" \ || fail "Diagnose: seit ${DAGE}s nicht geschrieben ($DIAG_FILE)" else warn "Diagnose: $DIAG_FILE fehlt" fi [ $QUIET -eq 1 ] || { [ $FAIL -eq 0 ] && echo "${G}${B}Alles in Ordnung.${N}" || echo "${R}${B}Fehler – siehe oben.${N}"; } exit $FAIL