nex-speech: Sprachdienste für NEX (whisper.cpp CUDA 12 + Piper) als Compose und Ansible-Rolle
ci / test (push) Successful in 29s
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:
co-authored by
Claude Opus 5.5
parent
b66c4c37cc
commit
afc5fa0360
@@ -0,0 +1,6 @@
|
||||
# NEX-Sprachdienste (Task 6.2): Erkennung (whisper.cpp, M4000) und Ausgabe (Piper, CPU) in gpu-pve52.
|
||||
# Aufruf auf ops: ansible-playbook playbooks/nex-speech.yml
|
||||
# Vorher Zugang auffrischen (DHCP): bash ../scripts/refresh-vm-access.sh
|
||||
- name: NEX-Sprachdienste
|
||||
hosts: "{{ nex_speech_hosts | default('gpu-pve52') }}"
|
||||
roles: [nex-speech]
|
||||
@@ -1,3 +1,4 @@
|
||||
collections:
|
||||
- name: ansible.posix
|
||||
- name: community.general
|
||||
- name: community.docker
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# NEX-Sprachdienste (Task 6.2): whisper-server (GPU) und Piper (CPU) als Docker Compose.
|
||||
# Compose-Dateien kommen aus deploy/compose/nex-speech, Modelle nach /srv/models (Modellplatte).
|
||||
nex_speech_dir: /opt/nex-speech
|
||||
nex_speech_models_dir: /srv/models
|
||||
|
||||
# Firewall nur in der VM (ufw). Die Proxmox-Firewall bleibt aus.
|
||||
nex_speech_allow_from: 192.168.100.29 # appsrv08 (LiteLLM, Bridge-Netz erscheint mit der Host-IP)
|
||||
nex_speech_ssh_from: 192.168.100.0/24 # ops hat DHCP, deshalb das ganze Netz für SSH
|
||||
nex_speech_stt_port: 8178
|
||||
nex_speech_tts_port: 8179
|
||||
|
||||
# Werte für .env (siehe deploy/compose/nex-speech/.env.example)
|
||||
nex_speech_env:
|
||||
MODELS_DIR: "{{ nex_speech_models_dir }}"
|
||||
STT_PORT: "{{ nex_speech_stt_port }}"
|
||||
STT_GPU: "0"
|
||||
WHISPER_TAG: v1.9.4
|
||||
WHISPER_COMMIT: 927cfce34f31707e17f2bff35c349632fb9e2c3a
|
||||
CUDA_ARCHS: "52;61"
|
||||
WHISPER_MODEL_FILE: ggml-large-v3-turbo.bin
|
||||
WHISPER_BEAM_SIZE: "5"
|
||||
WHISPER_EXTRA_ARGS: ""
|
||||
TTS_PORT: "{{ nex_speech_tts_port }}"
|
||||
PIPER_TTS_VERSION: 1.8.0
|
||||
PIPER_VOICES: de_DE-thorsten-high,de_DE-kerstin-low
|
||||
PIPER_DEFAULT_VOICE: de_DE-thorsten-high
|
||||
PCM_SAMPLE_RATE: "24000"
|
||||
TTS_MAX_CHARS: "5000"
|
||||
TTS_PARALLEL: "2"
|
||||
|
||||
# Modelle mit fester Revision und Prüfsumme (sha256 laut Hugging Face, LFS-OID bzw. nachgerechnet)
|
||||
_hf_whisper: https://huggingface.co/ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1
|
||||
_hf_piper: https://huggingface.co/rhasspy/piper-voices/resolve/c10ece1aade47bb51c153c893d14e5bf8e5b7117/de/de_DE
|
||||
nex_speech_models:
|
||||
- dest: whisper/ggml-large-v3-turbo.bin
|
||||
url: "{{ _hf_whisper }}/ggml-large-v3-turbo.bin"
|
||||
checksum: sha256:1fc70f774d38eb169993ac391eea357ef47c88757ef72ee5943879b7e8e2bc69
|
||||
- dest: piper/de_DE-thorsten-high.onnx
|
||||
url: "{{ _hf_piper }}/thorsten/high/de_DE-thorsten-high.onnx"
|
||||
checksum: sha256:9df1c43c61149ef9b39e618e2b861fbe41e1fcea9390b2dac62e8761573ea4f1
|
||||
- dest: piper/de_DE-thorsten-high.onnx.json
|
||||
url: "{{ _hf_piper }}/thorsten/high/de_DE-thorsten-high.onnx.json"
|
||||
checksum: sha256:6de734444e4c3f9e33b7ebe2746dbc19b71e85f613e79c65acf623200b99a76a
|
||||
- dest: piper/de_DE-kerstin-low.onnx
|
||||
url: "{{ _hf_piper }}/kerstin/low/de_DE-kerstin-low.onnx"
|
||||
checksum: sha256:d352a7641892cebf2903859af94e9ba81a141110215fe3943bcda7f7da401b7a
|
||||
- dest: piper/de_DE-kerstin-low.onnx.json
|
||||
url: "{{ _hf_piper }}/kerstin/low/de_DE-kerstin-low.onnx.json"
|
||||
checksum: sha256:56e708556b7b9b7a53c4f8957e021421e69f11a600962bba554cffbe72cf2d47
|
||||
@@ -0,0 +1,102 @@
|
||||
# --- Firewall in der VM: SSH aus dem LAN, Sprachdienste nur von appsrv08, sonst nichts ---
|
||||
# Reihenfolge wichtig: SSH-Regel vor dem Einschalten, sonst sperrt sich Ansible aus.
|
||||
- name: ufw installieren
|
||||
ansible.builtin.apt:
|
||||
name: ufw
|
||||
state: present
|
||||
|
||||
- name: ufw – SSH aus dem LAN erlauben
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
from_ip: "{{ nex_speech_ssh_from }}"
|
||||
port: "22"
|
||||
proto: tcp
|
||||
comment: ssh-lan
|
||||
|
||||
- name: ufw – Sprachdienste nur von appsrv08
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
from_ip: "{{ nex_speech_allow_from }}"
|
||||
port: "{{ item.port | string }}"
|
||||
proto: tcp
|
||||
comment: "{{ item.name }}"
|
||||
loop:
|
||||
- {name: nex-stt, port: "{{ nex_speech_stt_port }}"}
|
||||
- {name: nex-tts, port: "{{ nex_speech_tts_port }}"}
|
||||
|
||||
- name: ufw – eingehend sonst ablehnen, ausgehend erlauben
|
||||
community.general.ufw:
|
||||
direction: "{{ item.direction }}"
|
||||
default: "{{ item.policy }}"
|
||||
loop:
|
||||
- {direction: incoming, policy: deny}
|
||||
- {direction: outgoing, policy: allow}
|
||||
|
||||
- name: ufw einschalten
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
|
||||
# Compose baut nur mit buildx (BuildKit); der Altbau-Builder ist in Docker 29 veraltet
|
||||
- name: docker-buildx installieren
|
||||
ansible.builtin.apt:
|
||||
name: docker-buildx
|
||||
state: present
|
||||
|
||||
# --- Modelle ---
|
||||
- name: Modellverzeichnisse
|
||||
ansible.builtin.file:
|
||||
path: "{{ nex_speech_models_dir }}/{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop: [whisper, piper]
|
||||
|
||||
- name: Modelle laden (Prüfsumme gegen Hugging Face)
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ item.url }}"
|
||||
dest: "{{ nex_speech_models_dir }}/{{ item.dest }}"
|
||||
checksum: "{{ item.checksum }}"
|
||||
mode: "0644"
|
||||
timeout: 120
|
||||
loop: "{{ nex_speech_models }}"
|
||||
loop_control:
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
# --- Compose-Projekt ---
|
||||
- name: Compose-Dateien kopieren
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../../../deploy/compose/nex-speech/"
|
||||
dest: "{{ nex_speech_dir }}/"
|
||||
mode: preserve
|
||||
directory_mode: "0755"
|
||||
register: nex_speech_files
|
||||
|
||||
- name: .env schreiben (keine Geheimnisse)
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ nex_speech_dir }}/.env"
|
||||
mode: "0644"
|
||||
content: |
|
||||
# Verwaltet von Ansible (Rolle nex-speech)
|
||||
{% for k, v in nex_speech_env.items() %}
|
||||
{{ k }}={{ v }}
|
||||
{% endfor %}
|
||||
register: nex_speech_envfile
|
||||
|
||||
# Bauen dauert beim ersten Mal lange (whisper.cpp mit CUDA auf 4 Kernen, ca. 20–40 min)
|
||||
- name: Images bauen und Dienste starten
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ nex_speech_dir }}"
|
||||
build: "{{ 'always' if (nex_speech_files.changed or nex_speech_envfile.changed) else 'policy' }}"
|
||||
state: present
|
||||
wait: false
|
||||
async: 3600
|
||||
poll: 30
|
||||
|
||||
- name: Warten, bis beide Dienste gesund sind
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ item }}/health"
|
||||
status_code: 200
|
||||
register: nex_speech_health
|
||||
until: nex_speech_health.status == 200
|
||||
retries: 60
|
||||
delay: 10
|
||||
loop: ["{{ nex_speech_stt_port }}", "{{ nex_speech_tts_port }}"]
|
||||
Reference in New Issue
Block a user