File size: 5,157 Bytes
2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 2edb151 676f5d4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | #!/usr/bin/env bash
# =============================================================================
# ONE-SHOT: keys-Auto Receipts Studio (iPhone / may add Autonomous Lamp Skill)
# 1. Python 3.12 venv + app
# 2. Gemma 4 12B-it weights (skip if already on disk)
# 3. vLLM serve --gpu-memory-utilization 0.15 FP8 max-model-len 8192
# 4. Gradio UI on the LAN + print phone URL
# Idempotent. Re-run anytime. Never raises GPU util above 0.85.
# =============================================================================
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
# Only pin LLM URLs to this machine when local Gemma is actually up.
# UI-only boxes must keep .env pointing at the GPU box.
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'
|