fix: cloud build script — filter by server name, rsync upload, cx33
- Filter hcloud by SERVER_NAME to avoid touching other servers - Use rsync instead of tar (handles submodules, no macOS xattr spam) - Default server type cx33 - Release APK failure is non-fatal (debug APK still produced) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
370
scripts/build-android-cloud.sh
Executable file
370
scripts/build-android-cloud.sh
Executable file
@@ -0,0 +1,370 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build WarzonePhone Android APK using a temporary Hetzner Cloud VPS.
|
||||
# Creates a VM, builds both debug and release APKs, downloads them, destroys the VM.
|
||||
#
|
||||
# Prerequisites: hcloud CLI authenticated, SSH key "wz" registered.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/build-android-cloud.sh Full build (create → build → download → destroy)
|
||||
# ./scripts/build-android-cloud.sh --prepare Create VM and install deps only
|
||||
# ./scripts/build-android-cloud.sh --build Build on existing VM
|
||||
# ./scripts/build-android-cloud.sh --transfer Download APKs from VM
|
||||
# ./scripts/build-android-cloud.sh --destroy Delete the VM
|
||||
# ./scripts/build-android-cloud.sh --all prepare + build + transfer (VM persists)
|
||||
# ./scripts/build-android-cloud.sh --upload Re-upload source to existing VM
|
||||
#
|
||||
# Environment variables (all optional):
|
||||
# WZP_BRANCH Branch to build (default: feat/android-voip-client)
|
||||
# WZP_SERVER_TYPE Hetzner server type (default: cx32 — 4 vCPU, 8GB RAM)
|
||||
# WZP_KEEP_VM Set to 1 to skip destroy on full build
|
||||
|
||||
SSH_KEY_NAME="wz"
|
||||
SSH_KEY_PATH="/Users/manwe/CascadeProjects/wzp"
|
||||
SERVER_TYPE="${WZP_SERVER_TYPE:-cx33}"
|
||||
IMAGE="ubuntu-24.04"
|
||||
SERVER_NAME="wzp-android-builder"
|
||||
REMOTE_USER="root"
|
||||
OUTPUT_DIR="target/android-apk"
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BRANCH="${WZP_BRANCH:-feat/android-voip-client}"
|
||||
KEEP_VM="${WZP_KEEP_VM:-0}"
|
||||
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o LogLevel=ERROR"
|
||||
|
||||
# NDK 26.1 — NDK 27 crashes scudo on Android 16 MTE devices
|
||||
NDK_VERSION="26.1.10909125"
|
||||
ANDROID_API="34"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
log() { echo -e "\n\033[1;36m>>> $*\033[0m"; }
|
||||
err() { echo -e "\033[1;31mERROR: $*\033[0m" >&2; }
|
||||
die() { err "$@"; do_destroy_quiet; exit 1; }
|
||||
|
||||
get_vm_ip() {
|
||||
hcloud server list -o columns=name,ipv4 -o noheader 2>/dev/null | grep "$SERVER_NAME" | awk '{print $2}' | tr -d ' '
|
||||
}
|
||||
|
||||
ssh_cmd() {
|
||||
local ip
|
||||
ip=$(get_vm_ip)
|
||||
[ -n "$ip" ] || die "No VM found. Run --prepare first."
|
||||
ssh $SSH_OPTS -A -i "$SSH_KEY_PATH" "$REMOTE_USER@$ip" "$@"
|
||||
}
|
||||
|
||||
scp_down() {
|
||||
local ip
|
||||
ip=$(get_vm_ip)
|
||||
[ -n "$ip" ] || die "No VM found."
|
||||
scp $SSH_OPTS -i "$SSH_KEY_PATH" "$REMOTE_USER@$ip:$1" "$2"
|
||||
}
|
||||
|
||||
do_destroy_quiet() {
|
||||
local name
|
||||
name=$(hcloud server list -o columns=name -o noheader 2>/dev/null | grep "$SERVER_NAME" | tr -d ' ' || true)
|
||||
if [ -n "$name" ]; then
|
||||
echo ""
|
||||
err "Cleaning up — destroying VM $name"
|
||||
hcloud server delete "$name" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --prepare: Create VM, install all build dependencies
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
do_prepare() {
|
||||
# Check if VM already exists
|
||||
local existing
|
||||
existing=$(hcloud server list -o columns=name -o noheader 2>/dev/null | grep "$SERVER_NAME" | tr -d ' ' || true)
|
||||
if [ -n "$existing" ]; then
|
||||
log "VM already exists: $existing — reusing"
|
||||
do_upload
|
||||
return
|
||||
fi
|
||||
|
||||
log "Creating Hetzner VM ($SERVER_TYPE, $IMAGE)..."
|
||||
hcloud server create \
|
||||
--name "$SERVER_NAME" \
|
||||
--type "$SERVER_TYPE" \
|
||||
--image "$IMAGE" \
|
||||
--ssh-key "$SSH_KEY_NAME" \
|
||||
--location fsn1 \
|
||||
--quiet \
|
||||
|| die "Failed to create VM"
|
||||
|
||||
local ip
|
||||
ip=$(get_vm_ip)
|
||||
[ -n "$ip" ] || die "VM created but no IP found"
|
||||
echo " VM: $SERVER_NAME @ $ip"
|
||||
|
||||
# Wait for SSH
|
||||
log "Waiting for SSH..."
|
||||
local ok=0
|
||||
for i in $(seq 1 30); do
|
||||
if ssh $SSH_OPTS -i "$SSH_KEY_PATH" "$REMOTE_USER@$ip" "echo ok" &>/dev/null; then
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
[ "$ok" -eq 1 ] || die "SSH timeout after 60s"
|
||||
|
||||
# System packages
|
||||
log "Installing system packages (cmake, JDK 17, build tools)..."
|
||||
ssh_cmd "export DEBIAN_FRONTEND=noninteractive && \
|
||||
apt-get update -qq && \
|
||||
apt-get install -y -qq \
|
||||
build-essential cmake curl git libssl-dev pkg-config \
|
||||
unzip wget zip openjdk-17-jdk-headless \
|
||||
> /dev/null 2>&1" \
|
||||
|| die "Failed to install system packages"
|
||||
|
||||
# Verify cmake version (must be <= 3.30)
|
||||
local cmake_ver
|
||||
cmake_ver=$(ssh_cmd "cmake --version | head -1")
|
||||
echo " cmake: $cmake_ver"
|
||||
echo " java: $(ssh_cmd "java -version 2>&1 | head -1")"
|
||||
|
||||
# Rust
|
||||
log "Installing Rust toolchain..."
|
||||
ssh_cmd "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable > /dev/null 2>&1" \
|
||||
|| die "Failed to install Rust"
|
||||
ssh_cmd "source \$HOME/.cargo/env && rustup target add aarch64-linux-android > /dev/null 2>&1"
|
||||
ssh_cmd "source \$HOME/.cargo/env && cargo install cargo-ndk > /dev/null 2>&1" \
|
||||
|| die "Failed to install cargo-ndk"
|
||||
echo " rust: $(ssh_cmd "source \$HOME/.cargo/env && rustc --version")"
|
||||
|
||||
# Android SDK + NDK
|
||||
log "Installing Android SDK + NDK $NDK_VERSION..."
|
||||
ssh_cmd "export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 && \
|
||||
mkdir -p \$HOME/android-sdk/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 \$HOME/android-sdk/cmdline-tools && \
|
||||
mv \$HOME/android-sdk/cmdline-tools/cmdline-tools \$HOME/android-sdk/cmdline-tools/latest 2>/dev/null; \
|
||||
yes | \$HOME/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null 2>&1; \
|
||||
\$HOME/android-sdk/cmdline-tools/latest/bin/sdkmanager --install \
|
||||
'platforms;android-${ANDROID_API}' \
|
||||
'build-tools;${ANDROID_API}.0.0' \
|
||||
'ndk;${NDK_VERSION}' \
|
||||
'platform-tools' \
|
||||
2>&1 | grep -v '^\[' > /dev/null" \
|
||||
|| die "Failed to install Android SDK/NDK"
|
||||
|
||||
ssh_cmd "[ -d \$HOME/android-sdk/ndk/$NDK_VERSION ]" \
|
||||
|| die "NDK not found after install"
|
||||
echo " NDK: $NDK_VERSION"
|
||||
|
||||
# Upload source
|
||||
do_upload
|
||||
|
||||
log "VM ready!"
|
||||
echo " IP: $ip"
|
||||
echo " SSH: ssh -A -i $SSH_KEY_PATH root@$ip"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --upload: Upload source code to VM
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
do_upload() {
|
||||
log "Uploading source code (rsync)..."
|
||||
local ip
|
||||
ip=$(get_vm_ip)
|
||||
[ -n "$ip" ] || die "No VM found."
|
||||
rsync -az --delete \
|
||||
--exclude='target' \
|
||||
--exclude='.git' \
|
||||
--exclude='.claude' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='dist' \
|
||||
--exclude='desktop/src-tauri/gen' \
|
||||
-e "ssh $SSH_OPTS -i $SSH_KEY_PATH" \
|
||||
"$PROJECT_DIR/" "$REMOTE_USER@$ip:/root/wzp-build/"
|
||||
echo " Source uploaded."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --build: Build native .so + debug & release APKs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
do_build() {
|
||||
log "Building Rust native library (arm64-v8a, release)..."
|
||||
|
||||
# ANDROID_NDK must be set (not just ANDROID_NDK_HOME) — cmake checks it
|
||||
ssh_cmd "source \$HOME/.cargo/env && \
|
||||
export ANDROID_HOME=\$HOME/android-sdk && \
|
||||
export ANDROID_NDK_HOME=\$ANDROID_HOME/ndk/$NDK_VERSION && \
|
||||
export ANDROID_NDK=\$ANDROID_NDK_HOME && \
|
||||
cd /root/wzp-build && \
|
||||
cargo ndk -t arm64-v8a \
|
||||
-o android/app/src/main/jniLibs \
|
||||
build --release -p wzp-android 2>&1" | tail -5 \
|
||||
|| die "Rust native build failed"
|
||||
|
||||
ssh_cmd "[ -f /root/wzp-build/android/app/src/main/jniLibs/arm64-v8a/libwzp_android.so ]" \
|
||||
|| die "libwzp_android.so not found after build"
|
||||
|
||||
local so_size
|
||||
so_size=$(ssh_cmd "du -h /root/wzp-build/android/app/src/main/jniLibs/arm64-v8a/libwzp_android.so | cut -f1")
|
||||
echo " .so: $so_size"
|
||||
|
||||
# Generate debug keystore if missing
|
||||
ssh_cmd "[ -f /root/wzp-build/android/keystore/wzp-debug.jks ] || \
|
||||
(mkdir -p /root/wzp-build/android/keystore && \
|
||||
keytool -genkey -v \
|
||||
-keystore /root/wzp-build/android/keystore/wzp-debug.jks \
|
||||
-keyalg RSA -keysize 2048 -validity 10000 \
|
||||
-alias wzp-debug -storepass android -keypass android \
|
||||
-dname 'CN=WZP Debug' > /dev/null 2>&1)"
|
||||
|
||||
# Build debug APK
|
||||
log "Building debug APK..."
|
||||
ssh_cmd "export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 && \
|
||||
export ANDROID_HOME=\$HOME/android-sdk && \
|
||||
cd /root/wzp-build/android && \
|
||||
chmod +x ./gradlew && \
|
||||
./gradlew assembleDebug --no-daemon --warning-mode=none 2>&1" | tail -3 \
|
||||
|| die "Debug APK build failed"
|
||||
|
||||
# Build release APK (uses debug keystore for now)
|
||||
log "Building release APK..."
|
||||
# Copy debug keystore as release keystore (same password in build.gradle)
|
||||
ssh_cmd "cp /root/wzp-build/android/keystore/wzp-debug.jks /root/wzp-build/android/keystore/wzp-release.jks 2>/dev/null; true"
|
||||
ssh_cmd "export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 && \
|
||||
export ANDROID_HOME=\$HOME/android-sdk && \
|
||||
cd /root/wzp-build/android && \
|
||||
./gradlew assembleRelease --no-daemon --warning-mode=none 2>&1" | tail -3 \
|
||||
|| echo " (release APK failed — debug APK still available)"
|
||||
|
||||
log "Build complete!"
|
||||
ssh_cmd "find /root/wzp-build/android -name '*.apk' -path '*/outputs/apk/*' -exec ls -lh {} \;"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --transfer: Download APKs to local machine
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
do_transfer() {
|
||||
log "Downloading APKs..."
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
local ip
|
||||
ip=$(get_vm_ip)
|
||||
|
||||
# Debug APK
|
||||
local debug_apk
|
||||
debug_apk=$(ssh_cmd "find /root/wzp-build/android -name 'app-debug*.apk' -path '*/outputs/apk/*' | head -1")
|
||||
if [ -n "$debug_apk" ]; then
|
||||
scp_down "$debug_apk" "$OUTPUT_DIR/wzp-debug.apk"
|
||||
echo " debug: $OUTPUT_DIR/wzp-debug.apk ($(du -h "$OUTPUT_DIR/wzp-debug.apk" | cut -f1))"
|
||||
fi
|
||||
|
||||
# Release APK
|
||||
local release_apk
|
||||
release_apk=$(ssh_cmd "find /root/wzp-build/android -name 'app-release*.apk' -path '*/outputs/apk/*' | head -1" || true)
|
||||
if [ -n "$release_apk" ]; then
|
||||
scp_down "$release_apk" "$OUTPUT_DIR/wzp-release.apk"
|
||||
echo " release: $OUTPUT_DIR/wzp-release.apk ($(du -h "$OUTPUT_DIR/wzp-release.apk" | cut -f1))"
|
||||
fi
|
||||
|
||||
# Also copy the .so for inspection
|
||||
scp_down "/root/wzp-build/android/app/src/main/jniLibs/arm64-v8a/libwzp_android.so" "$OUTPUT_DIR/libwzp_android.so"
|
||||
echo " .so: $OUTPUT_DIR/libwzp_android.so"
|
||||
|
||||
log "Transfer complete!"
|
||||
echo ""
|
||||
echo " Install debug: adb install -r $OUTPUT_DIR/wzp-debug.apk"
|
||||
[ -f "$OUTPUT_DIR/wzp-release.apk" ] && echo " Install release: adb install -r $OUTPUT_DIR/wzp-release.apk"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --destroy: Delete the VM
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
do_destroy() {
|
||||
local name
|
||||
name=$(hcloud server list -o columns=name -o noheader 2>/dev/null | grep "$SERVER_NAME" | tr -d ' ' || true)
|
||||
if [ -z "$name" ]; then
|
||||
echo "No VM to destroy."
|
||||
return
|
||||
fi
|
||||
log "Deleting VM: $name"
|
||||
hcloud server delete "$name"
|
||||
echo " Done."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Full build: create → build → transfer → destroy
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
do_full() {
|
||||
trap 'err "Build failed!"; do_destroy_quiet; exit 1' ERR
|
||||
|
||||
do_prepare
|
||||
|
||||
# Disable trap during build — release APK failure is non-fatal
|
||||
trap - ERR
|
||||
do_build
|
||||
do_transfer
|
||||
trap 'err "Build failed!"; do_destroy_quiet; exit 1' ERR
|
||||
|
||||
if [ "$KEEP_VM" = "1" ]; then
|
||||
log "VM kept alive (WZP_KEEP_VM=1). Destroy with: $0 --destroy"
|
||||
else
|
||||
do_destroy
|
||||
fi
|
||||
|
||||
log "All done!"
|
||||
echo ""
|
||||
echo " ┌──────────────────────────────────────────────────┐"
|
||||
echo " │ Debug APK: $OUTPUT_DIR/wzp-debug.apk"
|
||||
[ -f "$OUTPUT_DIR/wzp-release.apk" ] && \
|
||||
echo " │ Release APK: $OUTPUT_DIR/wzp-release.apk"
|
||||
echo " │"
|
||||
echo " │ Install: adb install -r $OUTPUT_DIR/wzp-debug.apk"
|
||||
echo " └──────────────────────────────────────────────────┘"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
case "${1:-}" in
|
||||
--prepare) do_prepare ;;
|
||||
--build) do_build ;;
|
||||
--transfer) do_transfer ;;
|
||||
--destroy) do_destroy ;;
|
||||
--upload) do_upload ;;
|
||||
--all)
|
||||
do_prepare
|
||||
do_build
|
||||
do_transfer
|
||||
log "VM still running. Destroy with: $0 --destroy"
|
||||
;;
|
||||
"")
|
||||
do_full
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [--prepare|--build|--transfer|--destroy|--all|--upload]"
|
||||
echo ""
|
||||
echo " (no args) Full build: create VM → build → download → destroy VM"
|
||||
echo " --prepare Create VM and install deps"
|
||||
echo " --build Build on existing VM"
|
||||
echo " --transfer Download APKs from VM"
|
||||
echo " --destroy Delete the VM"
|
||||
echo " --all prepare + build + transfer (VM persists)"
|
||||
echo " --upload Re-upload source to existing VM"
|
||||
echo ""
|
||||
echo "Environment:"
|
||||
echo " WZP_BRANCH=$BRANCH"
|
||||
echo " WZP_SERVER_TYPE=$SERVER_TYPE"
|
||||
echo " WZP_KEEP_VM=$KEEP_VM (set to 1 to skip auto-destroy)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user