#!/usr/bin/env bash set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BOLD='\033[1m' NC='\033[0m' PROJECT_DIR="$HOME/.openclaw-android" PLATFORM_MARKER="$PROJECT_DIR/.platform" OA_VERSION="1.0.27" echo "" echo -e "${BOLD}========================================${NC}" echo -e "${BOLD} OpenClaw on Android - Updater v${OA_VERSION}${NC}" echo -e "${BOLD}========================================${NC}" echo "" step() { echo "" echo -e "${BOLD}[$1/5] $2${NC}" echo "----------------------------------------" } step 1 "Pre-flight Check" if [ -z "${PREFIX:-}" ]; then echo -e "${RED}[FAIL]${NC} Not running in Termux (\$PREFIX not set)" exit 1 fi echo -e "${GREEN}[OK]${NC} Termux detected" if ! command -v curl &>/dev/null; then echo -e "${RED}[FAIL]${NC} curl not found. Install it with: pkg install curl" exit 1 fi OLD_DIR="$HOME/.openclaw-lite" if [ -d "$OLD_DIR" ] && [ ! -d "$PROJECT_DIR" ]; then mv "$OLD_DIR" "$PROJECT_DIR" echo -e "${GREEN}[OK]${NC} Migrated $OLD_DIR -> $PROJECT_DIR" elif [ -d "$OLD_DIR" ] && [ -d "$PROJECT_DIR" ]; then cp -rn "$OLD_DIR"/. "$PROJECT_DIR"/ 2>/dev/null || true rm -rf "$OLD_DIR" echo -e "${GREEN}[OK]${NC} Merged $OLD_DIR into $PROJECT_DIR" else mkdir -p "$PROJECT_DIR" fi if [ -f "$PROJECT_DIR/scripts/lib.sh" ]; then source "$PROJECT_DIR/scripts/lib.sh" fi command -v resolve_npm_registry >/dev/null 2>&1 && resolve_npm_registry || true # Define REPO_TARBALL after sourcing lib.sh to prevent old installs from overwriting it REPO_TARBALL="https://github.com/AidanPark/openclaw-android/archive/refs/heads/main.tar.gz" if ! declare -f detect_platform &>/dev/null; then detect_platform() { if [ -f "$PLATFORM_MARKER" ]; then cat "$PLATFORM_MARKER" return 0 fi if command -v openclaw &>/dev/null; then echo "openclaw" mkdir -p "$(dirname "$PLATFORM_MARKER")" echo "openclaw" > "$PLATFORM_MARKER" return 0 fi echo "" return 1 } fi PLATFORM=$(detect_platform) || { echo -e "${RED}[FAIL]${NC} No platform detected" exit 1 } if [ -z "$PLATFORM" ]; then echo -e "${RED}[FAIL]${NC} No platform detected" exit 1 fi echo -e "${GREEN}[OK]${NC} Platform: $PLATFORM" IS_GLIBC=false if [ -f "$PROJECT_DIR/.glibc-arch" ]; then IS_GLIBC=true echo -e "${GREEN}[OK]${NC} Architecture: glibc" else echo -e "${YELLOW}[INFO]${NC} Architecture: Bionic (migration required)" fi SDK_INT=$(getprop ro.build.version.sdk 2>/dev/null || echo "0") if [ "$SDK_INT" -ge 31 ] 2>/dev/null; then echo -e "${YELLOW}[INFO]${NC} Android 12+ detected — if background processes get killed (signal 9)," echo " see: https://github.com/AidanPark/openclaw-android/blob/main/docs/disable-phantom-process-killer.md" fi step 2 "Download Latest Release (tarball)" mkdir -p "$PREFIX/tmp" RELEASE_TMP=$(mktemp -d "$PREFIX/tmp/oa-update.XXXXXX") || { echo -e "${RED}[FAIL]${NC} Failed to create temp directory" exit 1 } trap 'rm -rf "$RELEASE_TMP"' EXIT echo "Downloading latest scripts..." echo " (This may take a moment depending on network speed)" if curl -sfL "$REPO_TARBALL" | tar xz -C "$RELEASE_TMP" --strip-components=1; then echo -e "${GREEN}[OK]${NC} Downloaded latest release" else echo -e "${RED}[FAIL]${NC} Failed to download release" exit 1 fi REQUIRED_FILES=( "scripts/lib.sh" "scripts/setup-env.sh" "platforms/$PLATFORM/config.env" "platforms/$PLATFORM/update.sh" ) for f in "${REQUIRED_FILES[@]}"; do if [ ! -f "$RELEASE_TMP/$f" ]; then echo -e "${RED}[FAIL]${NC} Missing required file: $f" echo " The downloaded release may be corrupted. Try again." exit 1 fi done echo -e "${GREEN}[OK]${NC} All required files verified" source "$RELEASE_TMP/scripts/lib.sh" step 3 "Update Core Infrastructure" mkdir -p "$PROJECT_DIR/platforms" "$PROJECT_DIR/scripts" "$PROJECT_DIR/patches" rm -rf "$PROJECT_DIR/platforms/$PLATFORM" cp -r "$RELEASE_TMP/platforms/$PLATFORM" "$PROJECT_DIR/platforms/" cp "$RELEASE_TMP/scripts/lib.sh" "$PROJECT_DIR/scripts/lib.sh" cp "$RELEASE_TMP/scripts/setup-env.sh" "$PROJECT_DIR/scripts/setup-env.sh" if [ -f "$RELEASE_TMP/scripts/backup.sh" ]; then cp "$RELEASE_TMP/scripts/backup.sh" "$PROJECT_DIR/scripts/backup.sh" fi cp "$RELEASE_TMP/patches/glibc-compat.js" "$PROJECT_DIR/patches/glibc-compat.js" cp "$RELEASE_TMP/patches/argon2-stub.js" "$PROJECT_DIR/patches/argon2-stub.js" cp "$RELEASE_TMP/patches/spawn.h" "$PROJECT_DIR/patches/spawn.h" cp "$RELEASE_TMP/patches/systemctl" "$PROJECT_DIR/patches/systemctl" # Deploy supplementary glibc libraries (e.g., libcap.so.2 for native binary support) if [ -d "$RELEASE_TMP/patches/glibc-libs" ] && [ -d "$PREFIX/glibc/lib" ]; then for _lib in "$RELEASE_TMP/patches/glibc-libs"/*.so.*; do [ -f "$_lib" ] || continue _fn=$(basename "$_lib") if [ ! -f "$PREFIX/glibc/lib/$_fn" ]; then cp "$_lib" "$PREFIX/glibc/lib/$_fn" _sn=$(echo "$_fn" | sed -E 's/^(lib[^.]+\.so\.[0-9]+)\..*/\1/') [ "$_sn" != "$_fn" ] && ln -sf "$_fn" "$PREFIX/glibc/lib/$_sn" echo -e "${GREEN}[OK]${NC} Installed glibc lib: $_sn" fi done fi # Ensure glibc /etc/hosts exists (localhost resolution) if [ -d "$PREFIX/glibc/etc" ] && [ ! -f "$PREFIX/glibc/etc/hosts" ]; then cat > "$PREFIX/glibc/etc/hosts" <<'HOSTS' 127.0.0.1 localhost localhost.localdomain ::1 localhost ip6-localhost ip6-loopback HOSTS echo -e "${GREEN}[OK]${NC} Created glibc /etc/hosts" fi cp "$RELEASE_TMP/oa.sh" "$PREFIX/bin/oa" chmod +x "$PREFIX/bin/oa" cp "$RELEASE_TMP/update.sh" "$PREFIX/bin/oaupdate" chmod +x "$PREFIX/bin/oaupdate" cp "$RELEASE_TMP/uninstall.sh" "$PROJECT_DIR/uninstall.sh" chmod +x "$PROJECT_DIR/uninstall.sh" if [ "$IS_GLIBC" = false ]; then echo "" echo -e "${BOLD}[MIGRATE] Bionic -> glibc Architecture${NC}" echo "----------------------------------------" if bash "$RELEASE_TMP/scripts/install-glibc.sh" && bash "$RELEASE_TMP/scripts/install-nodejs.sh"; then IS_GLIBC=true echo -e "${GREEN}[OK]${NC} glibc migration complete" else echo -e "${YELLOW}[WARN]${NC} glibc migration failed (non-critical)" fi fi # Update Node.js if a newer version is available if [ "$IS_GLIBC" = true ]; then bash "$RELEASE_TMP/scripts/install-nodejs.sh" || true fi bash "$RELEASE_TMP/scripts/setup-env.sh" GLIBC_NODE_DIR="$PROJECT_DIR/node" GLIBC_BIN_DIR="$PROJECT_DIR/bin" if [ "$IS_GLIBC" = true ]; then # Migrate wrappers from node/bin/ to bin/ (safe from npm overwrites) if [ ! -d "$GLIBC_BIN_DIR" ] || [ ! -x "$GLIBC_BIN_DIR/node" ]; then echo "" echo -e "${BOLD}[MIGRATE] Moving wrappers to $GLIBC_BIN_DIR${NC}" bash "$RELEASE_TMP/scripts/install-nodejs.sh" || true echo -e "${GREEN}[OK]${NC} Wrapper migration complete" fi export PATH="$GLIBC_BIN_DIR:$GLIBC_NODE_DIR/bin:$HOME/.local/bin:$PATH" export OA_GLIBC=1 fi export TMPDIR="$PREFIX/tmp" export TMP="$TMPDIR" export TEMP="$TMPDIR" # Load platform-specific environment variables for current session PLATFORM_ENV_SCRIPT="$RELEASE_TMP/platforms/$PLATFORM/env.sh" if [ -f "$PLATFORM_ENV_SCRIPT" ]; then eval "$(bash "$PLATFORM_ENV_SCRIPT")" fi step 4 "Update Platform" if [ -f "$RELEASE_TMP/platforms/$PLATFORM/update.sh" ]; then bash "$RELEASE_TMP/platforms/$PLATFORM/update.sh" else echo -e "${YELLOW}[WARN]${NC} Platform update script not found" fi step 5 "Update Optional Tools" if command -v code-server &>/dev/null; then if bash "$RELEASE_TMP/scripts/install-code-server.sh" update; then echo -e "${GREEN}[OK]${NC} code-server update step complete" else echo -e "${YELLOW}[WARN]${NC} code-server update failed (non-critical)" fi else echo -e "${YELLOW}[SKIP]${NC} code-server not installed" fi if command -v chromium-browser &>/dev/null || command -v chromium &>/dev/null; then if [ -f "$RELEASE_TMP/scripts/install-chromium.sh" ]; then bash "$RELEASE_TMP/scripts/install-chromium.sh" update || true fi else echo -e "${YELLOW}[SKIP]${NC} Chromium not installed" fi if [ "$IS_GLIBC" = false ]; then echo -e "${YELLOW}[SKIP]${NC} OpenCode requires glibc architecture" else OPENCODE_INSTALLED=false command -v opencode &>/dev/null && OPENCODE_INSTALLED=true if [ "$OPENCODE_INSTALLED" = true ]; then CURRENT_OC_VER=$(opencode --version 2>/dev/null || echo "") LATEST_OC_VER=$(npm view opencode-ai version 2>/dev/null || echo "") if [ -n "$CURRENT_OC_VER" ] && [ -n "$LATEST_OC_VER" ] && [ "$CURRENT_OC_VER" = "$LATEST_OC_VER" ]; then echo -e "${GREEN}[OK]${NC} OpenCode $CURRENT_OC_VER is already the latest" else if [ -n "$CURRENT_OC_VER" ] && [ -n "$LATEST_OC_VER" ] && [ "$CURRENT_OC_VER" != "$LATEST_OC_VER" ]; then echo "OpenCode update available: $CURRENT_OC_VER -> $LATEST_OC_VER" fi echo " (This may take a few minutes for package download and binary processing)" if bash "$RELEASE_TMP/scripts/install-opencode.sh"; then echo -e "${GREEN}[OK]${NC} OpenCode ${LATEST_OC_VER:-} updated" else echo -e "${YELLOW}[WARN]${NC} OpenCode update failed (non-critical)" fi fi else echo -e "${YELLOW}[SKIP]${NC} OpenCode not installed" fi fi update_ai_tool() { local cmd="$1" local pkg="$2" local label="$3" if ! command -v "$cmd" &>/dev/null; then return 1 fi local current_ver latest_ver current_ver=$(npm list -g "$pkg" 2>/dev/null | grep "${pkg##*/}@" | sed 's/.*@//' | tr -d '[:space:]') latest_ver=$(npm view "$pkg" version 2>/dev/null || echo "") if [ -n "$current_ver" ] && [ -n "$latest_ver" ] && [ "$current_ver" = "$latest_ver" ]; then echo -e "${GREEN}[OK]${NC} $label $current_ver is already the latest" elif [ -n "$latest_ver" ]; then echo "Updating $label... ($current_ver -> $latest_ver)" echo " (This may take a few minutes depending on network speed)" if npm install -g "$pkg@latest" --no-fund --no-audit --ignore-scripts; then echo -e "${GREEN}[OK]${NC} $label $latest_ver updated" else echo -e "${YELLOW}[WARN]${NC} $label update failed (non-critical)" fi else echo -e "${YELLOW}[WARN]${NC} Could not check $label latest version" fi return 0 } AI_FOUND=false # Migrate codex from upstream (static musl, broken DNS on Android) to Termux fork (dynamic Bionic) if npm list -g @openai/codex &>/dev/null 2>&1; then echo -e " ${YELLOW}[MIGRATE]${NC} Replacing @openai/codex with Termux-optimized @mmmbuto/codex-cli-termux..." npm uninstall -g @openai/codex 2>/dev/null || true fi update_ai_tool "claude" "@anthropic-ai/claude-code" "Claude Code" && AI_FOUND=true update_ai_tool "gemini" "@google/gemini-cli" "Gemini CLI" && AI_FOUND=true update_ai_tool "codex" "@mmmbuto/codex-cli-termux" "Codex CLI (Termux)" && AI_FOUND=true # Create/refresh codex CLI wrapper (DioNanos fork launcher fix) _codex_bin="$PREFIX/bin/codex" _codex_pkg="$PREFIX/lib/node_modules/@mmmbuto/codex-cli-termux/bin" if [ -f "$_codex_pkg/codex.bin" ]; then [ -L "$_codex_bin" ] && rm -f "$_codex_bin" printf '#!%s/bin/bash\nPKG_BIN="%s"\nexport LD_LIBRARY_PATH="$PKG_BIN:${LD_LIBRARY_PATH:-}"\nexec "$PKG_BIN/codex.bin" "$@"\n' \ "$PREFIX" "$_codex_pkg" > "$_codex_bin" chmod +x "$_codex_bin" fi if [ "$AI_FOUND" = false ]; then echo -e "${YELLOW}[SKIP]${NC} No AI CLI tools installed" fi command -v fix_npm_global_shebangs >/dev/null 2>&1 && fix_npm_global_shebangs || true echo "" echo -e "${GREEN}${BOLD} Update Complete!${NC}" echo "" echo -e "${YELLOW}Run this to apply changes to the current session:${NC}" echo "" echo " source ~/.bashrc"