From bc021517c0f69a4fb4421bbc8076900f387641a2 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Mon, 25 May 2026 09:39:49 +0400 Subject: [PATCH] =?UTF-8?q?feat(scripts):=20android-build-async.sh=20?= =?UTF-8?q?=E2=80=94=20fire-and-forget=20APK=20builder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing build-tauri-android.sh holds an SSH connection open for the entire Docker build (~10 min). Running it in the background kills it when the SSH keepalive times out (~60s of silence during compile). New script: - uploads the build script to remote and launches it in a detached tmux session so it survives SSH disconnects - exits immediately (fire-and-forget); build result arrives via ntfy - --wait flag blocks + downloads APK when done (same as old script) - same flags as the original: --init, --rust, --no-pull, --debug Usage: ./scripts/android-build-async.sh # fire and forget ./scripts/android-build-async.sh --wait # block until APK downloaded ./scripts/android-build-async.sh --init --wait Co-Authored-By: Claude Sonnet 4.6 --- scripts/android-build-async.sh | 122 +++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100755 scripts/android-build-async.sh diff --git a/scripts/android-build-async.sh b/scripts/android-build-async.sh new file mode 100755 index 0000000..6ba3450 --- /dev/null +++ b/scripts/android-build-async.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Fire-and-forget Android APK builder. +# +# Uploads the build script to SepehrHomeserverdk, starts it in a tmux +# session so it survives SSH disconnects, then exits immediately. +# Progress and the finished APK URL arrive via ntfy.sh/wzp. +# +# Usage: +# ./scripts/android-build-async.sh # build current branch, arm64 +# ./scripts/android-build-async.sh --init # also run cargo tauri android init +# ./scripts/android-build-async.sh --rust # force-clean Rust target cache +# ./scripts/android-build-async.sh --no-pull # skip git fetch on remote +# ./scripts/android-build-async.sh --wait # block until done, then download APK +# +# When the build finishes, ntfy.sh/wzp will show: +# "WZP Tauri arm64 [] ready! " +# or on failure: +# "WZP Tauri Android build FAILED [] (line N) log: " + +set -euo pipefail + +REMOTE_HOST="SepehrHomeserverdk" +NTFY_TOPIC="https://ntfy.sh/wzp" +LOCAL_OUTPUT="target/tauri-android-apk" +TMUX_SESSION="wzp-android" +REMOTE_LOG="/tmp/wzp-tauri-build.log" +SSH_OPTS="-o ConnectTimeout=15 -o ServerAliveInterval=30 -o ServerAliveCountMax=6 -o LogLevel=ERROR" + +BRANCH="${WZP_BRANCH:-$(git -C "$(dirname "$0")/.." branch --show-current 2>/dev/null || echo "")}" +DO_PULL=1 +DO_INIT=0 +BUILD_RELEASE=1 +REBUILD_RUST=0 +BUILD_ARCH="arm64" +DO_WAIT=0 + +for arg in "$@"; do + case "$arg" in + --pull) DO_PULL=1 ;; + --no-pull) DO_PULL=0 ;; + --init) DO_INIT=1 ;; + --debug) BUILD_RELEASE=0 ;; + --rust) REBUILD_RUST=1 ;; + --wait) DO_WAIT=1 ;; + esac +done + +if [ -z "$BRANCH" ]; then + echo "ERROR: could not determine branch (detached HEAD?). Set WZP_BRANCH=name." + exit 1 +fi + +log() { echo -e "\033[1;36m>>> $*\033[0m"; } +err() { echo -e "\033[1;31mERROR: $*\033[0m" >&2; } +ssh_q() { ssh $SSH_OPTS "$REMOTE_HOST" "$@"; } + +# ── Step 1: upload the remote build script ────────────────────────────────── +log "Uploading build script to $REMOTE_HOST..." +# Re-use the existing full build script (it already handles all logic). +scp $SSH_OPTS "$(dirname "$0")/build-tauri-android.sh" "$REMOTE_HOST:/tmp/wzp-tauri-build-full.sh" +ssh_q "chmod +x /tmp/wzp-tauri-build-full.sh" + +# ── Step 2: launch in tmux (detached) ────────────────────────────────────── +log "Starting build in tmux session '$TMUX_SESSION' on $REMOTE_HOST..." +ssh_q "tmux kill-session -t $TMUX_SESSION 2>/dev/null; true" + +# The full script accepts flags directly; pass them through. +REMOTE_FLAGS="" +[ "$DO_PULL" = "1" ] || REMOTE_FLAGS="$REMOTE_FLAGS --no-pull" +[ "$DO_INIT" = "1" ] && REMOTE_FLAGS="$REMOTE_FLAGS --init" +[ "$BUILD_RELEASE" = "0" ] && REMOTE_FLAGS="$REMOTE_FLAGS --debug" +[ "$REBUILD_RUST" = "1" ] && REMOTE_FLAGS="$REMOTE_FLAGS --rust" + +# Run via WZP_BRANCH so the remote script picks up the right branch +# (it calls `git branch --show-current` which would return the remote's +# currently checked-out branch, not necessarily the one we want). +ssh_q "tmux new-session -d -s $TMUX_SESSION \ + 'WZP_BRANCH=$BRANCH bash /tmp/wzp-tauri-build-full.sh $REMOTE_FLAGS \ + 2>&1 | tee $REMOTE_LOG; echo DONE_EXIT_CODE=\$? >> $REMOTE_LOG'" + +log "Build dispatched! Notification on ntfy.sh/wzp when done." +echo "" +echo " Monitor : ssh $REMOTE_HOST 'tail -f $REMOTE_LOG'" +echo " Status : ssh $REMOTE_HOST 'tail -5 $REMOTE_LOG'" +echo " Attach : ssh $REMOTE_HOST 'tmux attach -t $TMUX_SESSION'" +echo "" + +# ── Step 3 (optional --wait): block until done, download APK ─────────────── +if [ "$DO_WAIT" = "0" ]; then + exit 0 +fi + +log "Waiting for build to finish (monitoring $REMOTE_LOG)..." +ssh_q "until grep -qE 'APK_REMOTE_PATH|FAILED|ERROR|DONE_EXIT_CODE' \ + $REMOTE_LOG 2>/dev/null; do sleep 20; done" + +# Check for failure +if ssh_q "grep -q 'FAILED\|ERROR' $REMOTE_LOG 2>/dev/null" && \ + ! ssh_q "grep -q 'APK_REMOTE_PATH' $REMOTE_LOG 2>/dev/null"; then + err "Build failed — check ntfy or: ssh $REMOTE_HOST 'cat $REMOTE_LOG'" + exit 1 +fi + +# Grab APK paths from log +APK_REMOTES=$(ssh_q "grep '^APK_REMOTE_PATH=' $REMOTE_LOG | cut -d= -f2-") +if [ -z "$APK_REMOTES" ]; then + err "No APK_REMOTE_PATH in log — build may have failed silently" + ssh_q "tail -20 $REMOTE_LOG" >&2 + exit 1 +fi + +mkdir -p "$LOCAL_OUTPUT" +echo "$APK_REMOTES" | while IFS= read -r REMOTE_PATH; do + [ -z "$REMOTE_PATH" ] && continue + APK_NAME=$(basename "$REMOTE_PATH") + log "Downloading $APK_NAME..." + scp $SSH_OPTS "$REMOTE_HOST:$REMOTE_PATH" "$LOCAL_OUTPUT/$APK_NAME" + echo " $LOCAL_OUTPUT/$APK_NAME ($(du -h "$LOCAL_OUTPUT/$APK_NAME" | cut -f1))" +done + +log "Done! APKs in $LOCAL_OUTPUT/" +ls -lh "$LOCAL_OUTPUT"/wzp-tauri-*.apk 2>/dev/null || true