#!/usr/bin/env bash # Download the frank-tts-engine payload: the two GGUF files the arm A engine # (qwentts.cpp `tts-server`, dankclaw/voiceservice/Containerfile.engine) loads # once at startup — Serveurperso/Qwen3-TTS-GGUF's **1.7 B base talker at # Q4_K_M** and the **12 Hz tokenizer at Q8_0**. Public repo — no auth header, # and never a token on the command line (world-readable in /proc//cmdline). # # THE BASE VARIANT IS MANDATORY, NOT A PREFERENCE. The engine refuses every # voice registration unless the loaded model reports type "base" # (tools/tts-server.cpp, register_voice): on a customvoice or voicedesign talker # the cloned-voice product does not exist. The 0.6 B talker (628,905,056 B for # its Q4_K_M) also exists and is NOT the decided artifact — 1.7 B is what live # output was measured with on 2026-10-18. # # Fresh downloads, no resume (the same rule the frank-zealot fetcher learned the # hard way: `curl -C -` once produced an oversized file on a multi-GB payload); # -f/--fail-with-body so an HTTP error is never a success; then exact byte count # and sha256 against the Hub's own LFS oid. This repo IS LFS, so the sha values # below are the pointer oids the Hub publishes — verified at fetch, not # computed locally. Idempotent: a complete, correctly-hashed file is skipped, # and `--verify-only` reports without downloading (that is the mode the deployer # and the runbook use to answer "are the weights there?"). # # Both files together are 1.4 GiB and the engine mmaps them: peak RSS measured # 5.7 GiB with both resident. Nothing here is deleted on a swap — the previous # talker stays on disk, because the rollback for a bad engine build is to point # QWENTTS_MODEL at a different file, not to re-download 1.4 GiB under pressure. # # Usage: # scripts/download-frank-tts-engine-qwentts.sh [DEST_DIR] [--verify-only] # Default DEST_DIR is the host directory the frank-tts-engine unit mounts # read-only at /models. set -euo pipefail DEST="" VERIFY_ONLY=0 for arg in "$@"; do case "$arg" in --verify-only) VERIFY_ONLY=1 ;; *) DEST="$arg" ;; esac done DEST="${DEST:-/home/dank/models/Qwen3-TTS-GGUF}" # repo | hub-name | dest-name | bytes | sha256 (Hub LFS oid, resolved 2026-10-18) FILES=( "Serveurperso/Qwen3-TTS-GGUF|qwen-talker-1.7b-base-Q4_K_M.gguf|qwen-talker-1.7b-base-Q4_K_M.gguf|1219245248|ea393ebaf2167ea23ce9fc18b093822851358a950d7075cd47ab4f6ce23e887d" "Serveurperso/Qwen3-TTS-GGUF|qwen-tokenizer-12hz-Q8_0.gguf|qwen-tokenizer-12hz-Q8_0.gguf|291150624|1883beeed99348fc35e23dd225e9082f93f6f8c109330a33d935baa8acdbfd94" ) mkdir -p "$DEST" status=0 for entry in "${FILES[@]}"; do IFS='|' read -r repo hubname destname size sha <<<"$entry" url="https://huggingface.co/${repo}/resolve/main/${hubname}" f="$DEST/$destname" have=0 if [[ -f "$f" ]] && [[ "$(stat -c%s "$f")" == "$size" ]]; then if sha256sum -c --quiet <(printf '%s %s\n' "$sha" "$f"); then have=1 fi fi if (( have )); then echo "OK $destname" continue fi if (( VERIFY_ONLY )); then echo "MISSING $destname" status=1 continue fi echo "FETCHING $destname (from $repo)" tmp="$f.part" curl -fsSL --fail-with-body -o "$tmp" "$url" mv "$tmp" "$f" if [[ "$(stat -c%s "$f")" != "$size" ]]; then echo "SIZE FAIL $destname ($(stat -c%s "$f") != $size)" status=1 continue fi if ! sha256sum -c --quiet <(printf '%s %s\n' "$sha" "$f"); then echo "SHA FAIL $destname" status=1 continue fi echo "VERIFIED $destname" done if (( status == 0 )); then echo "DOWNLOAD_DONE $DEST" else echo "DOWNLOAD_FAILED $DEST" >&2 fi exit "$status"