Bundle the whisper base model + one-shot standalone provisioning

- models/hf-cache/ vendors Systran/faster-whisper-base (142MB, gitignored);
  engine spawn pins HF_HUB_CACHE there, so transcription works with a cold
  ~/.cache and fully offline
- verified: HOME=/tmp/fakehome HF_HUB_OFFLINE=1 model loads and transcribes
  via the bundled cache alone
- setup-standalone.sh: idempotent fresh-clone provisioning (local venv,
  model copy-or-download, engine boot smoke on :8123) — runs green
- backend/README rewritten for the desktop bundled-lite reality (was
  pointing at a nonexistent root README/docs and a Postgres quickstart)

Kotlin suite still green (gradlew test, 0 failures)
This commit is contained in:
avi 2026-09-14 21:05:41 -05:00
commit c25d3ab150
4 changed files with 77 additions and 9 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ backend/__pycache__/
**/__pycache__/ **/__pycache__/
*.pyc *.pyc
.dev-restart-flag .dev-restart-flag
models/hf-cache/

View file

@ -311,6 +311,9 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
"SHONAR_STORAGE_PATH" to File(appDir, "storage").absolutePath, "SHONAR_STORAGE_PATH" to File(appDir, "storage").absolutePath,
"SHONAR_TRANSCRIPTION_PROVIDER" to "faster_whisper", "SHONAR_TRANSCRIPTION_PROVIDER" to "faster_whisper",
"SHONAR_TRANSCRIPTION_MODEL" to "base", "SHONAR_TRANSCRIPTION_MODEL" to "base",
// Bundled whisper model: shipped in models/hf-cache so
// transcription works with a cold ~/.cache or offline.
"HF_HUB_CACHE" to File(repo, "models/hf-cache").absolutePath,
) )
// Summarizer preference: LAN inference server (H200) when // Summarizer preference: LAN inference server (H200) when
// its key is present in the environment, then local Ollama. // its key is present in the environment, then local Ollama.

View file

@ -1,16 +1,19 @@
# S.H.O.N.A.R. backend # S.H.O.N.A.R. backend (desktop bundled-lite)
FastAPI + PostgreSQL backend for the S.H.O.N.A.R. Android app. FastAPI engine for SHONAR Desktop, running standalone on this machine:
SQLite + in-process queue, self-migrating on boot. No Postgres/Redis/Docker.
See the repository root [README](../README.md) and `docs/` for full documentation. ## Setup (this repo is self-contained)
## Quick start (development)
```bash ```bash
cd backend cd backend
uv venv .venv && uv pip install -e ".[dev]" uv venv .venv && uv pip install -p .venv/bin/python -e ".[dev,faster-whisper]"
cp ../.env.example .env # then edit SHONAR_SECRET_KEY etc.
uvicorn shonar.main:app --reload --port 8000
``` ```
Tests: `pytest` · Lint: `ruff check .` · Migrations: `alembic upgrade head` The app (DesktopState) creates this venv's env vars itself, including
`HF_HUB_CACHE=models/hf-cache` — the `base` faster-whisper model is vendored
under `models/hf-cache/` (gitignored; copy from any HF cache to reprovision).
Tests: `.venv/bin/python -m pytest` (SQLite by default; point
`SHONAR_TEST_DATABASE_URL` at Postgres to test that path) · Lint: `ruff check .`
· Migrations: `alembic upgrade head`

61
setup-standalone.sh Executable file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env bash
# One-shot provisioning for a fresh clone of SHONAR Desktop:
# local engine venv + vendored whisper base model. Idempotent.
set -uo pipefail
cd "$(dirname "$0")"
echo "==> backend venv (local, editable install of THIS backend)"
if [ ! -x backend/.venv/bin/uvicorn ]; then
command -v uv >/dev/null || { echo "uv not found — install it: curl -LsSf https://astral.sh/uv/install.sh | sh"; exit 1; }
(cd backend && uv venv .venv --python 3.11 && uv pip install -p .venv/bin/python -e ".[dev,faster-whisper]")
else
echo " already present"
fi
echo "==> whisper base model -> models/hf-cache"
MC="models/hf-cache/models--Systran--faster-whisper-base"
if [ -d "$MC/snapshots" ] && [ -n "$(ls -A "$MC/snapshots" 2>/dev/null)" ]; then
echo " already present"
else
# Prefer copying an existing local copy (works offline)...
src=""
for c in "${HF_HUB_CACHE:-$HOME/.cache/huggingface/hub}/models--Systran--faster-whisper-base" \
"$HOME/.cache/huggingface/hub/models--Systran--faster-whisper-base"; do
[ -d "$c/snapshots" ] && { src="$c"; break; }
done
mkdir -p models/hf-cache
if [ -n "$src" ]; then
echo " copying from $src"
cp -r "$src" models/hf-cache/
else
echo " no local copy — downloading from HuggingFace (needs internet once)"
backend/.venv/bin/python - <<'EOF'
from huggingface_hub import snapshot_download
snapshot_download("Systran/faster-whisper-base", cache_dir="models/hf-cache")
EOF
fi
fi
echo "==> smoke: engine boots on :8123 with the bundled cache"
tmp=$(mktemp -d)
SHONAR_SECRET_KEY="bootstrap-verify-secret-0123456789abcdef" \
SHONAR_DATABASE_URL="sqlite+aiosqlite:///$tmp/e.db" \
SHONAR_QUEUE_BACKEND=inline SHONAR_AUTO_MIGRATE=1 \
SHONAR_STORAGE_PATH="$tmp/storage" \
HF_HUB_CACHE="$PWD/models/hf-cache" \
backend/.venv/bin/uvicorn shonar.main:app --app-dir backend --port 8123 \
>"$tmp/uvicorn.log" 2>&1 &
pid=$!
ok=""
for _ in $(seq 1 30); do
sleep 1
curl -sf -m 2 http://127.0.0.1:8123/api/v1/readyz >/dev/null && { ok=1; break; }
done
kill $pid 2>/dev/null; wait $pid 2>/dev/null
rm -rf "$tmp"
if [ -n "$ok" ]; then
echo " engine ready — provisioning complete. Start the app: ./desktop-dev.sh"
else
echo " ENGINE FAILED TO START" >&2
exit 1
fi