nex-speech: Sprachdienste für NEX (whisper.cpp CUDA 12 + Piper) als Compose und Ansible-Rolle
ci / test (push) Successful in 29s

- deploy/compose/nex-speech: whisper-server v1.9.4 (sm_52/sm_61, Port 8178, -l de -bs 5
  --convert, tmpfs, Aufwärmen mit 1 s Stille) und OpenAI-kompatibler Piper-Wrapper
  (Port 8179, mp3/opus/aac/flac/pcm gestreamt, wav, PCM 24 kHz mit Rate-Header)
- Host-Netz, damit ufw greift; keine Texte in Logs, kein Access-Log
- Rolle nex-speech + Playbook (nur gpu-pve52): ufw, Modelle mit sha256, Compose bauen/starten
- compose-check um nex-speech erweitert, community.docker in requirements.yml

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Rapp
2026-10-12 20:41:11 +02:00
co-authored by Claude Opus 5.5
parent b66c4c37cc
commit afc5fa0360
13 changed files with 643 additions and 1 deletions
@@ -0,0 +1,57 @@
#!/bin/sh
# Start von whisper-server für NEX (Spracherkennung, OpenAI-kompatibel).
# - Parameter laut Auftrag: -l de -bs 5 --convert, ohne --print-realtime (kein Text im Log).
# - Temp-Dateien nur im tmpfs; Reste (Fehlerpfad von whisper-server) werden regelmäßig gelöscht.
# - Nach dem Start einmal mit 1 s Stille aufwärmen.
set -eu
MODEL="${WHISPER_MODEL:-/models/whisper/ggml-large-v3-turbo.bin}"
PORT="${WHISPER_PORT:-8178}"
HOST="${WHISPER_HOST:-0.0.0.0}"
TMP_DIR="${WHISPER_TMP_DIR:-/run/nex-whisper}"
BEAM="${WHISPER_BEAM_SIZE:-5}"
LANGUAGE="${WHISPER_LANGUAGE:-de}"
# Zusätzliche Schalter, z. B. "-nfa" (Flash-Attention aus) oder "-t 4"
EXTRA="${WHISPER_EXTRA_ARGS:-}"
mkdir -p "$TMP_DIR"
chmod 0700 "$TMP_DIR"
# Aufräumer: Dateien älter als 10 Minuten im tmpfs löschen
( while sleep 300; do find "$TMP_DIR" -type f -mmin +10 -delete 2>/dev/null || true; done ) &
cleaner=$!
# shellcheck disable=SC2086
whisper-server -m "$MODEL" -l "$LANGUAGE" -bs "$BEAM" --convert --tmp-dir "$TMP_DIR" \
--inference-path /v1/audio/transcriptions --host "$HOST" --port "$PORT" $EXTRA &
pid=$!
stop() { kill -TERM "$pid" 2>/dev/null || true; kill "$cleaner" 2>/dev/null || true; }
trap stop TERM INT
# Warten, bis das Modell geladen ist (/health liefert bis dahin 503)
i=0
until curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; do
kill -0 "$pid" 2>/dev/null || { echo "whisper-server beendet sich beim Start" >&2; wait "$pid" || exit $?; exit 1; }
i=$((i + 1))
[ "$i" -le 600 ] || { echo "whisper-server nach 600 s nicht bereit" >&2; stop; exit 1; }
sleep 1
done
# Aufwärmen: 1 s Stille, Ergebnis wird verworfen
warm="$TMP_DIR/warmup.wav"
if ffmpeg -f lavfi -i anullsrc=r=16000:cl=mono -t 1 -c:a pcm_s16le -y "$warm" \
&& curl -fsS -o /dev/null -F "file=@${warm}" -F response_format=json \
"http://127.0.0.1:${PORT}/v1/audio/transcriptions"; then
echo "whisper-server bereit und aufgewärmt (Port ${PORT})"
else
echo "Aufwärmen fehlgeschlagen, Dienst läuft trotzdem" >&2
fi
rm -f "$warm"
# Auf whisper-server warten; nach einem Signal erneut warten, bis er wirklich beendet ist
rc=0
wait "$pid" || rc=$?
while kill -0 "$pid" 2>/dev/null; do wait "$pid" || rc=$?; done
kill "$cleaner" 2>/dev/null || true
exit "$rc"