#!/bin/bash # Startet die Telefonannahme: FreeSWITCH nimmt Anrufe an, pipe.watch verarbeitet # die Aufnahmen. Prueft vorher, was erfahrungsgemaess schiefgeht. set -uo pipefail PROJEKT="$(cd "$(dirname "$0")/.." && pwd)" cd "$PROJEKT" # Werte aus der .env uebernehmen (FS_PORT, FS_ETC, OLLAMA_URL, LEITSTAND_PORT). # Wie in pipe/config.py gewinnt eine bereits gesetzte Umgebungsvariable - so # bleibt ein einmaliges "FS_PORT=8021 ./telefon/starten.sh" moeglich. if [ -f .env ]; then while IFS= read -r zeile; do case "$zeile" in ''|'#'*) continue ;; *=*) : ;; *) continue ;; esac schluessel="${zeile%%=*}" wert="${zeile#*=}" wert="${wert%\"}"; wert="${wert#\"}" [ -n "${!schluessel:-}" ] || export "$schluessel=$wert" done < .env fi # Rohe Prozess-Logs (stdout/stderr von watcher/leitstand/monitor + die # eigene Ausgabe hier, wenn per launchd-Watchdog alle 5 Min aufgerufen) # wachsen sonst unbegrenzt - anders als verlauf.log (protokoll.kuerze(), nach # jedem Anruf) und audit.log (bewusst permanent, siehe pipe/audit.py). Ab # 2 MB auf die letzten 2000 Zeilen kuerzen, in-place (gleiches Inode) - sicher # fuer >>-Redirects (O_APPEND springt bei jedem Schreiben ans Dateiende, # unabhaengig vom vorherigen Offset). LOG_GRENZE_BYTES=2000000 for log in telefon/autostart.log telefon/watcher.log telefon/leitstand.log telefon/monitor.log; do if [ -f "$log" ] && [ "$(wc -c < "$log" 2>/dev/null || echo 0)" -gt "$LOG_GRENZE_BYTES" ]; then tail -n 2000 "$log" > "$log.tmp" 2>/dev/null && cat "$log.tmp" > "$log" && rm -f "$log.tmp" fi done # Stoerungswache als Erstes - sie muss gerade dann laufen, wenn der Rest nicht # hochkommt (Vorabpruefung schlaegt fehl, FreeSWITCH stirbt, Trunk registriert # nicht). Frueher startete sie erst nach Trunk UP und fehlte damit genau bei # Ausfaellen: 2026-09-14 war die Leitung nach einem Neustart ~80 min tot, ohne # Alarm. Sie meldet FreeSWITCH/Trunk selbst (pipe.dienste), beim normalen # Hochfahren hoechstens einmal "gestoert" und kurz darauf "wieder ok". if pgrep -f "[p]ipe.monitor" >/dev/null; then echo "Stoerungswache laeuft bereits." else nohup python3 -u -m pipe.monitor >> telefon/monitor.log 2>&1 & echo "Stoerungswache gestartet (Log: telefon/monitor.log)" fi fehler=0 melde() { printf ' %-38s %s\n' "$1" "$2"; } echo "Vorabpruefung" # Ollama - ohne Kategorisierung bleibt jeder Anruf liegen. if curl -s --max-time 5 "${OLLAMA_URL:-http://127.0.0.1:11434}/api/version" >/dev/null 2>&1; then melde "Ollama" "laeuft" else melde "Ollama" "FEHLT - 'ollama serve &' starten"; fehler=1 fi # Ansage if [ -f telefon/ansage.wav ]; then melde "Ansage" "$(ffprobe -v error -show_entries format=duration -of csv=p=0 telefon/ansage.wav | cut -c1-4) s" else melde "Ansage" "FEHLT - telefon/ansage_bauen.sh ausfuehren"; fehler=1 fi # Trunk-Konfiguration FS_ETC="${FS_ETC:-/opt/homebrew/etc/freeswitch}" FS_PORT="${FS_PORT:-8099}" # Event-Socket; 8021 ist auf diesem Mac belegt if [ -f "$FS_ETC/sip_profiles/external/plusnet.xml" ]; then melde "Trunk-Konfiguration" "eingerichtet" else melde "Trunk-Konfiguration" "FEHLT - telefon/freeswitch/einrichten.sh"; fehler=1 fi # Telephone.app streitet sich mit uns um dieselbe SIP-Registrierung. if pgrep -xq Telephone; then melde "Telephone.app" "LAEUFT - beenden, sonst Registrierungskonflikt"; fehler=1 else melde "Telephone.app" "beendet" fi [ $fehler -eq 0 ] || { echo; echo "Abbruch - erst die Punkte oben klaeren."; exit 1; } echo if pgrep -xq freeswitch; then echo "FreeSWITCH laeuft bereits." else echo "Starte FreeSWITCH ..." # -conf ist noetig: FreeSWITCH liest sonst aus dem Cellar-Verzeichnis, das # jedes Homebrew-Upgrade ueberschreibt. $FS_ETC bleibt bestehen. # -rp: Echtzeit-Priorität. Auf einem Rechner, der nebenher Whisper und ein # Sprachmodell laufen lässt, kommt FreeSWITCH sonst nicht rechtzeitig dran und # das Audio stückelt - RTP will alle 20 ms ein Paket losschicken. # -nonat: keine NAT-PMP/UPnP-Erkennung am Router (192.168.2.1:5351). Unter # launchd (Watchdog) blockiert macOS' "Lokales Netzwerk"-Datenschutz jedes # Senden ins LAN mit EPIPE; so frueh im Start ignoriert FreeSWITCH SIGPIPE # noch nicht und stirbt 0,5 s nach dem Backgrounding - ohne Log, ohne # Crash-Report. Aus einer Terminal-Shell passiert das nicht, deshalb lief es # manuell. Der Router antwortet ohnehin nicht (nat_map status: UNKNOWN); die # externe IP kommt per STUN (vars.xml), Trunks und Sockets brauchen kein LAN. freeswitch -nc -rp -nonat -nonatmap \ -conf "$FS_ETC" \ -log /opt/homebrew/var/log/freeswitch \ -db /opt/homebrew/var/lib/freeswitch/db \ -run /opt/homebrew/var/run/freeswitch # FreeSWITCH braucht nach dem Backgrounding ~40 s, bis Sofia die Profile hat. sleep 15 fi # Oeffentliche IP abgleichen. FreeSWITCH holt sie per STUN nur beim Laden der # XML (vars.xml, stun-set) und meldet sie Plusnet als Contact. Nach einer # Zwangstrennung des Routers mit neuer IP bleibt der Trunk trotzdem REGED/UP # (Registrierung und Pings gehen raus), aber eingehende INVITEs laufen an die # alte Adresse - die ganze Leitung ist stumm, ohne Fehler im Log. So am # 2026-09-17 passiert: Contact 9.246.125.72, tatsaechlich 217.142.18.120. # Bei Abweichung: reloadxml (STUN neu) + external-Profil neu starten, nur wenn # gerade kein Gespraech laeuft - sonst im naechsten Watchdog-Lauf. aktuell=$(fs_cli -P "$FS_PORT" -x "stun stun.freeswitch.org" 2>/dev/null | head -1 | cut -d: -f1) gemeldet=$(fs_cli -P "$FS_PORT" -x "sofia status profile external" 2>/dev/null \ | awk '/^Ext-SIP-IP/{print $2}') if [[ "$aktuell" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && [ -n "$gemeldet" ] \ && [ "$aktuell" != "$gemeldet" ]; then gespraeche=$(fs_cli -P "$FS_PORT" -x "show calls count" 2>/dev/null | awk '/total/{print $1}') if [ "${gespraeche:-0}" = "0" ]; then echo "$(date '+%F %T') Oeffentliche IP geaendert ($gemeldet -> $aktuell) - external-Profil wird neu gestartet." fs_cli -P "$FS_PORT" -x "reloadxml" >/dev/null 2>&1 fs_cli -P "$FS_PORT" -x "sofia profile external restart" >/dev/null 2>&1 sleep 10 else echo "$(date '+%F %T') Oeffentliche IP geaendert ($gemeldet -> $aktuell), aber $gespraeche Gespraech(e) aktiv - naechster Lauf." fi fi # Auf die Registrierung beim Provider warten. # Quelle ist fs_cli, nicht das Log: das Log wird zum Testen geleert und taugt # dann nicht mehr als Zustandsquelle. echo -n "Warte auf Trunk-Registrierung " status="" for i in $(seq 1 30); do wert=$(fs_cli -P "$FS_PORT" -x "sofia status gateway plusnet" 2>/dev/null \ | awk '/^Status/{print $2}') case "$wert" in UP) status="UP"; break ;; DOWN) : ;; # noch im Aufbau, weiter warten esac echo -n "."; sleep 2 done echo fs_cli -P "$FS_PORT" -x "sofia status gateway plusnet" 2>/dev/null \ | grep -E '^(Name|State|Status)' || echo " (fs_cli antwortet nicht)" if [ "${status:-}" = "UP" ]; then # Watcher und Leitstand mitstarten, falls sie nicht schon laufen. if pgrep -f "[p]ipe.watch" >/dev/null; then echo "Verarbeitung laeuft bereits." else # nice 10: Transkription und Kategorisierung dürfen warten, ein laufendes # Telefonat nicht. Auf 16 GB RAM konkurrieren beide sonst um die Maschine. nohup nice -n 10 python3 -u -m pipe.watch >> telefon/watcher.log 2>&1 & echo "Verarbeitung gestartet (Log: telefon/watcher.log)" fi if pgrep -f "[p]ipe.server" >/dev/null; then echo "Leitstand laeuft bereits." else nohup python3 -u -m pipe.server >> telefon/leitstand.log 2>&1 & sleep 2 tail -1 telefon/leitstand.log fi # Pipecat-Testleitung (Nebenstelle 7501) - nur wenn konfiguriert, siehe # dialog/pipecat_bootstrap.py und dialplan/default/06_agentzwei_pipecat_test.xml. if [ -n "${ASTRA_TELEFON_WS_URL:-}" ]; then if pgrep -f "[d]ialog.pipecat_bootstrap" >/dev/null; then echo "Pipecat-Bootstrap laeuft bereits." else # Aus der Projekt-venv: greenswitch/gevent fehlen im System-python3, # dort bricht der Bootstrap sofort mit ModuleNotFoundError ab. nohup .venv/bin/python -u -m dialog.pipecat_bootstrap >> telefon/pipecat_bootstrap.log 2>&1 & echo "Pipecat-Bootstrap gestartet (Log: telefon/pipecat_bootstrap.log)" fi fi sleep 2 echo ip=$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null) echo "Bereit. Leitstand: http://${ip:-127.0.0.1}:${LEITSTAND_PORT:-8088}" else echo echo "Trunk ist NICHT registriert. Log:" echo " tail -f /opt/homebrew/var/log/freeswitch/freeswitch.log" exit 1 fi