monitoring: BSN Health Check mit Testschleife (snkbnet + CF-Europa-Re-Routing)
Quality Gate / 🛡️ Lint + Type Check + Tests (push) Failing after 14m50s

- Erweitert um snkbnet.de-Check: Server- vs Cloudflare-Problem-Differenzierung
- CF-Status-Seiten-Scraping auf Europa-Re-Routing (HTML + Components-API)
- Gleiche Logik: still bei OK, Alarm nur bei Problemen
This commit is contained in:
Hermes Agent
2026-06-29 09:35:49 +02:00
parent fd42514901
commit 5edc5d49a8
+255
View File
@@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""BSN Health Check — Überwacht brettspiel-news.de + Cloudflare-Status.
Ausgabe: LEER wenn alles OK. Nur bei Problemen wird ein Alarm-Text ausgegeben.
Läuft als Hermes-Cronjob (no_agent=True, alle 5 Minuten).
Überwachte Fehlertypen:
- HTTP 5xx mit CF-Ray-Header (Cloudflare-Fehlerseiten)
- HTTP 502/521/522/523/524/525/526/530 (spezifische CF-Error-Codes)
- Cloudflare-Status-API: ungelöste Incidents (gefiltert auf CDN/DNS/Routing)
- Komplettausfall (keine HTTP-Antwort)
Testschleife (neu):
1. Wenn Seite offline/CF-Fehler → prüfe Alternativ-URL (wp.snkbnet.de)
→ Erreichbar = Server läuft, Cloudflare-Problem wahrscheinlich
2. Cloudflare-Status-Seite scrapen → Europa-Re-Routing erkennen
"""
import json
import re
import subprocess
from datetime import datetime, timezone
TARGET = "https://www.brettspiel-news.de/"
SNKBNET_URL = "https://wp.snkbnet.de/login_up.php?success_redirect_url=%2Fsmb%2Fweb%2Fview"
CF_STATUS_API = "https://www.cloudflarestatus.com/api/v2"
CF_STATUS_PAGE = "https://www.cloudflarestatus.com/"
TIMEOUT = 15
# Cloudflare-Fehlermuster (Body-Scan nach HTTP 5xx)
CF_ERROR_PATTERNS = [
("Error 502", "502 Bad Gateway — Origin-Server nicht erreichbar"),
("Error 521", "521 Web Server Down — Server antwortet nicht"),
("Error 522", "522 Connection Timeout — Timeout zum Origin"),
("Error 523", "523 Origin Unreachable — Origin nicht erreichbar"),
("Error 524", "524 A Timeout Occurred — Timeout"),
("Error 525", "525 SSL Handshake Failed — SSL-Fehler"),
("Error 526", "526 Invalid SSL Certificate — Ungültiges Zertifikat"),
("Error 530", "530 Origin DNS Error — DNS-Fehler"),
("cloudflare-nginx", "Cloudflare-nginx — CF-Fehlerseite"),
("Bad gateway", "Bad Gateway — CF-Proxy-Fehler"),
]
# Cloudflare-Services die NICHTS mit Website-Erreichbarkeit zu tun haben
IRRELEVANT_SERVICES = [
"workers ai", "workers kv", "durable objects", "d1", "r2",
"pages", "images", "stream", "zero trust", "access",
"spectrum", "magic", "warp",
"analytics", "logs", "dashboard", "registrar",
"billing", "support case", "ssl for saas", "custom certificates"
]
def curl(url, timeout=TIMEOUT):
"""Führe curl aus und gib Status, Header, Body zurück."""
try:
result = subprocess.run(
["curl", "-sL", "-o", "/tmp/bsn_health_body.tmp",
"-w", "%{http_code}|%{time_total}|%{size_download}",
"-D", "/tmp/bsn_health_headers.tmp",
"--max-time", str(timeout), url],
capture_output=True, text=True, timeout=timeout + 5
)
parts = result.stdout.strip().split("|")
http_code = int(parts[0]) if parts and parts[0].isdigit() else 0
time_total = float(parts[1]) if len(parts) > 1 else 0
size = int(parts[2]) if len(parts) > 2 else 0
body = headers = ""
try:
with open("/tmp/bsn_health_body.tmp", "r", errors="replace") as f:
body = f.read()[:5000]
with open("/tmp/bsn_health_headers.tmp", "r", errors="replace") as f:
headers = f.read()[:2000]
except Exception:
pass
return {"http_code": http_code, "time": time_total, "size": size,
"body": body, "headers": headers, "ok": True}
except Exception as e:
return {"http_code": 0, "time": 0, "size": 0,
"body": "", "headers": "", "ok": False, "error": str(e)}
def check_snkbnet():
"""Testschleife: Prüfe Alternativ-URL auf demselben Server.
Wenn wp.snkbnet.de erreichbar ist, läuft der Server grundsätzlich —
das Problem liegt dann mit hoher Wahrscheinlichkeit bei Cloudflare.
Returns:
dict mit 'reachable' (bool), 'http_code', 'time', 'error'
"""
r = curl(SNKBNET_URL)
if not r["ok"]:
return {"reachable": False, "http_code": 0, "time": 0,
"error": r.get("error", "Unbekannt")}
# Erfolg: Alles außer 5xx gilt als erreichbar
# (302/303 Redirects bei Login sind normal und bedeuten Server läuft)
is_reachable = r["http_code"] < 500 and r["http_code"] > 0
return {"reachable": is_reachable, "http_code": r["http_code"],
"time": r["time"], "error": None}
def check_cf_europe_rerouting():
"""Scrape cloudflarestatus.com auf Europa-Re-Routing.
Sucht die HTML-Seite nach Hinweisen auf Rerouting in Europa.
Cloudflare listet regionale Status in der Komponenten-Tabelle.
Returns:
str | None: Alarm-Text bei Fund, None sonst
"""
r = curl(CF_STATUS_PAGE)
if not r["ok"] or r["http_code"] != 200:
return None
html = r["body"].lower()
alerts = []
# Suche nach "re-routed" oder "partially re-routed" im Europa-Kontext
# Pattern: Im HTML gibt's Tabellen/Divs mit Region+Status
reroute_patterns = [
(r"europe.*?re-routed", "🟡 Europa: Re-Routed erkannt"),
(r"europe.*?partially re-routed", "🟠 Europa: Partially Re-Routed"),
(r"re-routed.*?europe", "🟡 Re-Routed in Europa"),
(r"partially re-routed.*?europe", "🟠 Partially Re-Routed in Europa"),
]
for pattern, desc in reroute_patterns:
if re.search(pattern, html, re.DOTALL):
alerts.append(f"{desc}")
# Alternativ: Prüfe Components-API auf Europa-Status
try:
comp_r = curl(CF_STATUS_API + "/components.json")
if comp_r["ok"]:
data = json.loads(comp_r["body"])
components = data.get("components", [])
for comp in components:
name = comp.get("name", "").lower()
status = comp.get("status", "")
# "partial_outage" oder "degraded_performance" im Europa-Kontext
if ("europe" in name or "europa" in name) and \
status in ("partial_outage", "degraded_performance", "major_outage"):
alerts.append(
f" • CF-Komponente [{status}]: {comp.get('name', '?')}"
)
except Exception:
pass
return "\n".join(alerts) if alerts else None
def check_cf_status():
"""Prüfe Cloudflare-Status-API auf ungelöste Incidents."""
try:
r = curl(CF_STATUS_API + "/incidents/unresolved.json")
if not r["ok"]:
return None
data = json.loads(r["body"])
incidents = data.get("incidents", [])
if not incidents:
return None
alerts = []
for inc in incidents:
name = inc.get("name", "").lower()
if any(kw in name for kw in IRRELEVANT_SERVICES):
continue
impact = inc.get("impact", "unknown")
link = inc.get("shortlink", "")
alerts.append(f"🔴 CF-Incident [{impact}]: {inc.get('name', '?')}\n {link}")
return "\n".join(alerts) if alerts else None
except Exception:
return None
def main():
now = datetime.now(timezone.utc).strftime("%d.%m.%Y %H:%M UTC")
alerts = []
r = curl(TARGET)
has_problem = False
# --- Fall 1: Komplettausfall ---
if not r["ok"]:
has_problem = True
alerts.append(f"❌ brettspiel-news.de NICHT ERREICHBAR\n Fehler: {r.get('error', 'Unbekannt')}")
# --- Fall 2: HTTP-Fehler ---
elif r["http_code"] >= 500:
has_problem = True
has_cf = "cf-ray" in r["headers"].lower()
if has_cf:
cf_ray = ""
for line in r["headers"].split("\n"):
if line.lower().startswith("cf-ray:"):
cf_ray = line.split(":", 1)[1].strip()
break
found = []
for pattern, desc in CF_ERROR_PATTERNS:
if pattern.lower() in r["body"].lower():
found.append(f"{desc}")
alerts.append(
f"❌ brettspiel-news.de: HTTP {r['http_code']} (CF-Fehler)\n"
f" CF-Ray: {cf_ray}\n" + ("\n".join(found) if found else " CF-Fehlerseite erkannt")
)
else:
alerts.append(f"⚠️ brettspiel-news.de: HTTP {r['http_code']} (Server-Fehler)")
elif r["http_code"] == 403:
has_problem = True
alerts.append(f"⚠️ brettspiel-news.de: HTTP 403 (möglicher CF-Block/Challenge)")
elif r["http_code"] == 0 or r["http_code"] >= 600:
has_problem = True
alerts.append(f"❌ brettspiel-news.de: Keine gültige HTTP-Antwort (Code: {r['http_code']})")
# --- Testschleife: Nur bei Problemen ---
if has_problem:
# 1. Prüfe Alternativ-URL (gleicher Server, andere Domain)
snkb = check_snkbnet()
if snkb["reachable"]:
alerts.append(
f"\n🧪 Testschleife:\n"
f" ✅ Alternativ-URL erreichbar (HTTP {snkb['http_code']}, {snkb['time']:.2f}s)\n"
f" 📡 Server läuft — Cloudflare-Problem wahrscheinlich"
)
else:
alerts.append(
f"\n🧪 Testschleife:\n"
f" ❌ Alternativ-URL NICHT erreichbar"
+ (f" (Fehler: {snkb['error']})" if snkb.get('error') else "")
+ f"\n ⚠️ Server selbst könnte betroffen sein"
)
# 2. Cloudflare-Status-Seite auf Europa-Re-Routing prüfen
cf_europe = check_cf_europe_rerouting()
if cf_europe:
alerts.append(f"\n🌍 Cloudflare-Status (Europa):\n{cf_europe}")
else:
alerts.append("\n🌍 Cloudflare-Status: Kein Europa-Re-Routing erkannt")
# --- CF-Status-API immer mitprüfen ---
cf_alert = check_cf_status()
if cf_alert:
alerts.append(f"\n📢 Cloudflare-Incidents:\n{cf_alert}")
# --- Ausgabe ---
if has_problem:
print(f"🚨 BSN-Health-Alarm {now}\n" + "\n".join(alerts))
# Sonst: still (kein stdout → kein Alarm)
if __name__ == "__main__":
main()