#!/usr/bin/env bash # Download the Cactus-Compute Gemma 4 E2B INT4 model and push it to a # connected Android device running Sakhi. One command from clone → on-device # model ready. # # Prerequisites: # 1. The Sakhi APK must already be INSTALLED on the phone (debuggable), # because we use `run-as com.sakhi.app` to write into app-private storage. # Build/install instructions are in the main README. # 2. `adb` must be on PATH and the phone must show as an authorized device # (`adb devices` lists it with state "device", not "unauthorized"). # 3. `curl` and `unzip` must be available (unzip runs on-device via # adb shell, so only the host `curl` is needed). # 4. You must have ACCEPTED the terms on the Cactus-Compute model page: # https://huggingface.co/Cactus-Compute/gemma-4-E2B-it # and have a HuggingFace access token available. The script reads, in # order: $HF_TOKEN env var, then ~/.cache/huggingface/token. If neither # exists the script bails with instructions. # # Tested on: Windows 11 + Git Bash, macOS 14, Ubuntu 22.04. # adb wireless and USB both work — the script is transport-agnostic. set -euo pipefail MODEL_REPO="Cactus-Compute/gemma-4-E2B-it" MODEL_FILE="gemma-4-e2b-it-int4.zip" # NOTE: lowercase despite repo casing MODEL_URL="https://huggingface.co/${MODEL_REPO}/resolve/main/weights/${MODEL_FILE}" PKG="com.sakhi.app" ON_DEVICE_DIR="files/models/gemma-4-e2b" # relative to /data/data/$PKG/ DOWNLOAD_DIR="${DOWNLOAD_DIR:-./models/cactus}" LOCAL_ZIP="${DOWNLOAD_DIR}/${MODEL_FILE}" # ── Token resolution ──────────────────────────────────────────────────────── resolve_hf_token() { if [ -n "${HF_TOKEN:-}" ]; then echo "$HF_TOKEN" return 0 fi if [ -f "$HOME/.cache/huggingface/token" ]; then cat "$HOME/.cache/huggingface/token" return 0 fi return 1 } if ! TOKEN=$(resolve_hf_token); then cat >&2 </dev/null 2>&1; then echo "ERROR: adb not on PATH. Install Android platform-tools first." >&2 exit 1 fi DEVICE_COUNT=$(adb devices | awk 'NR>1 && $2=="device" {count++} END {print count+0}') if [ "$DEVICE_COUNT" -eq 0 ]; then echo "ERROR: no authorized adb device connected. Run 'adb devices' to diagnose." >&2 exit 1 fi if [ "$DEVICE_COUNT" -gt 1 ]; then echo "WARNING: multiple adb devices connected — the first one will be used." >&2 echo " Set ANDROID_SERIAL= to target a specific device." >&2 fi # Confirm Sakhi is installed and debuggable (run-as needs both). if ! adb shell "run-as $PKG true" >/dev/null 2>&1; then cat >&2 <&2 echo " Likely HTTP error body saved as zip. Inspect: head -c 500 '$LOCAL_ZIP'" >&2 exit 1 fi # ── Push + extract on-device ─────────────────────────────────────────────── # Git-Bash path-mangling guard: /data/local/tmp would become C:\Program Files\Git\data\... export MSYS_NO_PATHCONV=1 export MSYS2_ARG_CONV_EXCL="*" TMP_ON_DEVICE="/data/local/tmp/${MODEL_FILE}" echo "→ Pushing zip to $TMP_ON_DEVICE (this can take a few minutes over USB/wireless)..." adb push "$LOCAL_ZIP" "$TMP_ON_DEVICE" echo "→ Creating app-private model directory..." adb shell "run-as $PKG mkdir -p $ON_DEVICE_DIR" echo "→ Extracting in-place (app-private storage)..." adb shell "run-as $PKG sh -c 'cd $ON_DEVICE_DIR && unzip -o $TMP_ON_DEVICE'" echo "→ Removing pushed zip from /data/local/tmp..." adb shell "rm $TMP_ON_DEVICE" # ── Verify ───────────────────────────────────────────────────────────────── CONFIG_EXISTS=$(adb shell "run-as $PKG sh -c 'test -f $ON_DEVICE_DIR/config.txt && echo YES || echo NO'" | tr -d '\r\n') if [ "$CONFIG_EXISTS" != "YES" ]; then echo "ERROR: config.txt not found under $ON_DEVICE_DIR after extract. Unzip likely failed." >&2 exit 1 fi FILE_COUNT=$(adb shell "run-as $PKG sh -c 'ls $ON_DEVICE_DIR | wc -l'" | tr -d '\r\n') echo "✓ Model extracted: $FILE_COUNT files under /data/data/$PKG/$ON_DEVICE_DIR" echo "" echo "Next: open the Sakhi app → Field Mode → On-Device Probe → Check Status," echo " then Load Model. First load takes ~10 s."