feat(android): Tauri 2.x mobile build infrastructure
Adds infrastructure for building the Tauri 2.x Android app (the pivot away from the Kotlin+JNI approach whose stack overflow / libcrypto TLS crash / thread lifecycle hell is documented in the incident report): - scripts/Dockerfile.android-builder: extended to support both the legacy Kotlin+JNI pipeline (cargo-ndk + Gradle) and the new Tauri mobile pipeline (tauri-cli + Node/npm). Adds Node.js 20 LTS, API level 36 + build-tools 35.0.0, and additional apt packages. - scripts/build-tauri-android.sh: fire-and-forget remote build via Docker on SepehrHomeserverdk, with ntfy.sh notifications and rustypaste upload of the resulting APK. Mirrors the pattern of build-tauri-android-docker.sh but targets the new Tauri pipeline. - docs/incident-tauri-android-init-tcb.md: postmortem of the Kotlin+JNI crash cascade that drove the Tauri mobile rewrite decision. Covers the __init_tcb / pthread_create bionic private symbol leak, the staticlib + cdylib crate-type interaction, the Dispatchers.IO 512 KB thread stack overflow, and the tokio runtime / libcrypto TLS race. - scripts/mint-tmux.sh, scripts/prep-linux-mint.sh: general dev infrastructure (tmux + Linux Mint workstation prep scripts). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
# =============================================================================
|
||||
# WZ Phone — Android build environment (Debian 12 / Bookworm)
|
||||
#
|
||||
# Matches the bare-metal build-android.sh environment:
|
||||
# Supports both:
|
||||
# 1. Legacy Kotlin+JNI Android app (via cargo-ndk + gradle)
|
||||
# 2. Tauri 2.x Mobile Android app (via tauri-cli + Node/npm)
|
||||
#
|
||||
# Toolchain:
|
||||
# - Debian 12 (cmake 3.25, no Android cross-compilation bugs)
|
||||
# - JDK 17 (Gradle 8.5 + AGP 8.2.0 compatible)
|
||||
# - NDK 26.1 (last stable before scudo/MTE crash on NDK 27+)
|
||||
# - Rust stable with aarch64-linux-android target + cargo-ndk
|
||||
# - Node.js 20 LTS (for Tauri frontend build)
|
||||
# - Rust stable with all 4 Android targets + cargo-ndk + tauri-cli 2.x
|
||||
#
|
||||
# Build: docker build -t wzp-android-builder -f Dockerfile.android-builder .
|
||||
# =============================================================================
|
||||
@@ -13,6 +18,11 @@ FROM debian:bookworm
|
||||
|
||||
ARG NDK_VERSION=26.1.10909125
|
||||
ARG ANDROID_API=34
|
||||
# Tauri 2.x mobile targets compileSdk 36 + build-tools 35 by default. Install
|
||||
# both 34 (legacy Kotlin app) and 35/36 (Tauri mobile) so the same image works
|
||||
# for both pipelines.
|
||||
ARG ANDROID_API_TAURI=36
|
||||
ARG BUILD_TOOLS_TAURI=35.0.0
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
ANDROID_HOME=/opt/android-sdk \
|
||||
@@ -35,8 +45,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
openjdk-17-jdk-headless \
|
||||
ca-certificates \
|
||||
libasound2-dev \
|
||||
file \
|
||||
xz-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ── Node.js 20 LTS (required by Tauri for frontend build) ────────────────────
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& node --version \
|
||||
&& npm --version
|
||||
|
||||
# ── Android SDK + NDK 26.1 ──────────────────────────────────────────────────
|
||||
RUN mkdir -p $ANDROID_HOME/cmdline-tools \
|
||||
&& cd /tmp \
|
||||
@@ -49,10 +68,36 @@ RUN yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/nu
|
||||
&& $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install \
|
||||
"platforms;android-${ANDROID_API}" \
|
||||
"build-tools;${ANDROID_API}.0.0" \
|
||||
"platforms;android-${ANDROID_API_TAURI}" \
|
||||
"build-tools;${BUILD_TOOLS_TAURI}" \
|
||||
"ndk;${NDK_VERSION}" \
|
||||
"platform-tools" \
|
||||
2>&1 | grep -v '^\[' > /dev/null
|
||||
|
||||
# Work around the API-24 libc.a stub in the NDK. Any C++ static lib we
|
||||
# link into libwzp_desktop_lib.so (e.g. the Oboe audio bridge) pulls in
|
||||
# bionic's static pthread_create from API-24 libc.a via libc++_shared,
|
||||
# and that pthread_create crashes at __init_tcb+4 when called from a
|
||||
# .so loaded via dlopen (the static stub expects libc init state that
|
||||
# only exists for main executables). API-26 has the proper runtime
|
||||
# bindings. Tauri-cli hard-codes aarch64-linux-android24-clang as the
|
||||
# linker and ignores .cargo/config.toml overrides, so the only sure
|
||||
# fix is to replace the NDK's ${abi}24-clang binary itself with a
|
||||
# shim that exec()s the ${abi}26-clang equivalent. Applies to all four
|
||||
# ABIs × {clang, clang++}. The legacy wzp-android crate works without
|
||||
# this because cargo-ndk honours a crate-level linker override; the
|
||||
# shim is the minimal targeted fix for the cargo-tauri build path.
|
||||
# Added as Option 3 for the incremental Step E regression (commit 4250f1b).
|
||||
RUN set -eux; \
|
||||
BIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin; \
|
||||
for abi in aarch64-linux-android armv7a-linux-androideabi i686-linux-android x86_64-linux-android; do \
|
||||
for suffix in clang clang++; do \
|
||||
mv "$BIN/${abi}24-${suffix}" "$BIN/${abi}24-${suffix}.orig"; \
|
||||
printf '#!/bin/sh\nexec "%s/%s26-%s" "$@"\n' "$BIN" "$abi" "$suffix" > "$BIN/${abi}24-${suffix}"; \
|
||||
chmod +x "$BIN/${abi}24-${suffix}"; \
|
||||
done; \
|
||||
done
|
||||
|
||||
# Make SDK world-readable so builder user can access it
|
||||
RUN chmod -R a+rX $ANDROID_HOME
|
||||
|
||||
@@ -64,12 +109,22 @@ USER builder
|
||||
WORKDIR /home/builder
|
||||
|
||||
# ── Rust toolchain ───────────────────────────────────────────────────────────
|
||||
# Install all 4 Android targets (Tauri Mobile builds for all ABIs by default;
|
||||
# cargo-ndk legacy path only needs arm64-v8a — both workflows supported).
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||
| sh -s -- -y --default-toolchain stable \
|
||||
&& . $HOME/.cargo/env \
|
||||
&& rustup target add aarch64-linux-android \
|
||||
&& cargo install cargo-ndk
|
||||
&& rustup target add \
|
||||
aarch64-linux-android \
|
||||
armv7-linux-androideabi \
|
||||
i686-linux-android \
|
||||
x86_64-linux-android \
|
||||
&& cargo install cargo-ndk \
|
||||
&& cargo install tauri-cli --version "^2.0" --locked
|
||||
|
||||
ENV PATH="/home/builder/.cargo/bin:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$JAVA_HOME/bin:$PATH"
|
||||
|
||||
# NDK_HOME is the env var tauri-cli checks (in addition to ANDROID_NDK_HOME)
|
||||
ENV NDK_HOME=$ANDROID_NDK_HOME
|
||||
|
||||
WORKDIR /build/source
|
||||
|
||||
253
scripts/build-tauri-android.sh
Executable file
253
scripts/build-tauri-android.sh
Executable file
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# =============================================================================
|
||||
# WZ Phone — Tauri 2.x Mobile Android APK build
|
||||
#
|
||||
# Builds the desktop/ Tauri app as an Android APK via cargo-tauri inside the
|
||||
# wzp-android-builder Docker image on SepehrHomeserverdk. Uploads the APK to
|
||||
# rustypaste, fires ntfy.sh/wzp notifications at start + finish, and SCPs the
|
||||
# APK back locally.
|
||||
#
|
||||
# Same pattern as build-and-notify.sh but for the Tauri mobile pipeline:
|
||||
# - Source: desktop/src-tauri/ (not android/)
|
||||
# - Build: cargo tauri android build (not gradlew assembleDebug)
|
||||
# - Output: desktop/src-tauri/gen/android/.../*.apk
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/build-tauri-android.sh # full pipeline (debug)
|
||||
# ./scripts/build-tauri-android.sh --release # release APK
|
||||
# ./scripts/build-tauri-android.sh --no-pull # skip git fetch
|
||||
# ./scripts/build-tauri-android.sh --rust # force-clean rust target
|
||||
# ./scripts/build-tauri-android.sh --init # also run `cargo tauri android init`
|
||||
#
|
||||
# Environment:
|
||||
# WZP_BRANCH Branch to build (default: feat/desktop-audio-rewrite)
|
||||
# =============================================================================
|
||||
|
||||
REMOTE_HOST="SepehrHomeserverdk"
|
||||
BASE_DIR="/mnt/storage/manBuilder"
|
||||
NTFY_TOPIC="https://ntfy.sh/wzp"
|
||||
LOCAL_OUTPUT="target/tauri-android-apk"
|
||||
BRANCH="${WZP_BRANCH:-feat/desktop-audio-rewrite}"
|
||||
SSH_OPTS="-o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -o LogLevel=ERROR"
|
||||
|
||||
REBUILD_RUST=0
|
||||
DO_PULL=1
|
||||
DO_INIT=0
|
||||
BUILD_RELEASE=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--rust) REBUILD_RUST=1 ;;
|
||||
--pull) DO_PULL=1 ;;
|
||||
--no-pull) DO_PULL=0 ;;
|
||||
--init) DO_INIT=1 ;;
|
||||
--release) BUILD_RELEASE=1 ;;
|
||||
-h|--help)
|
||||
sed -n '3,30p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
log() { echo -e "\033[1;36m>>> $*\033[0m"; }
|
||||
ssh_cmd() { ssh -A $SSH_OPTS "$REMOTE_HOST" "$@"; }
|
||||
|
||||
notify_local() { curl -s -d "$1" "$NTFY_TOPIC" > /dev/null 2>&1 || true; }
|
||||
|
||||
mkdir -p "$LOCAL_OUTPUT"
|
||||
|
||||
log "Uploading remote build script..."
|
||||
ssh_cmd "cat > /tmp/wzp-tauri-build.sh" <<'REMOTE_SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_DIR="/mnt/storage/manBuilder"
|
||||
NTFY_TOPIC="https://ntfy.sh/wzp"
|
||||
BRANCH="${1:-feat/desktop-audio-rewrite}"
|
||||
DO_PULL="${2:-1}"
|
||||
REBUILD_RUST="${3:-0}"
|
||||
DO_INIT="${4:-0}"
|
||||
BUILD_RELEASE="${5:-0}"
|
||||
|
||||
LOG_FILE=/tmp/wzp-tauri-build.log
|
||||
GIT_HASH="unknown" # populated after fetch
|
||||
ENV_FILE="$BASE_DIR/.env"
|
||||
|
||||
notify() { curl -s -d "$1" "$NTFY_TOPIC" > /dev/null 2>&1 || true; }
|
||||
|
||||
# Upload a file to rustypaste; print URL on stdout (or empty on failure).
|
||||
upload_to_rustypaste() {
|
||||
local file="$1"
|
||||
[ ! -f "$ENV_FILE" ] && { echo ""; return; }
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
if [ -n "${rusty_address:-}" ] && [ -n "${rusty_auth_token:-}" ]; then
|
||||
curl -s -F "file=@$file" -H "Authorization: $rusty_auth_token" "$rusty_address" || echo ""
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# On failure: upload the build log to rustypaste, then notify with hash + url.
|
||||
on_error() {
|
||||
local line="$1"
|
||||
local log_url
|
||||
log_url=$(upload_to_rustypaste "$LOG_FILE" || echo "")
|
||||
if [ -n "$log_url" ]; then
|
||||
notify "WZP Tauri Android build FAILED [$GIT_HASH] (line $line)
|
||||
log: $log_url"
|
||||
else
|
||||
notify "WZP Tauri Android build FAILED [$GIT_HASH] (line $line) — log upload failed, see $LOG_FILE on remote"
|
||||
fi
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
exec > >(tee "$LOG_FILE") 2>&1
|
||||
|
||||
if [ "$DO_PULL" = "1" ]; then
|
||||
echo ">>> git fetch + reset $BRANCH"
|
||||
cd "$BASE_DIR/data/source"
|
||||
git reset --hard HEAD 2>/dev/null || true
|
||||
# NOTE: deliberately do NOT run `git clean -fd` here. It would wipe the
|
||||
# tauri-generated `desktop/src-tauri/gen/android/` scaffold (gradlew,
|
||||
# settings.gradle, etc.) which is expensive to recreate and breaks
|
||||
# subsequent builds with "gradlew not found".
|
||||
git gc --prune=now 2>/dev/null || true
|
||||
git fetch origin "$BRANCH" 2>&1 | tail -3
|
||||
git checkout "$BRANCH" 2>/dev/null || git checkout -b "$BRANCH" "origin/$BRANCH"
|
||||
git reset --hard "origin/$BRANCH"
|
||||
git submodule update --init || true
|
||||
fi
|
||||
|
||||
GIT_HASH=$(cd "$BASE_DIR/data/source" && git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
GIT_MSG=$(cd "$BASE_DIR/data/source" && git log -1 --pretty=%s 2>/dev/null | head -c 60 || echo "?")
|
||||
notify "WZP Tauri Android build STARTED [$GIT_HASH] — $GIT_MSG"
|
||||
|
||||
# Fix perms so uid 1000 can write
|
||||
find "$BASE_DIR/data/source" "$BASE_DIR/data/cache" \
|
||||
! -user 1000 -o ! -group 1000 2>/dev/null | \
|
||||
xargs -r chown 1000:1000 2>/dev/null || true
|
||||
|
||||
# Optionally clean rust target for android triples
|
||||
if [ "$REBUILD_RUST" = "1" ]; then
|
||||
echo ">>> Cleaning Rust android target dirs..."
|
||||
rm -rf "$BASE_DIR/data/cache/target/aarch64-linux-android" \
|
||||
"$BASE_DIR/data/cache/target/armv7-linux-androideabi" \
|
||||
"$BASE_DIR/data/cache/target/i686-linux-android" \
|
||||
"$BASE_DIR/data/cache/target/x86_64-linux-android"
|
||||
fi
|
||||
|
||||
# Profile flag
|
||||
PROFILE_FLAG="--debug"
|
||||
[ "$BUILD_RELEASE" = "1" ] && PROFILE_FLAG=""
|
||||
|
||||
# Persist ~/.android (where the auto-generated debug.keystore lives) so every
|
||||
# build is signed with the SAME key. Without this, every fresh container gets
|
||||
# a new debug keystore and `adb install -r` fails with INSTALL_FAILED_UPDATE_
|
||||
# INCOMPATIBLE because the signature changed.
|
||||
mkdir -p "$BASE_DIR/data/cache/android-home"
|
||||
chown 1000:1000 "$BASE_DIR/data/cache/android-home" 2>/dev/null || true
|
||||
|
||||
docker run --rm \
|
||||
--user 1000:1000 \
|
||||
-e DO_INIT="$DO_INIT" \
|
||||
-e PROFILE_FLAG="$PROFILE_FLAG" \
|
||||
-v "$BASE_DIR/data/source:/build/source" \
|
||||
-v "$BASE_DIR/data/cache/cargo-registry:/home/builder/.cargo/registry" \
|
||||
-v "$BASE_DIR/data/cache/cargo-git:/home/builder/.cargo/git" \
|
||||
-v "$BASE_DIR/data/cache/target:/build/source/target" \
|
||||
-v "$BASE_DIR/data/cache/gradle:/home/builder/.gradle" \
|
||||
-v "$BASE_DIR/data/cache/android-home:/home/builder/.android" \
|
||||
wzp-android-builder \
|
||||
bash -c '
|
||||
set -euo pipefail
|
||||
cd /build/source/desktop
|
||||
|
||||
echo ">>> npm install"
|
||||
npm install --silent 2>&1 | tail -5 || npm install 2>&1 | tail -20
|
||||
|
||||
cd src-tauri
|
||||
|
||||
# Run init if forced, OR if the gradle wrapper is missing. Just checking
|
||||
# for `gen/android` is not enough — Tauri creates a few subdirectories
|
||||
# during build (app/, buildSrc/, .gradle/) that survive a partial wipe and
|
||||
# would make a naive `[ ! -d gen/android ]` check return false even though
|
||||
# the build wrapper itself is gone.
|
||||
if [ "${DO_INIT}" = "1" ] || [ ! -x gen/android/gradlew ]; then
|
||||
echo ">>> cargo tauri android init"
|
||||
cargo tauri android init 2>&1 | tail -20
|
||||
fi
|
||||
|
||||
# ─── wzp-native standalone cdylib (built with cargo-ndk, not cargo-tauri) ──
|
||||
# Produces libwzp_native.so which wzp-desktop dlopens at runtime via
|
||||
# libloading. Split exists because cargo-tauri`s linker wiring pulls
|
||||
# bionic private symbols into any cdylib with cc::Build C++, causing
|
||||
# __init_tcb+4 SIGSEGV. cargo-ndk uses the same linker path as the
|
||||
# legacy wzp-android crate which works.
|
||||
echo ">>> cargo ndk build -p wzp-native --release"
|
||||
JNI_ABI_DIR=gen/android/app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p "$JNI_ABI_DIR"
|
||||
(
|
||||
cd /build/source
|
||||
cargo ndk -t arm64-v8a -o desktop/src-tauri/gen/android/app/src/main/jniLibs \
|
||||
build --release -p wzp-native 2>&1 | tail -10
|
||||
)
|
||||
if [ -f "$JNI_ABI_DIR/libwzp_native.so" ]; then
|
||||
ls -lh "$JNI_ABI_DIR/libwzp_native.so"
|
||||
else
|
||||
echo ">>> WARNING: libwzp_native.so not produced"
|
||||
fi
|
||||
|
||||
echo ">>> cargo tauri android build ${PROFILE_FLAG} --target aarch64 --apk"
|
||||
cargo tauri android build ${PROFILE_FLAG} --target aarch64 --apk
|
||||
|
||||
echo ""
|
||||
echo ">>> Build artifacts:"
|
||||
find gen/android -name "*.apk" -exec ls -lh {} \; 2>/dev/null
|
||||
'
|
||||
|
||||
# Locate the produced APK
|
||||
APK=$(find "$BASE_DIR/data/source/desktop/src-tauri/gen/android" -name "*.apk" -type f 2>/dev/null | head -1)
|
||||
if [ -z "$APK" ] || [ ! -f "$APK" ]; then
|
||||
LOG_URL=$(upload_to_rustypaste "$LOG_FILE" || echo "")
|
||||
if [ -n "$LOG_URL" ]; then
|
||||
notify "WZP Tauri Android build [$GIT_HASH]: no APK produced
|
||||
log: $LOG_URL"
|
||||
else
|
||||
notify "WZP Tauri Android build [$GIT_HASH]: no APK produced — log upload failed"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
APK_SIZE=$(du -h "$APK" | cut -f1)
|
||||
|
||||
RUSTY_URL=$(upload_to_rustypaste "$APK" || echo "")
|
||||
if [ -n "$RUSTY_URL" ]; then
|
||||
notify "WZP Tauri Android build OK [$GIT_HASH] ($APK_SIZE)
|
||||
$RUSTY_URL"
|
||||
else
|
||||
notify "WZP Tauri Android build OK [$GIT_HASH] ($APK_SIZE) — rustypaste upload skipped"
|
||||
fi
|
||||
|
||||
# Print path so the local script can grab it
|
||||
echo "APK_REMOTE_PATH=$APK"
|
||||
REMOTE_SCRIPT
|
||||
|
||||
ssh_cmd "chmod +x /tmp/wzp-tauri-build.sh"
|
||||
|
||||
notify_local "WZP Tauri Android build dispatched (branch=$BRANCH, release=$BUILD_RELEASE)"
|
||||
log "Triggering remote build (branch=$BRANCH)..."
|
||||
|
||||
# Run; capture full output, last line is APK_REMOTE_PATH=...
|
||||
REMOTE_OUTPUT=$(ssh_cmd "/tmp/wzp-tauri-build.sh '$BRANCH' '$DO_PULL' '$REBUILD_RUST' '$DO_INIT' '$BUILD_RELEASE'" || true)
|
||||
echo "$REMOTE_OUTPUT" | tail -60
|
||||
|
||||
APK_REMOTE=$(echo "$REMOTE_OUTPUT" | grep '^APK_REMOTE_PATH=' | tail -1 | cut -d= -f2-)
|
||||
if [ -n "$APK_REMOTE" ]; then
|
||||
log "Downloading APK to $LOCAL_OUTPUT/wzp-tauri.apk..."
|
||||
scp $SSH_OPTS "$REMOTE_HOST:$APK_REMOTE" "$LOCAL_OUTPUT/wzp-tauri.apk"
|
||||
echo " $LOCAL_OUTPUT/wzp-tauri.apk ($(du -h "$LOCAL_OUTPUT/wzp-tauri.apk" | cut -f1))"
|
||||
else
|
||||
log "No APK produced — see ntfy / remote log /tmp/wzp-tauri-build.log"
|
||||
exit 1
|
||||
fi
|
||||
72
scripts/mint-tmux.sh
Executable file
72
scripts/mint-tmux.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# mint-tmux.sh — run a command inside a persistent tmux session on the
|
||||
# Linux Mint build box so the user can attach and watch/interact at any time.
|
||||
#
|
||||
# Usage:
|
||||
# mint-tmux.sh run <window-name> <command...> # start a new tmux window
|
||||
# mint-tmux.sh send <window-name> <text...> # send keys to a window
|
||||
# mint-tmux.sh kill <window-name> # close a window
|
||||
# mint-tmux.sh list # list windows
|
||||
# mint-tmux.sh tail <window-name> # dump last 200 lines
|
||||
#
|
||||
# Session name is always "wzp". Attach manually with:
|
||||
# ssh -t root@172.16.81.192 tmux attach -t wzp
|
||||
#
|
||||
# If the wzp session doesn't exist yet, it's created automatically.
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
HOST="root@172.16.81.192"
|
||||
SESSION="wzp"
|
||||
SSH_OPTS="-o ConnectTimeout=10 -o LogLevel=ERROR"
|
||||
|
||||
ensure_session() {
|
||||
ssh $SSH_OPTS "$HOST" "
|
||||
tmux has-session -t $SESSION 2>/dev/null || tmux new-session -d -s $SESSION -n home 'bash -l'
|
||||
"
|
||||
}
|
||||
|
||||
cmd="${1:-list}"
|
||||
shift || true
|
||||
|
||||
case "$cmd" in
|
||||
run)
|
||||
WIN="${1:?window name required}"; shift
|
||||
ensure_session
|
||||
# Use a heredoc so multi-arg commands don't need escaping
|
||||
CMD="$*"
|
||||
ssh $SSH_OPTS "$HOST" bash -s <<REMOTE
|
||||
if tmux list-windows -t $SESSION -F '#W' 2>/dev/null | grep -qx '$WIN'; then
|
||||
tmux kill-window -t $SESSION:$WIN 2>/dev/null || true
|
||||
fi
|
||||
tmux new-window -t $SESSION -n '$WIN' "bash -l -c '$CMD; echo; echo --- window $WIN exited with code \\\$?; exec bash -l'"
|
||||
REMOTE
|
||||
echo "Started '$WIN' in tmux session $SESSION on $HOST"
|
||||
echo "Attach: ssh -t $HOST tmux attach -t $SESSION"
|
||||
;;
|
||||
send)
|
||||
WIN="${1:?window name required}"; shift
|
||||
TEXT="$*"
|
||||
ssh $SSH_OPTS "$HOST" "tmux send-keys -t $SESSION:$WIN '$TEXT' C-m"
|
||||
;;
|
||||
kill)
|
||||
WIN="${1:?window name required}"
|
||||
ssh $SSH_OPTS "$HOST" "tmux kill-window -t $SESSION:$WIN 2>/dev/null || true"
|
||||
;;
|
||||
list)
|
||||
ensure_session
|
||||
ssh $SSH_OPTS "$HOST" "tmux list-windows -t $SESSION"
|
||||
;;
|
||||
tail)
|
||||
WIN="${1:?window name required}"
|
||||
ssh $SSH_OPTS "$HOST" "tmux capture-pane -p -t $SESSION:$WIN -S -200 || echo 'no such window'"
|
||||
;;
|
||||
attach)
|
||||
exec ssh -t $SSH_OPTS "$HOST" tmux attach -t $SESSION
|
||||
;;
|
||||
*)
|
||||
sed -n '3,20p' "$0"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
167
scripts/prep-linux-mint.sh
Executable file
167
scripts/prep-linux-mint.sh
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Prepare a Linux Mint / Debian / Ubuntu x86_64 host as a full WarzonePhone
|
||||
# Android build environment. Installs everything the docker wzp-android-builder
|
||||
# image has, but directly on the host — so we can iterate locally without
|
||||
# docker layer caching, see real linker output, run gdbserver, etc.
|
||||
#
|
||||
# Target host: root@172.16.81.192 (Linux Mint on the LAN)
|
||||
#
|
||||
# Usage (from the macOS workstation):
|
||||
# scp scripts/prep-linux-mint.sh root@172.16.81.192:/tmp/
|
||||
# ssh root@172.16.81.192 'nohup bash /tmp/prep-linux-mint.sh > /var/log/wzp-prep.log 2>&1 &'
|
||||
#
|
||||
# The script is idempotent: safe to re-run if a step fails. Each stage tests
|
||||
# for its target before doing work. Progress + completion is pinged to
|
||||
# ntfy.sh/wzp so we can track it from the phone.
|
||||
#
|
||||
# On success the host has:
|
||||
# - JDK 17
|
||||
# - Android SDK (cmdline-tools + platforms 34/36, build-tools 34/35, NDK 26.1)
|
||||
# - Node.js 20 LTS + npm
|
||||
# - Rust stable + aarch64/armv7/i686/x86_64 android targets
|
||||
# - cargo-ndk + cargo tauri-cli 2.x
|
||||
# - /opt/wzp/warzonePhone (cloned workspace checkout on feat/desktop-audio-rewrite)
|
||||
#
|
||||
# Everything lives under /opt/android-sdk and /opt/wzp so nothing leaks into $HOME.
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
NTFY_TOPIC="https://ntfy.sh/wzp"
|
||||
NDK_VERSION="26.1.10909125"
|
||||
ANDROID_API=34
|
||||
ANDROID_API_TAURI=36
|
||||
BUILD_TOOLS_TAURI="35.0.0"
|
||||
ANDROID_HOME=/opt/android-sdk
|
||||
WZP_DIR=/opt/wzp
|
||||
GIT_REPO="ssh://git@git.manko.yoga:222/manawenuz/wz-phone.git"
|
||||
GIT_BRANCH="feat/desktop-audio-rewrite"
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
export ANDROID_HOME ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
export NDK_HOME="$ANDROID_NDK_HOME"
|
||||
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:/root/.cargo/bin:$PATH"
|
||||
|
||||
notify() { curl -s -d "$1" "$NTFY_TOPIC" > /dev/null 2>&1 || true; }
|
||||
log() { echo -e "\n\033[1;36m[prep-linux-mint]\033[0m $*"; }
|
||||
die() { notify "wzp prep-linux-mint FAILED: $1"; echo "FATAL: $1" >&2; exit 1; }
|
||||
|
||||
trap 'die "line $LINENO"' ERR
|
||||
|
||||
notify "wzp prep-linux-mint STARTED on $(hostname) ($(whoami))"
|
||||
|
||||
# ─── 1. Base packages ────────────────────────────────────────────────────────
|
||||
log "Installing base packages..."
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
cmake \
|
||||
curl \
|
||||
file \
|
||||
git \
|
||||
libasound2-dev \
|
||||
libc6-dev \
|
||||
libssl-dev \
|
||||
openjdk-17-jdk-headless \
|
||||
pkg-config \
|
||||
unzip \
|
||||
wget \
|
||||
xz-utils \
|
||||
zip
|
||||
|
||||
# ─── 2. Android SDK + NDK ────────────────────────────────────────────────────
|
||||
if [ ! -x "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then
|
||||
log "Installing Android cmdline-tools..."
|
||||
mkdir -p "$ANDROID_HOME/cmdline-tools"
|
||||
cd /tmp
|
||||
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O cmdtools.zip
|
||||
unzip -qo cmdtools.zip -d "$ANDROID_HOME/cmdline-tools"
|
||||
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
|
||||
rm cmdtools.zip
|
||||
else
|
||||
log "cmdline-tools already installed"
|
||||
fi
|
||||
|
||||
if [ ! -d "$ANDROID_HOME/ndk/$NDK_VERSION" ] || \
|
||||
[ ! -d "$ANDROID_HOME/platforms/android-$ANDROID_API" ] || \
|
||||
[ ! -d "$ANDROID_HOME/platforms/android-$ANDROID_API_TAURI" ]; then
|
||||
log "Installing Android platforms + NDK $NDK_VERSION..."
|
||||
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null 2>&1 || true
|
||||
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install \
|
||||
"platforms;android-$ANDROID_API" \
|
||||
"build-tools;$ANDROID_API.0.0" \
|
||||
"platforms;android-$ANDROID_API_TAURI" \
|
||||
"build-tools;$BUILD_TOOLS_TAURI" \
|
||||
"ndk;$NDK_VERSION" \
|
||||
"platform-tools" 2>&1 | grep -v '^\[' || true
|
||||
else
|
||||
log "Android SDK components already installed"
|
||||
fi
|
||||
|
||||
# ─── 3. Node.js 20 LTS ───────────────────────────────────────────────────────
|
||||
if ! command -v node >/dev/null 2>&1 || ! node --version | grep -q "^v20"; then
|
||||
log "Installing Node.js 20 LTS..."
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt-get install -y --no-install-recommends nodejs
|
||||
else
|
||||
log "Node.js already at $(node --version)"
|
||||
fi
|
||||
|
||||
# ─── 4. Rust + Android targets ───────────────────────────────────────────────
|
||||
if ! command -v rustup >/dev/null 2>&1; then
|
||||
log "Installing rustup..."
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
fi
|
||||
. /root/.cargo/env
|
||||
|
||||
log "Ensuring Rust android targets + cargo-ndk + cargo-tauri..."
|
||||
rustup target add \
|
||||
aarch64-linux-android \
|
||||
armv7-linux-androideabi \
|
||||
i686-linux-android \
|
||||
x86_64-linux-android
|
||||
command -v cargo-ndk >/dev/null 2>&1 || cargo install cargo-ndk
|
||||
command -v cargo-tauri >/dev/null 2>&1 || cargo install tauri-cli --version "^2.0" --locked
|
||||
|
||||
# ─── 5. Clone the workspace ──────────────────────────────────────────────────
|
||||
mkdir -p "$WZP_DIR"
|
||||
cd "$WZP_DIR"
|
||||
if [ -d warzonePhone/.git ]; then
|
||||
log "Pulling latest on $GIT_BRANCH..."
|
||||
cd warzonePhone
|
||||
git fetch origin || true
|
||||
git checkout "$GIT_BRANCH" 2>/dev/null || git checkout -b "$GIT_BRANCH" "origin/$GIT_BRANCH"
|
||||
git reset --hard "origin/$GIT_BRANCH" || true
|
||||
else
|
||||
log "Cloning warzonePhone from $GIT_REPO..."
|
||||
# The public repo URL needs ssh keys; if unavailable, skip and let the user sort it later
|
||||
if git clone --branch "$GIT_BRANCH" "$GIT_REPO" warzonePhone 2>/dev/null; then
|
||||
log " cloned ok"
|
||||
else
|
||||
log " clone failed (no SSH keys for $GIT_REPO — skipping, user will rsync)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── 6. Persistent env for the user ──────────────────────────────────────────
|
||||
cat > /etc/profile.d/wzp-android.sh <<ENVEOF
|
||||
export ANDROID_HOME=$ANDROID_HOME
|
||||
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION
|
||||
export NDK_HOME=\$ANDROID_NDK_HOME
|
||||
export PATH=\$ANDROID_HOME/cmdline-tools/latest/bin:\$ANDROID_HOME/platform-tools:/root/.cargo/bin:\$PATH
|
||||
ENVEOF
|
||||
chmod 644 /etc/profile.d/wzp-android.sh
|
||||
|
||||
# ─── 7. Sanity summary ───────────────────────────────────────────────────────
|
||||
log "Sanity checks:"
|
||||
echo " java: $(java -version 2>&1 | head -1)"
|
||||
echo " node: $(node --version)"
|
||||
echo " npm: $(npm --version)"
|
||||
echo " rustc: $(rustc --version)"
|
||||
echo " cargo-ndk: $(cargo ndk --version 2>&1 | head -1)"
|
||||
echo " cargo-tauri:$(cargo tauri --version 2>&1 | head -1)"
|
||||
echo " NDK dir: $ANDROID_NDK_HOME"
|
||||
echo " WZP dir: $WZP_DIR/warzonePhone"
|
||||
|
||||
notify "wzp prep-linux-mint DONE on $(hostname) — ready at /opt/wzp/warzonePhone"
|
||||
log "All done."
|
||||
Reference in New Issue
Block a user