# Deploy Chatterbox Turbo (Resemble AI's low-latency English TTS w/ # voice cloning) via the devnen/Chatterbox-TTS-Server wrapper to # irv-ml1. # # Builds the image locally from devnen's Dockerfile.gpu via docker # buildx git URL context. ~8-10 min cold build (CUDA + torch + # Chatterbox deps). First start pulls Chatterbox-Turbo weights (~6 GB) # into the HF cache. # # Usage: # scripts/elway irv-ml1 --playbook playbooks/deploy-chatterbox.yaml # # Idempotent — every step is creates-/when-gated; rerun is safe. vars: compose_dir: /opt/docker/compose/chatterbox reference_dir: /worktank/chatterbox/reference_audio cache_dir: /worktank/chatterbox/cache host_port: "8196" steps: # ── host-side dirs ────────────────────────────────────────────────── - name: Ensure /worktank/chatterbox root exists (one-time, sudo) shell: mkdir -p /worktank/chatterbox sudo: true creates: /worktank/chatterbox - name: Chown /worktank/chatterbox to lkraven shell: chown lkraven:lkraven /worktank/chatterbox sudo: true when: '[ "$(stat -c %U /worktank/chatterbox)" != lkraven ]' - name: Ensure reference-audio dir exists shell: mkdir -p {{ reference_dir }} creates: "{{ reference_dir }}" - name: Ensure cache dir exists shell: mkdir -p {{ cache_dir }} creates: "{{ cache_dir }}" - name: Ensure compose dir exists shell: mkdir -p {{ compose_dir }} creates: "{{ compose_dir }}" # ── deploy compose files ──────────────────────────────────────────── - name: Upload compose.yaml upload: src: stacks/chatterbox/compose.yaml dest: "{{ compose_dir }}/compose.yaml" mode: "0644" - name: Seed .env from template (only if absent) upload: src: stacks/chatterbox/.env.example dest: "{{ compose_dir }}/.env" mode: "0644" when: "[ ! -f {{ compose_dir }}/.env ]" # ── build + bring up ──────────────────────────────────────────────── - name: docker compose build (~8-10 min first time; cached after) # See deploy-vibevoice.yaml for the rationale — same filter pattern. shell: | set -o pipefail cd {{ compose_dir }} && docker compose build --progress=plain 2>&1 \ | grep -vE '^#[0-9]+ [0-9.]+ (Downloading|Collecting|Requirement|Using cached|Installing collected|Successfully (installed|built)|Saved /|━|Resolved|Prepared|Built)' - name: docker compose up -d shell: cd {{ compose_dir }} && docker compose up -d - name: Wait for /api/model-info to report loaded:true (allow ~15 min for model download + warmup) # The devnen wrapper has no /health endpoint. /api/model-info returns # `{"loaded":true,...}` only after the model finishes loading, so it # doubles as a liveness + ready probe. shell: | for i in $(seq 1 180); do curl -sf --max-time 3 http://localhost:{{ host_port }}/api/model-info \ | grep -q '"loaded":true' && exit 0 sleep 5 done exit 1 changed_when: "false" verify: - name: /api/model-info reports loaded:true shell: curl -sf http://localhost:{{ host_port }}/api/model-info | grep -q '"loaded":true' changed_when: "false" - name: /v1/audio/voices returns the wrapper's status:ok + voices list # Devnen's response shape is `{"status":"ok","voices":["Abigail.wav",...]}` # — voices is an array of files in the predefined-voices dir. Assert # both fields rather than greping for any one voice name (the seed # set could change with upstream). shell: | curl -sf http://localhost:{{ host_port }}/v1/audio/voices \ | python3 -c "import json,sys; d=json.load(sys.stdin); assert d.get('status')=='ok' and isinstance(d.get('voices'), list)" changed_when: "false" - name: Container is running shell: docker inspect chatterbox --format '{{.State.Status}}' | grep -q running changed_when: "false"