android-emulator: new stack — shared in-cluster Android 16 testing instance
Viktor is setting up an Android app development pipeline (tripit is the first app) and wants agents to natively test changes on Android before shipping. This adds the testing environment: an API-36 Google emulator under KVM as a privileged pod (namespace joins the Kyverno exclude list), SDK/system-image/AVD on a proxmox-lvm PVC, adb on the shared MetalLB IP 10.0.20.200:5555 (LAN only), noVNC screen view at android-emulator.viktorbarzin.lan. Image is built manually from the stack's docker/ dir (rare rebuilds; off-infra-CI rule targets repeated builds). First infra ADR records the trade-offs (devvm/VM/redroid/budtmo rejected).
This commit is contained in:
parent
5486b9d438
commit
8b7c77c794
10 changed files with 469 additions and 4 deletions
43
stacks/android-emulator/docker/Dockerfile
Normal file
43
stacks/android-emulator/docker/Dockerfile
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Android emulator runner — slim on purpose: the SDK proper (platform-tools,
|
||||
# emulator, system image, AVD) lives on the stack's PVC and is installed by
|
||||
# entrypoint.sh on first boot, so this image carries only the JDK,
|
||||
# cmdline-tools and the native libraries the emulator needs at runtime.
|
||||
#
|
||||
# Rebuild + push (rare — only when tool/library versions bump):
|
||||
# docker build -t forgejo.viktorbarzin.me/viktor/android-emulator:api36-v1 .
|
||||
# docker push forgejo.viktorbarzin.me/viktor/android-emulator:api36-v1
|
||||
FROM eclipse-temurin:17-jdk-jammy
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# emulator runtime deps (Qt window into Xvfb)
|
||||
libpulse0 libgl1 libglu1-mesa libnss3 libasound2 libfontconfig1 \
|
||||
libx11-6 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
|
||||
libxfixes3 libxi6 libxrandr2 libxrender1 libxtst6 libxkbcommon0 \
|
||||
libxkbfile1 libsm6 libice6 libdbus-1-3 \
|
||||
# virtual display + browser viewing
|
||||
xvfb x11vnc novnc websockify openbox \
|
||||
# adb TCP forwarding + fetch tooling
|
||||
socat wget unzip ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Android cmdline-tools (sdkmanager/avdmanager). Pinned; SDK packages they
|
||||
# install land on the PVC at /sdk, not in this image.
|
||||
ARG CMDLINE_TOOLS_VERSION=13114758
|
||||
RUN mkdir -p /opt/android/cmdline-tools && \
|
||||
wget -q "https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip" -O /tmp/clt.zip && \
|
||||
unzip -q /tmp/clt.zip -d /opt/android/cmdline-tools && \
|
||||
mv /opt/android/cmdline-tools/cmdline-tools /opt/android/cmdline-tools/latest && \
|
||||
rm /tmp/clt.zip
|
||||
|
||||
ENV ANDROID_SDK_ROOT=/sdk \
|
||||
ANDROID_USER_HOME=/sdk/.android \
|
||||
ANDROID_AVD_HOME=/sdk/.android/avd \
|
||||
PATH="/opt/android/cmdline-tools/latest/bin:/sdk/platform-tools:/sdk/emulator:${PATH}"
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# 5555 adb (socat → emulator adbd), 6080 noVNC web UI
|
||||
EXPOSE 5555 6080
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
68
stacks/android-emulator/docker/entrypoint.sh
Normal file
68
stacks/android-emulator/docker/entrypoint.sh
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env bash
|
||||
# Boot sequence: ensure SDK + AVD on the PVC (/sdk), bring up a virtual
|
||||
# display with browser viewing (Xvfb → x11vnc → noVNC :6080), start the
|
||||
# emulator windowed into it, and expose its adbd on :5555 for the LAN.
|
||||
set -euo pipefail
|
||||
|
||||
API_LEVEL="${API_LEVEL:-36}"
|
||||
SYSTEM_IMAGE="system-images;android-${API_LEVEL};google_apis;x86_64"
|
||||
AVD_NAME="${AVD_NAME:-lab}"
|
||||
EMULATOR_RAM_MB="${EMULATOR_RAM_MB:-4096}"
|
||||
SCREEN_GEOMETRY="${SCREEN_GEOMETRY:-1080x2280x24}"
|
||||
|
||||
[ -e /dev/kvm ] || { echo "FATAL: /dev/kvm not present — pod needs the kvm hostPath + privileged"; exit 1; }
|
||||
|
||||
mkdir -p "$ANDROID_USER_HOME"
|
||||
|
||||
# --- SDK packages on the PVC (idempotent; first boot downloads ~2.5GB) ------
|
||||
if [ ! -x /sdk/platform-tools/adb ] || [ ! -x /sdk/emulator/emulator ] || \
|
||||
[ ! -d "/sdk/system-images/android-${API_LEVEL}" ]; then
|
||||
echo "Installing SDK packages into /sdk (first boot)..."
|
||||
# (yes || true): yes dies of SIGPIPE (141) when sdkmanager stops reading,
|
||||
# which set -o pipefail would otherwise turn into a fatal error.
|
||||
(yes || true) | sdkmanager --sdk_root=/sdk --licenses >/dev/null
|
||||
sdkmanager --sdk_root=/sdk "platform-tools" "emulator" "$SYSTEM_IMAGE"
|
||||
fi
|
||||
|
||||
# --- AVD (idempotent) --------------------------------------------------------
|
||||
if ! avdmanager list avd -c | grep -qx "$AVD_NAME"; then
|
||||
echo "Creating AVD '$AVD_NAME' (${SYSTEM_IMAGE}, pixel_7)..."
|
||||
(echo no || true) | avdmanager create avd -n "$AVD_NAME" -k "$SYSTEM_IMAGE" --device pixel_7
|
||||
cat >> "${ANDROID_AVD_HOME}/${AVD_NAME}.avd/config.ini" <<EOF
|
||||
hw.ramSize=${EMULATOR_RAM_MB}
|
||||
disk.dataPartition.size=8192M
|
||||
hw.keyboard=yes
|
||||
EOF
|
||||
fi
|
||||
|
||||
# --- virtual display + browser viewing ---------------------------------------
|
||||
export DISPLAY=:0
|
||||
Xvfb :0 -screen 0 "$SCREEN_GEOMETRY" -nolisten tcp &
|
||||
sleep 1
|
||||
openbox &
|
||||
x11vnc -display :0 -nopw -forever -shared -quiet -bg
|
||||
websockify --web /usr/share/novnc 6080 localhost:5900 &
|
||||
|
||||
# --- emulator -----------------------------------------------------------------
|
||||
# swiftshader = CPU rendering (no GPU dependency); KVM does the heavy lifting.
|
||||
emulator -avd "$AVD_NAME" \
|
||||
-gpu swiftshader_indirect -accel on \
|
||||
-memory "$EMULATOR_RAM_MB" \
|
||||
-no-audio -no-boot-anim \
|
||||
&
|
||||
|
||||
adb wait-for-device
|
||||
echo "Emulator up; waiting for boot completion..."
|
||||
until [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = "1" ]; do
|
||||
sleep 3
|
||||
done
|
||||
echo "Boot completed."
|
||||
|
||||
# Expose the emulator's adbd (localhost:5555) to the pod network. Plain TCP,
|
||||
# no auth — reachable only inside the LAN via the MetalLB IP.
|
||||
socat TCP-LISTEN:5555,fork,reuseaddr TCP:127.0.0.1:5555 &
|
||||
|
||||
# Supervise: if any background process dies, exit so the pod restarts.
|
||||
wait -n
|
||||
echo "A supervised process exited; restarting pod." >&2
|
||||
exit 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue