The emulator in ~/Android/Sdk auto-updated to 37.2.12 on Oct 11 and since
then consistently fails its own KVM probe ("/dev/kvm is not found") and
segfaults on that failure path — while KVM is healthy: a manual
KVM_GET_API_VERSION ioctl returns the current API level 12, and both
/opt/android-sdk and the container-home emulator pass -accel-check on
the same kernel and device node. The script now prefers the documented
host SDK at /opt/android-sdk when no ANDROID_SDK_ROOT is set.
Once the home SDK is repaired (or the emulator regression disappears),
the fallback order can be revisited.
177 lines
No EOL
6.9 KiB
Bash
Executable file
177 lines
No EOL
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# start-emulator.sh — boot the JTube TV test emulator on the HOST (not inside the
|
|
# agent container), with GPU acceleration, headless by default (drive it via
|
|
# adb/scrcpy). JTUBE_EMU_MODE=windowed shows the Qt window.
|
|
#
|
|
# Boots on console :5600 (adbd :5601); the agent container bridges
|
|
# rutherford:5556 -> 127.0.0.1:5601 with socat, so after this script the emulator is
|
|
# reachable from anywhere as:
|
|
#
|
|
# adb connect rutherford:5556 && scrcpy -s rutherford:5556
|
|
#
|
|
# An RSA authorization popup appears on the emulator window on first connect —
|
|
# click "Always allow"/OK.
|
|
set -euo pipefail
|
|
|
|
SDK="${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}"
|
|
# Prefer the working host SDK at /opt/android-sdk: the emulator that
|
|
# ~/Android/Sdk auto-updated to on Oct 11 (37.2.12) fails its own KVM probe
|
|
# ("/dev/kvm is not found") with KVM perfectly healthy — /opt/android-sdk and
|
|
# the container-home copy both pass -accel-check on the same kernel, so that
|
|
# install is broken. /opt/android-sdk is the documented host SDK anyway.
|
|
if [ -z "${ANDROID_SDK_ROOT:-}" ] && [ -x /opt/android-sdk/emulator/emulator ]; then
|
|
SDK=/opt/android-sdk
|
|
fi
|
|
EMU="$SDK/emulator/emulator"
|
|
ADB="$SDK/platform-tools/adb"
|
|
SDKM="$SDK/cmdline-tools/latest/bin/sdkmanager"
|
|
AVDM="$SDK/cmdline-tools/latest/bin/avdmanager"
|
|
AVD="${1:-jtubesc}"
|
|
PORT=5600
|
|
IMAGE="system-images;android-36;android-tv;x86_64"
|
|
|
|
[ -x "$EMU" ] || { echo "emulator not found at $EMU — install Android SDK cmdline-tools first"; exit 1; }
|
|
|
|
# --- bootstrap: java, system image, AVD -------------------------------------
|
|
if ! command -v java >/dev/null; then
|
|
echo "no java on PATH — required by sdkmanager/AVD tools"
|
|
echo " install it (Arch): sudo pacman -S --noconfirm jdk21-openjdk"
|
|
echo " then re-run this script"
|
|
exit 1
|
|
fi
|
|
|
|
if ! "$SDKM" --list_installed 2>/dev/null | grep -q "$IMAGE"; then
|
|
echo "installing $IMAGE (~1.5 GB, once)…"
|
|
yes | "$SDKM" "$IMAGE" > /dev/null
|
|
echo "installed"
|
|
fi
|
|
|
|
if [ ! -f "$HOME/.android/avd/$AVD.ini" ]; then
|
|
echo "creating AVD '$AVD'…"
|
|
echo no | "$AVDM" create avd -n "$AVD" -k "$IMAGE" -d tv_1080p > /dev/null
|
|
fi
|
|
|
|
# --- free stale emulator instances holding our port --------------------------
|
|
for pid in $(ps aux | awk '/[q]emu-system-x86_64/ {print $2}' 2>/dev/null); do
|
|
if tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q "\-port $PORT"; then
|
|
echo "Killing stale emulator pid $pid"
|
|
kill -9 "$pid" 2>/dev/null || true
|
|
fi
|
|
done
|
|
sleep 1
|
|
|
|
MODE="${JTUBE_EMU_MODE:-headless}" # headless (default) | windowed
|
|
WINDOWED=1
|
|
ACCEL=()
|
|
if [ "$MODE" = windowed ]; then
|
|
if [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]; then
|
|
echo "windowed (JTUBE_EMU_MODE=windowed) — emulator window + GPU host"
|
|
else
|
|
echo "JTUBE_EMU_MODE=windowed but no display detected — falling back to headless"
|
|
WINDOWED=0
|
|
ACCEL+=(-no-window -gpu swiftshader_indirect)
|
|
fi
|
|
else
|
|
WINDOWED=0
|
|
# Headless has no X display, so -gpu host/GLX can't init EGL ("Failed to get EGL
|
|
# display") and the emulator dies on launch — fall back to software rendering.
|
|
ACCEL+=(-no-window -gpu swiftshader_indirect)
|
|
echo "headless (default; JTUBE_EMU_MODE=windowed for a window) — no emulator window"
|
|
fi
|
|
if [ -e /dev/kvm ]; then
|
|
ACCEL+=(-accel on)
|
|
echo "/dev/kvm found — hardware acceleration on"
|
|
fi
|
|
|
|
# Pin the emulator process to the integrated GPU so the discrete card stays free
|
|
# for AI inference (windowed mode uses the host GL/Vulkan stack; headless runs on
|
|
# swiftshader — pure software — and never touches any GPU at all). Discovered
|
|
# from the PCI tree: first display device NOT driven by the nvidia driver.
|
|
IGPU=""
|
|
for dev in /sys/bus/pci/devices/*; do
|
|
class=$(cat "$dev/class" 2>/dev/null) || continue
|
|
case "$class" in
|
|
0x0300*|0x0380*) ;; # VGA / display controller
|
|
*) continue ;;
|
|
esac
|
|
if [ "$(basename "$(readlink "$dev/driver" 2>/dev/null)")" = nvidia ]; then continue; fi
|
|
IGPU=$(basename "$dev")
|
|
break
|
|
done
|
|
if [ -n "$IGPU" ]; then
|
|
# Mesa GL/GLES: force the Mesa vendors, then the Intel render device.
|
|
export __GLX_VENDOR_LIBRARY_NAME=mesa
|
|
export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
|
|
export DRI_PRIME="pci-${IGPU//./_}"
|
|
# Vulkan: restrict the loader to the iGPU's ICD (both var names — loader
|
|
# version differences).
|
|
for icd in /usr/share/vulkan/icd.d/intel_icd.x86_64.json \
|
|
/usr/share/vulkan/icd.d/Intel_icd.x86_64.json; do
|
|
if [ -f "$icd" ]; then
|
|
export VK_ICD_FILENAMES="$icd"
|
|
export VK_DRIVER_FILES="$icd"
|
|
break
|
|
fi
|
|
done
|
|
echo "GPU pinned to iGPU $IGPU (Mesa/Intel env) — dGPU untouched"
|
|
else
|
|
echo "no iGPU found — leaving GPU selection to the driver (dGPU may be used)"
|
|
fi
|
|
|
|
# --- build combined adb vendor-keys file ------------------------------------
|
|
# The TV system image is a user build: adb push to /data/misc/adb/adb_keys is
|
|
# blocked, so remote keys are trusted via ADB_VENDOR_KEYS at launch instead.
|
|
# Drop extra public keys into /tmp/jtube-adbkeys/ (one per file), e.g. from the
|
|
# agent container: docker exec <ctr> sh -c 'cat ~/.android/adbkey.pub' > /tmp/jtube-adbkeys/agent-container.pub
|
|
mkdir -p /tmp/jtube-adbkeys
|
|
VKEYS=/tmp/jtube-adbkeys-combined
|
|
{
|
|
cat "$HOME/.android/adbkey.pub"
|
|
# Optional extra keys — must not fail the script under `set -e` when absent.
|
|
shopt -s nullglob
|
|
for k in /tmp/jtube-adbkeys/*.pub; do cat "$k"; done
|
|
shopt -u nullglob
|
|
} > "$VKEYS"
|
|
chmod 600 "$VKEYS"
|
|
|
|
echo "Booting AVD '$AVD' …"
|
|
ADB_VENDOR_KEYS="$VKEYS" setsid nohup "$EMU" -avd "$AVD" -port "$PORT" "${ACCEL[@]:-}" \
|
|
-no-snapshot > /tmp/jtube-emu.log 2>&1 < /dev/null &
|
|
echo "emulator PID $! — log: /tmp/jtube-emu.log"
|
|
|
|
for i in $(seq 1 90); do
|
|
# adb exits non-zero while the device is still booting — don't let
|
|
# pipefail+set -e kill the wait loop.
|
|
ok=$( ("$ADB" -s "emulator-$PORT" shell getprop sys.boot_completed 2>/dev/null || true) | tr -d '\r')
|
|
if [ "$ok" = "1" ]; then echo "BOOTED"; break; fi
|
|
sleep 5
|
|
done
|
|
|
|
# --- the bridge: advertise adbd on the LAN as :5556 ---------------------------
|
|
if ! ss -tln 2>/dev/null | grep -q ":5556"; then
|
|
if command -v socat > /dev/null; then
|
|
setsid nohup socat TCP-LISTEN:5556,bind=0.0.0.0,fork,reuseaddr TCP:127.0.0.1:$((PORT+1)) > /tmp/jtube-socat.log 2>&1 < /dev/null &
|
|
echo "socat bridge listening on :5556 (pid $!)"
|
|
else
|
|
echo "install socat for the remote bridge: sudo pacman -S --noconfirm socat"
|
|
fi
|
|
fi
|
|
|
|
# Keep the JTube TV app current + running so scrcpy lands on it.
|
|
APK="$HOME/Projects/jtube-tv/app/build/outputs/apk/debug/app-debug.apk"
|
|
if [ -f "$APK" ]; then
|
|
echo "Installing JTube TV…"
|
|
"$ADB" -s "emulator-$PORT" install -r "$APK" >/dev/null && echo "installed"
|
|
"$ADB" -s "emulator-$PORT" shell am start -n net.jeena.jtubetv/.MainActivity && echo "app started"
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
Ready. From any machine that can reach rutherford:
|
|
|
|
adb connect rutherford:5556
|
|
scrcpy -s rutherford:5556
|
|
|
|
If scrcpy says "unauthorized", click the RSA dialog on the emulator window.
|
|
EOF |