| #!/usr/bin/env bash |
| |
| |
| |
| |
| |
| |
| |
| |
| set -euo pipefail |
| ROOT="$(cd "$(dirname "$0")" && pwd)" |
| cd "$ROOT" |
|
|
| MODEL_ID="${RECEIPT_HF_MODEL:-google/gemma-4-12B-it}" |
| MODEL_DIR="${RECEIPT_GEMMA_PATH:-$HOME/models-gemma4-12b-it}" |
| PORT_LLM="${RECEIPT_VLLM_PORT:-8080}" |
| PORT_UI="${RECEIPT_UI_PORT:-7860}" |
| UTIL="${RECEIPT_GPU_MEMORY_UTILIZATION:-0.15}" |
|
|
| say(){ printf '\n\033[1;36m==> %s\033[0m\n' "$*"; } |
| die(){ printf '\n\033[1;31mFAILED: %s\033[0m\n' "$*" >&2; exit 1; } |
|
|
| python3 -c 'import sys; assert sys.version_info >= (3,12), sys.version' \ |
| || die "Python 3.12+ required" |
|
|
| if python3 -c "u=float('$UTIL'); assert u<=0.85" 2>/dev/null; then :; else |
| die "gpu_memory_utilization $UTIL > 0.85 hard cap" |
| fi |
|
|
| say "1/5 venv + install" |
| if [[ ! -x .venv/bin/python ]]; then |
| python3 -m venv .venv |
| fi |
| .venv/bin/pip install -q -U pip |
| .venv/bin/pip install -q -e ".[dev]" |
| [[ -f .env ]] || cp .env.example .env |
|
|
| HAVE_VLLM=0 |
| if curl -sf -m3 "http://127.0.0.1:$PORT_LLM/v1/models" >/dev/null 2>&1; then |
| HAVE_VLLM=1 |
| elif command -v vllm >/dev/null 2>&1; then |
| HAVE_VLLM=1 |
| fi |
|
|
| say "2/5 Gemma 4 12B-it weights → $MODEL_DIR" |
| if [[ "$HAVE_VLLM" != 1 ]]; then |
| echo " skip (no vllm on PATH and nothing on :$PORT_LLM) — UI-only. Point .env at a GPU box." |
| elif [[ -f "$MODEL_DIR/config.json" ]] && ls "$MODEL_DIR"/*.safetensors >/dev/null 2>&1; then |
| echo " present" |
| else |
| .venv/bin/pip install -q huggingface_hub |
| mkdir -p "$MODEL_DIR" |
| .venv/bin/python - "$MODEL_ID" "$MODEL_DIR" <<'PY' || die "weight download failed (run: .venv/bin/hf auth login)" |
| import sys |
| from huggingface_hub import snapshot_download |
| snapshot_download(sys.argv[1], local_dir=sys.argv[2]) |
| print(" downloaded") |
| PY |
| fi |
|
|
| say "3/5 vLLM Gemma (util=$UTIL FP8, :$PORT_LLM)" |
| if curl -sf -m3 "http://127.0.0.1:$PORT_LLM/v1/models" >/dev/null 2>&1; then |
| echo " already serving" |
| elif command -v vllm >/dev/null 2>&1 && [[ -f "$MODEL_DIR/config.json" ]]; then |
| mkdir -p data |
| nohup bash "$ROOT/scripts/serve-gemma.sh" >> data/vllm-gemma.log 2>&1 & |
| echo " pid $! log data/vllm-gemma.log" |
| else |
| echo " skip — no local vLLM/Gemma. Set RECEIPT_LLM_BASE_URL in .env to the GPU box." |
| fi |
|
|
| say "4/5 wait until Gemma answers /v1/models (first boot compiles kernels)" |
| if curl -sf -m3 "http://127.0.0.1:$PORT_LLM/v1/models" >/dev/null 2>&1; then |
| echo " already healthy" |
| elif command -v vllm >/dev/null 2>&1 && [[ -f "$MODEL_DIR/config.json" ]]; then |
| ok=0 |
| for i in $(seq 1 120); do |
| if curl -sf -m3 "http://127.0.0.1:$PORT_LLM/v1/models" >/dev/null 2>&1; then |
| echo " healthy ($i)" |
| ok=1 |
| break |
| fi |
| sleep 5 |
| done |
| [[ "$ok" = 1 ]] || die "vLLM not healthy — tail data/vllm-gemma.log" |
| else |
| echo " skip wait (no local Gemma). UI will still start." |
| fi |
|
|
| say "5/5 UI on LAN :$PORT_UI" |
| mkdir -p data |
| export RECEIPT_UI_SHARE_LAN=true |
| |
| |
| if curl -sf -m3 "http://127.0.0.1:$PORT_LLM/v1/models" >/dev/null 2>&1; then |
| export RECEIPT_LLM_BASE_URL="http://127.0.0.1:${PORT_LLM}/v1" |
| export RECEIPT_EMBED_BASE_URL="http://127.0.0.1:${PORT_LLM}/v1" |
| export RECEIPT_LLM_MODEL="$MODEL_ID" |
| export RECEIPT_EMBED_MODEL="$MODEL_ID" |
| export RECEIPT_EMBED_DIM=3840 |
| export RECEIPT_EMBED_BACKEND=omni |
| fi |
| if curl -sf -m2 "http://127.0.0.1:$PORT_UI/phone" >/dev/null 2>&1; then |
| echo " UI already up" |
| else |
| nohup .venv/bin/python -m app.cli ui >> data/ui.log 2>&1 & |
| echo " pid $!" |
| for i in $(seq 1 40); do |
| curl -sf -m2 "http://127.0.0.1:$PORT_UI/phone" >/dev/null 2>&1 && break |
| sleep 0.25 |
| done |
| fi |
|
|
| LAN="$(python3 - <<'PY' |
| import socket |
| s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| try: |
| s.connect(("192.0.2.1",1)); print(s.getsockname()[0]) |
| except OSError: |
| print("127.0.0.1") |
| finally: |
| s.close() |
| PY |
| )" |
|
|
| printf '\n\033[1;32m✅ READY\033[0m keys-Auto Receipts Studio\n' |
| printf ' Review (this machine): http://127.0.0.1:%s\n' "$PORT_UI" |
| printf ' iPhone Safari: http://%s:%s/phone\n' "$LAN" "$PORT_UI" |
| if curl -sf -m2 "http://127.0.0.1:$PORT_LLM/v1/models" >/dev/null 2>&1; then |
| printf ' Gemma /v1: http://127.0.0.1:%s/v1 model %s util=%s\n' "$PORT_LLM" "$MODEL_ID" "$UTIL" |
| else |
| printf ' Gemma /v1: not local — set RECEIPT_LLM_BASE_URL in .env to the GPU box\n' |
| fi |
| printf ' Desktop launcher: bash scripts/install-launcher.sh\n' |
| printf ' Windows desktop icon: scripts\\install-launcher.bat\n' |
| printf '\n Hold a receipt up → Take photo on the phone page (Safari).\n' |
| printf ' Lamp skill: skills/keys-receipt-scanner/ — 12B does not fit in 6GB RAM.\n' |
|
|