Files
timmy-talking-turd/scripts/bootstrap_selfhost_smolvlm.sh
T
Timmy 46f961668f
Quality gates / quality (pull_request) Successful in 3m29s
feat: pin the self-hosted worker container and gate readiness on the loaded model (#15)
A worker that answers /v1/models has not necessarily loaded its weights, so the app
was offering an AI control that could only fail after the upload. Readiness now
passes only once the pinned model is loaded, and the not-loaded state reaches manual
Bristol entry in one tap. Verified against the real pinned llama.cpp build: it serves
503 while loading and the gate reports the distinct not-loaded state.

Adds the reproducible container recipe (digest-pinned bases, pinned llama.cpp commit,
hash-verified read-only weights, unprivileged, loopback-only publish), one shared
post-load readiness gate used by the host bootstrap and the image healthcheck, and a
target-GPU benchmark that refuses to write a receipt it did not measure.

Refs #15
2026-10-13 08:20:21 +00:00

155 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
LLAMA_CPP_COMMIT="${TIMMY_LLAMA_CPP_COMMIT:-6d05498314db1b57f81c271080018aa2d0b89be9}"
MODEL_SHA256="${TIMMY_MODEL_SHA256:-0cf76814555b8665149075b74ab6b5c1d428ea1d3d01c1918c12012e8d7c9f58}"
MMPROJ_SHA256="${TIMMY_MMPROJ_SHA256:-ae07ea1facd07dd3230c4483b63e8cda96c6944ad2481f33d531f79e892dd024}"
HOST="${TIMMY_MODEL_HOST:-127.0.0.1}"
PORT="${TIMMY_MODEL_PORT:-8080}"
ROOT="${TIMMY_SELFHOST_ROOT:-${XDG_DATA_HOME:-$HOME/.local/share}/timmy-selfhost}"
MODEL_DIR="$ROOT/models"
MODEL="$MODEL_DIR/SmolVLM2-2.2B-Instruct-Q4_K_M.gguf"
MMPROJ="$MODEL_DIR/mmproj-SmolVLM2-2.2B-Instruct-Q8_0.gguf"
SOURCE="$ROOT/llama.cpp"
BUILD="$SOURCE/build"
HF_BASE="https://huggingface.co/ggml-org/SmolVLM2-2.2B-Instruct-GGUF/resolve/main"
SERVER="$BUILD/bin/llama-server"
RUNTIME="$ROOT/run"
PIDFILE="$RUNTIME/llama-server.pid"
LOGFILE="$RUNTIME/llama-server.log"
verify_files() {
for file in "$MODEL" "$MMPROJ"; do
if [[ ! -f "$file" ]]; then
printf 'Missing required file: %s. Run %s install.\n' "$file" "$0" >&2
return 1
fi
done
printf '%s %s\n%s %s\n' "$MODEL_SHA256" "$MODEL" "$MMPROJ_SHA256" "$MMPROJ" | sha256sum --check --status
}
download_verified() {
local url="$1" target="$2" expected="$3" temporary="$2.download"
rm -f "$temporary"
curl --fail --location --retry 3 --output "$temporary" "$url"
printf '%s %s\n' "$expected" "$temporary" | sha256sum --check --status || {
rm -f "$temporary"
printf 'Downloaded file failed pinned SHA-256 verification: %s\n' "$target" >&2
return 1
}
mv "$temporary" "$target"
}
install_worker() {
command -v git >/dev/null
command -v cmake >/dev/null
command -v curl >/dev/null
mkdir -p "$ROOT" "$MODEL_DIR"
if [[ ! -d "$SOURCE/.git" ]]; then
git clone https://github.com/ggml-org/llama.cpp "$SOURCE"
fi
git -C "$SOURCE" fetch origin "$LLAMA_CPP_COMMIT"
git -C "$SOURCE" checkout --detach "$LLAMA_CPP_COMMIT"
cmake -S "$SOURCE" -B "$BUILD" -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF
cmake --build "$BUILD" --parallel "${TIMMY_BUILD_JOBS:-4}" --target llama-server
download_verified "$HF_BASE/SmolVLM2-2.2B-Instruct-Q4_K_M.gguf" "$MODEL" "$MODEL_SHA256"
download_verified "$HF_BASE/mmproj-SmolVLM2-2.2B-Instruct-Q8_0.gguf" "$MMPROJ" "$MMPROJ_SHA256"
verify_files
printf 'Pinned model files verified.\n'
}
health_worker() {
curl --fail --silent --show-error --retry 5 --retry-connrefused --retry-delay 1 --max-time 3 "http://$HOST:$PORT/v1/models" | grep -q 'SmolVLM2-2.2B-Instruct'
printf 'Self-hosted worker healthy at http://%s:%s/v1\n' "$HOST" "$PORT"
}
start_worker() {
verify_files
if [[ ! -x "$SERVER" ]]; then
printf 'Missing llama-server executable: %s. Run %s install.\n' "$SERVER" "$0" >&2
return 1
fi
mkdir -p "$RUNTIME"
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
health_worker
return
fi
rm -f "$PIDFILE"
nohup "$SERVER" \
--model "$MODEL" \
--mmproj "$MMPROJ" \
--alias SmolVLM2-2.2B-Instruct \
--host "$HOST" \
--port "$PORT" \
--ctx-size 4096 \
--parallel 1 \
--threads "${TIMMY_MODEL_THREADS:-4}" \
--no-webui >"$LOGFILE" 2>&1 &
printf '%s\n' "$!" > "$PIDFILE"
for _ in {1..30}; do
if health_worker >/dev/null 2>&1; then
printf 'Self-hosted worker started privately at http://%s:%s/v1\n' "$HOST" "$PORT"
return
fi
sleep 0.2
done
kill "$(cat "$PIDFILE")" 2>/dev/null || true
rm -f "$PIDFILE"
printf 'Worker did not become healthy; inspect %s\n' "$LOGFILE" >&2
return 1
}
stop_worker() {
if [[ ! -f "$PIDFILE" ]]; then
printf 'Self-hosted worker is not running.\n'
return
fi
local pid
pid="$(cat "$PIDFILE")"
kill "$pid" 2>/dev/null || true
for _ in {1..25}; do
if ! kill -0 "$pid" 2>/dev/null; then
rm -f "$PIDFILE"
printf 'Self-hosted worker stopped.\n'
return
fi
sleep 0.1
done
kill -9 "$pid" 2>/dev/null || true
rm -f "$PIDFILE"
printf 'Self-hosted worker stopped.\n'
}
case "${1:-}" in
receipt)
printf 'llama.cpp commit: %s\n' "$LLAMA_CPP_COMMIT"
printf 'SmolVLM2-2.2B-Instruct-Q4_K_M.gguf sha256: %s\n' "$MODEL_SHA256"
printf 'mmproj-SmolVLM2-2.2B-Instruct-Q8_0.gguf sha256: %s\n' "$MMPROJ_SHA256"
printf 'bind address: %s:%s\n' "$HOST" "$PORT"
;;
verify)
verify_files
printf 'Pinned model files verified.\n'
;;
install)
install_worker
;;
start)
start_worker
;;
ready)
# Post-load readiness (issue #15): delegate so the host and the container gate cannot drift.
exec "$(dirname "$0")/worker_ready.sh" --host "$HOST" --port "$PORT" --alias SmolVLM2-2.2B-Instruct "${@:2}"
;;
health)
health_worker
;;
stop)
stop_worker
;;
*)
printf 'Usage: %s {install|start|health|ready|stop|receipt|verify}\n' "$0" >&2
exit 64
;;
esac