#!/bin/sh # Downloads the configured GGML model on first start, then runs whisper-server. # Uses the GPU (Vulkan) automatically when one is present; set NO_GPU=1 # to force CPU. # # Both the freshly downloaded file and any existing file are validated # (minimum size + ggml magic bytes) so a truncated download or a saved # error/redirect page can never be mistaken for a model — a poisoned # file is deleted and re-downloaded instead. set -eu MODEL="${MODEL:-small}" THREADS="${THREADS:-4}" HOST="${HOST:-0.0.0.0}" PORT="${PORT:-8085}" MODEL_DIR="${MODEL_DIR:-/models}" FILE="$MODEL_DIR/ggml-$MODEL.bin" URL="https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-$MODEL.bin" # smallest supported model (ggml-tiny.bin) is ~75 MB MIN_SIZE=50000000 GGML_MAGIC="$(printf 'lmgg')" # GGML_FILE_MAGIC 0x67676d6c, little-endian is_valid_model() { [ -s "$1" ] || return 1 [ "$(stat -c%s "$1")" -ge "$MIN_SIZE" ] || return 1 [ "$(head -c 4 "$1")" = "$GGML_MAGIC" ] || return 1 return 0 } mkdir -p "$MODEL_DIR" if ! is_valid_model "$FILE" && [ -e "$FILE" ]; then echo "existing $FILE is invalid ($(stat -c%s "$FILE") bytes) — " \ "deleting and re-downloading" fi if ! is_valid_model "$FILE"; then rm -f "$FILE" "$FILE.part" echo "downloading $URL to $FILE ..." curl -fLS --retry 3 --retry-delay 5 -C - -o "$FILE.part" "$URL" if ! is_valid_model "$FILE.part"; then size="$(stat -c%s "$FILE.part" 2>/dev/null || echo 0)" echo "ERROR: download is not a valid model ($size bytes)." echo "------- first 400 bytes of the response (probably an error/redirect page):" head -c 400 "$FILE.part" echo "" echo "------- end of response" rm -f "$FILE.part" exit 1 fi mv "$FILE.part" "$FILE" echo "downloaded $(stat -c%s "$FILE") bytes, magic ok." fi GPU_FLAGS="" if [ "${NO_GPU:-0}" = "1" ]; then GPU_FLAGS="-ng" fi echo "starting whisper-server: model=$MODEL threads=$THREADS host=$HOST port=$PORT gpu=auto" exec whisper-server \ -m "$FILE" \ -l auto \ -t "$THREADS" \ $GPU_FLAGS \ --host "$HOST" \ --port "$PORT"