A Rust backend that owns the sessions and an Android app that reads them. The server spawns and adopts CLI processes, normalises everything they emit into one event model, keeps the transcript, and serves it over pinned TLS on a WireGuard interface; the phone streams that, replies, sends images, and imports conversations the machine already has. `AGENTS.md` is the working guide -- what runs where, what has been measured, and the faults that were expensive to find. `PLAN.md` is the design record. History before this point was squashed away. It was a personal project's running commentary and carried a name and a couple of machine paths that have no business in a public repository; the tree is what mattered and the tree is here.
103 lines
4.4 KiB
Bash
Executable File
103 lines
4.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# Builds the app's APK, ready to install on a phone through Dev Updater.
|
|
#
|
|
# ./build-apk.sh
|
|
#
|
|
# The APK pins the CA on *this* machine ($XDG_CONFIG_HOME/ai-app/certs/ca.pem,
|
|
# or AI_APP_CA), so build it on the machine that runs the backend: an app
|
|
# built somewhere else trusts a CA that backend can't present, and simply
|
|
# won't connect. Start ai-server once first if there are no certificates
|
|
# yet -- it generates them; the build stops with that instruction if it
|
|
# can't find one.
|
|
#
|
|
# Unlike ./run-android.sh, this touches no emulator: it only produces the
|
|
# file. Installing on a real phone goes through Dev Updater, which serves
|
|
# whatever is under this project's build directory.
|
|
set -eu
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
cd "$SCRIPT_DIR"
|
|
|
|
# Prefer an SDK this machine has already configured -- the host and the dev
|
|
# VM don't keep it in the same place, and android-env.sh is written for the
|
|
# VM's layout (it also installs missing packages, which isn't wanted here).
|
|
if [ -n "${ANDROID_HOME:-}" ] && [ -d "${ANDROID_HOME}" ]; then
|
|
echo "==> Using ANDROID_HOME=$ANDROID_HOME"
|
|
elif [ -n "${ANDROID_SDK_ROOT:-}" ] && [ -d "${ANDROID_SDK_ROOT}" ]; then
|
|
ANDROID_HOME="$ANDROID_SDK_ROOT"
|
|
export ANDROID_HOME
|
|
echo "==> Using ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"
|
|
elif [ -d "$HOME/Android/Sdk" ]; then
|
|
ANDROID_HOME="$HOME/Android/Sdk"
|
|
ANDROID_SDK_ROOT="$ANDROID_HOME"
|
|
export ANDROID_HOME ANDROID_SDK_ROOT
|
|
echo "==> Using $ANDROID_HOME"
|
|
else
|
|
echo "No Android SDK found. Set ANDROID_HOME to it, or install one" >&2
|
|
echo "(Android Studio's default location is ~/Android/Sdk)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
CA="${AI_APP_CA:-${XDG_CONFIG_HOME:-$HOME/.config}/ai-app/certs/ca.pem}"
|
|
if [ -f "$CA" ]; then
|
|
# Printed so a wrong or stale certificate is visible here rather than
|
|
# as a handshake failure on the phone -- compare it against the CA the
|
|
# backend is actually presenting.
|
|
FINGERPRINT=$(openssl x509 -in "$CA" -pubkey -noout 2>/dev/null \
|
|
| openssl pkey -pubin -outform der 2>/dev/null \
|
|
| openssl dgst -sha256 -binary 2>/dev/null \
|
|
| openssl base64 2>/dev/null || echo "(openssl unavailable)")
|
|
echo "==> Pinning the CA at $CA"
|
|
echo " fingerprint: $FINGERPRINT"
|
|
else
|
|
echo "No CA certificate at $CA -- start ai-server once on this machine" >&2
|
|
echo "(it generates them), or set AI_APP_CA. The APK embeds it at build time." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Dev Updater draws a real progress bar from "@@progress done/total" lines,
|
|
# and ignores anything that isn't exactly that shape. Gradle can't be asked
|
|
# for this directly: an init script using taskGraph.afterTask is rejected
|
|
# outright by the configuration cache, and whenReady never fires on a cache
|
|
# hit. --dry-run costs about a second, is cache-friendly, and prints one
|
|
# ":task SKIPPED" line per task the real build will run, which is exactly
|
|
# the total. The build then prints one "> Task :x" line per task as it
|
|
# goes, so counting those against it is the whole mechanism.
|
|
#
|
|
# Task count is not time -- compileDebugKotlin and dexBuilder are most of
|
|
# the wall clock -- so the bar moves unevenly. It is still counted work
|
|
# rather than a guess at how long last time took.
|
|
TASKS=$(./gradlew :androidApp:assembleDebug --dry-run --console=plain 2>/dev/null \
|
|
| grep -c '^:[A-Za-z:]* SKIPPED' || true)
|
|
|
|
echo "==> Building"
|
|
if [ "${TASKS:-0}" -gt 0 ]; then
|
|
echo "@@progress 0/$TASKS"
|
|
DONE=0
|
|
./gradlew :androidApp:assembleDebug --console=plain 2>&1 | while IFS= read -r line; do
|
|
echo "$line"
|
|
case "$line" in
|
|
"> Task "*)
|
|
DONE=$((DONE + 1))
|
|
echo "@@progress $DONE/$TASKS"
|
|
;;
|
|
esac
|
|
done
|
|
# The pipeline's exit status is the shell's, not gradle's, so ask
|
|
# gradle again rather than reporting a failed build as a success. It is
|
|
# up to date by now, so this is a second or two.
|
|
./gradlew :androidApp:assembleDebug --console=plain >/dev/null
|
|
else
|
|
./gradlew :androidApp:assembleDebug
|
|
fi
|
|
|
|
APK="$SCRIPT_DIR/androidApp/build/outputs/apk/debug/androidApp-debug.apk"
|
|
echo
|
|
echo "==> Built $APK"
|
|
[ -f "$APK" ] && ls -lh "$APK" | awk '{print " " $5}'
|
|
echo
|
|
echo "To get it onto the phone: add this project to Dev Updater (or hit"
|
|
echo "Update on it if it's already there) and install from there."
|
|
echo "Then start the backend and scan the enrollment QR it prints:"
|
|
echo " ./server/target/release/ai-server --rotate-token"
|