feat: package pinned self-host vision bootstrap
Quality gates / quality (pull_request) Successful in 1m30s
Quality gates / quality (pull_request) Successful in 1m30s
This commit is contained in:
1 parent
ca31e6d38b
commit
982fab273c
7 files changed
+266
-16
No files matched your search
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/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
|
||||
;;
|
||||
health)
|
||||
health_worker
|
||||
;;
|
||||
stop)
|
||||
stop_worker
|
||||
;;
|
||||
*)
|
||||
printf 'Usage: %s {install|start|health|stop|receipt|verify}\n' "$0" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
@@ -105,7 +105,7 @@ await caption('Automated checks replay this synthetic path before review', 1200)
|
||||
await caption('One clear photo action. Manual logging stays one tap away.', 1500);
|
||||
await tap('[data-scan]', 450);
|
||||
await page.getByText(/Self-hosted model ready/i).waitFor();
|
||||
await caption('The self-hosted vision route is ready', 1000);
|
||||
await caption('The pinned bootstrap verifies both model files before starting on private loopback', 1500);
|
||||
await page.locator('#ai-photo').setInputFiles('tests/fixtures/synthetic-type4.jpg');
|
||||
await caption('Nothing uploads until explicit consent', 1100);
|
||||
await page.locator('#ai-consent').check();
|
||||
|
||||
Reference in new issue
Block a user