mirror of
https://github.com/Jeuners/Logpy-AgentOne.git
synced 2026-09-09 15:02:33 +02:00
Dienste-Ampel (Telefon, Verarbeitung, Spracherkennung, Nextcloud) alarmierte bisher nur im offenen Leitstand. pipe/monitor.py laeuft jetzt als eigener Dauerprozess, prueft dieselbe Ampel (neu ausgelagert nach pipe/dienste.py) periodisch und alarmiert bei Stoerung lokal per macOS-Benachrichtigung + Ton (pipe/alarm.py) - unabhaengig davon, ob pipe.watch oder pipe.server laufen. Wird ab jetzt von telefon/starten.sh mitgestartet. Verschluesselung at rest: keine App-eigene Verschluesselung eingebaut, stattdessen im README als Betreiber-Pflicht dokumentiert (FileVault aktivieren) - App verlaesst sich auf Festplattenverschluesselung durch das Betriebssystem. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
"""Zustand der beteiligten Dienste (Telefon, Verarbeitung, Spracherkennung,
|
|
Nextcloud) — geteilt zwischen Leitstand (pipe.server) und Störungswache
|
|
(pipe.monitor), damit beide dieselbe Sicht auf "läuft/steht" haben.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
from datetime import datetime
|
|
|
|
from . import config, store
|
|
|
|
# Statusabfragen sind teuer (Netz, Unterprozesse) - kurz zwischenspeichern,
|
|
# damit haeufiges Nachfragen den Rechner nicht belastet.
|
|
_CACHE: dict[str, tuple[float, dict]] = {}
|
|
CACHE_S = 4.0
|
|
|
|
|
|
def _laeuft(muster: str) -> bool:
|
|
try:
|
|
return subprocess.run(["pgrep", "-f", muster], capture_output=True).returncode == 0
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def _http_ok(url: str, timeout: float = 5.0) -> bool:
|
|
"""Erreichbarkeitstest. Mit eigenem User-Agent - ein WAF vor Nextcloud
|
|
blockt den Standardnamen von urllib und meldete fälschlich 'nicht
|
|
erreichbar'."""
|
|
req = urllib.request.Request(url, headers={"User-Agent": "praxis-telefon-agent/1.0"})
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=timeout, context=store._ssl_kontext()):
|
|
return True
|
|
except urllib.error.HTTPError:
|
|
return True # antwortet - reicht als Lebenszeichen
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def _trunk() -> tuple[str, str]:
|
|
"""(zustand, text) des SIP-Trunks."""
|
|
if not _laeuft("[f]reeswitch"):
|
|
return "aus", "FreeSWITCH läuft nicht"
|
|
try:
|
|
roh = subprocess.run(
|
|
["fs_cli", "-P", config.FS_PORT, "-x", "sofia status gateway plusnet"],
|
|
capture_output=True, text=True, timeout=8).stdout
|
|
for zeile in roh.splitlines():
|
|
if zeile.startswith("Status"):
|
|
wert = zeile.split()[-1]
|
|
return ("gut", "registriert") if wert == "UP" else ("schlecht", f"Trunk {wert}")
|
|
return "unklar", "Gateway unbekannt"
|
|
except Exception:
|
|
return "unklar", "fs_cli nicht erreichbar"
|
|
|
|
|
|
def status() -> dict:
|
|
jetzt = time.time()
|
|
gepuffert = _CACHE.get("status")
|
|
if gepuffert and jetzt - gepuffert[0] < CACHE_S:
|
|
return gepuffert[1]
|
|
|
|
trunk_zustand, trunk_text = _trunk()
|
|
watcher = _laeuft("[p]ipe.watch")
|
|
ollama = _http_ok(f"{config.OLLAMA_URL}/api/version")
|
|
nextcloud = (_http_ok(f"{config.NEXTCLOUD_URL}/status.php")
|
|
if config.nextcloud_aktiv() else None)
|
|
|
|
offen = 0
|
|
if config.TELEFON_EINGANG.is_dir():
|
|
offen = sum(1 for d in config.TELEFON_EINGANG.iterdir()
|
|
if d.is_file() and not d.name.startswith("."))
|
|
fehler = 0
|
|
if config.TELEFON_FEHLER.is_dir():
|
|
fehler = sum(1 for d in config.TELEFON_FEHLER.iterdir() if d.is_file())
|
|
|
|
daten = {
|
|
"dienste": [
|
|
{"name": "Telefon", "zustand": trunk_zustand, "text": trunk_text},
|
|
{"name": "Verarbeitung", "zustand": "gut" if watcher else "aus",
|
|
"text": "beobachtet Eingang" if watcher else "Watcher läuft nicht"},
|
|
{"name": "Spracherkennung", "zustand": "gut" if ollama else "schlecht",
|
|
"text": "Ollama bereit" if ollama else "Ollama nicht erreichbar"},
|
|
{"name": "Nextcloud", "zustand": "unklar" if nextcloud is None
|
|
else ("gut" if nextcloud else "schlecht"),
|
|
"text": "nicht konfiguriert" if nextcloud is None
|
|
else ("erreichbar" if nextcloud else "nicht erreichbar")},
|
|
],
|
|
"eingang_offen": offen,
|
|
"fehler": fehler,
|
|
"stand": datetime.now().strftime("%H:%M:%S"),
|
|
}
|
|
_CACHE["status"] = (jetzt, daten)
|
|
return daten
|