#!/usr/bin/env bash # OpenClaw Android — Post-Bootstrap Setup # Runs in the terminal after Termux bootstrap extraction. # Installs: git, glibc, Node.js, OpenClaw # # Strategy: # - Termux .deb packages: dpkg-deb -x + relocate (bypasses dpkg hardcoded paths) # - Pacman .pkg.tar.xz packages: tar -xJf + relocate (bypasses pacman entirely) # - Both have files under data/data/com.termux/files/usr/ which we relocate to $PREFIX # # Why not apt-get/dpkg/pacman? # All three have hardcoded /data/data/com.termux/... paths that libtermux-exec # cannot rewrite (it only intercepts execve, not open/opendir). set -eo pipefail # ─── Paths ──────────────────────────────────── : "${PREFIX:?PREFIX not set}" : "${HOME:?HOME not set}" : "${TMPDIR:=$(dirname "$PREFIX")/tmp}" OCA_DIR="$HOME/.openclaw-android" NODE_DIR="$OCA_DIR/node" BIN_DIR="$OCA_DIR/bin" NODE_VERSION="22.22.0" GLIBC_LDSO="$PREFIX/glibc/lib/ld-linux-aarch64.so.1" MARKER="$OCA_DIR/.post-setup-done" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # ─── GitHub mirror fallback (for China/restricted networks) ── REPO_BASE_ORIGIN="https://raw.githubusercontent.com/AidanPark/openclaw-android/main" REPO_BASE="$REPO_BASE_ORIGIN" resolve_repo_base() { if curl -sI --connect-timeout 3 "$REPO_BASE_ORIGIN/oa.sh" >/dev/null 2>&1; then REPO_BASE="$REPO_BASE_ORIGIN"; return 0 fi local mirrors=( "https://ghfast.top/$REPO_BASE_ORIGIN" "https://ghproxy.net/$REPO_BASE_ORIGIN" "https://mirror.ghproxy.com/$REPO_BASE_ORIGIN" ) for m in "${mirrors[@]}"; do if curl -sI --connect-timeout 3 "$m/oa.sh" >/dev/null 2>&1; then echo -e " ${YELLOW}[MIRROR]${NC} Using mirror for GitHub downloads" REPO_BASE="$m"; return 0 fi done return 1 } # Kept in sync with scripts/lib.sh resolve_npm_registry() NPM_REGISTRY_ORIGIN="https://registry.npmjs.org/" NPM_REGISTRY_MIRROR="https://registry.npmmirror.com/" resolve_npm_registry() { local choice local cache_file="$OCA_DIR/.npm-registry" local reachable=0 if curl -sI --connect-timeout 5 "$NPM_REGISTRY_ORIGIN" >/dev/null 2>&1; then choice="$NPM_REGISTRY_ORIGIN" reachable=1 elif curl -sI --connect-timeout 5 "$NPM_REGISTRY_MIRROR" >/dev/null 2>&1; then echo -e " ${YELLOW}[MIRROR]${NC} Using npm mirror: ${NPM_REGISTRY_MIRROR}" choice="$NPM_REGISTRY_MIRROR" reachable=1 else choice="$NPM_REGISTRY_ORIGIN" fi mkdir -p "$(dirname "$cache_file")" printf '%s' "$choice" > "$cache_file.tmp" && mv "$cache_file.tmp" "$cache_file" export NPM_CONFIG_REGISTRY="$choice" if [ "$reachable" -eq 1 ]; then return 0 fi return 1 } # SSL cert for curl (bootstrap curl looks at hardcoded com.termux path) export CURL_CA_BUNDLE="$PREFIX/etc/tls/cert.pem" export SSL_CERT_FILE="$PREFIX/etc/tls/cert.pem" export GIT_SSL_CAINFO="$PREFIX/etc/tls/cert.pem" # Git system config has hardcoded com.termux path — skip it export GIT_CONFIG_NOSYSTEM=1 # Git exec path (git looks for helpers like git-remote-https here) export GIT_EXEC_PATH="$PREFIX/libexec/git-core" # Git template dir (hardcoded /data/data/com.termux path workaround) export GIT_TEMPLATE_DIR="$PREFIX/share/git-core/templates" if [ -f "$MARKER" ]; then echo -e "${GREEN}Post-setup already completed.${NC}" exit 0 fi echo "" echo "══════════════════════════════════════════════" echo " OpenClaw Android — Installing components" echo "══════════════════════════════════════════════" echo "" mkdir -p "$OCA_DIR" "$OCA_DIR/patches" "$TMPDIR" TERMUX_DEB_REPO="https://packages-cf.termux.dev/apt/termux-main" PACMAN_PKG_REPO="https://service.termux-pacman.dev/gpkg/aarch64" TERMUX_INNER="data/data/com.termux/files/usr" DEB_DIR="$TMPDIR/debs" PKG_DIR="$TMPDIR/pkgs" EXTRACT_DIR="$TMPDIR/pkg-extract" # ─── Helper: install_deb ────────────────────── # Downloads a .deb from Termux repo and extracts into $PREFIX install_deb() { local filename="$1" local name name=$(basename "$filename" | sed 's/_[0-9].*//') local url="${TERMUX_DEB_REPO}/${filename}" local deb_file="${DEB_DIR}/$(basename "$filename")" if [ -f "$deb_file" ]; then echo " (cached) $name" else echo " downloading $name..." curl -fsSL --max-time 120 -o "$deb_file" "$url" fi rm -rf "$EXTRACT_DIR" mkdir -p "$EXTRACT_DIR" dpkg-deb -x "$deb_file" "$EXTRACT_DIR" 2>/dev/null # Relocate: data/data/com.termux/files/usr/* → $PREFIX/ if [ -d "$EXTRACT_DIR/$TERMUX_INNER" ]; then cp -a "$EXTRACT_DIR/$TERMUX_INNER/"* "$PREFIX/" 2>/dev/null || true fi rm -rf "$EXTRACT_DIR" } # ─── Helper: install_pacman_pkg ─────────────── # Downloads a .pkg.tar.xz from pacman repo and extracts into target dir install_pacman_pkg() { local filename="$1" local target="$2" # e.g., $PREFIX/glibc local name name=${filename%%-[0-9]*} local url="${PACMAN_PKG_REPO}/${filename}" local pkg_file="${PKG_DIR}/${filename}" if [ -f "$pkg_file" ]; then echo " (cached) $name" else echo " downloading $name..." curl -fsSL --max-time 300 -o "$pkg_file" "$url" fi rm -rf "$EXTRACT_DIR" mkdir -p "$EXTRACT_DIR" tar -xJf "$pkg_file" -C "$EXTRACT_DIR" 2>/dev/null # Pacman packages also extract under data/data/com.termux/files/usr/... local inner="$EXTRACT_DIR/$TERMUX_INNER" if [ -d "$inner/glibc" ]; then # glibc packages go under $PREFIX/glibc/ cp -a "$inner/glibc/"* "$target/" 2>/dev/null || true elif [ -d "$inner" ]; then cp -a "$inner/"* "$target/" 2>/dev/null || true fi rm -rf "$EXTRACT_DIR" } # ─── [1/7] Install essential packages ───────── echo -e "▸ ${YELLOW}[1/7]${NC} Installing essential packages..." mkdir -p "$DEB_DIR" "$PKG_DIR" # Download Packages index to resolve .deb filenames echo " Fetching package index..." PACKAGES_FILE="$TMPDIR/Packages" curl -fsSL --max-time 60 \ "${TERMUX_DEB_REPO}/dists/stable/main/binary-aarch64/Packages" \ -o "$PACKAGES_FILE" # Resolve package filename from Packages index get_deb_filename() { local pkg="$1" awk -v pkg="$pkg" ' /^Package: / { found = ($2 == pkg) } found && /^Filename:/ { print $2; exit } ' "$PACKAGES_FILE" } # Packages to install via dpkg-deb (dependency order, only those missing from bootstrap) DEB_PACKAGES=( libexpat # git dep pcre2 # git dep git # for npm/openclaw ) TOTAL=${#DEB_PACKAGES[@]} COUNT=0 for pkg in "${DEB_PACKAGES[@]}"; do COUNT=$((COUNT + 1)) filename=$(get_deb_filename "$pkg") if [ -z "$filename" ]; then echo -e " ${RED}✗${NC} Package '$pkg' not found in index" continue fi echo " [$COUNT/$TOTAL] $pkg" install_deb "$filename" done # Make sure newly extracted binaries are executable chmod +x "$PREFIX/bin/"* 2>/dev/null || true # Verify git if [ -f "$PREFIX/bin/git" ]; then echo -e " ${GREEN}✓${NC} git $(git --version 2>/dev/null | head -1)" else echo -e " ${RED}✗${NC} git not found after extraction" exit 1 fi # ─── [2/7] glibc runtime ───────────────────── echo -e "▸ ${YELLOW}[2/7]${NC} Installing glibc runtime..." if [ -x "$GLIBC_LDSO" ]; then echo -e " ${GREEN}[SKIP]${NC} glibc already installed" else mkdir -p "$PREFIX/glibc" # Download glibc package directly from pacman repo (no pacman needed) # The gpkg.db tells us: glibc-2.42-0-aarch64.pkg.tar.xz (~9.7MB) echo " Downloading glibc (~10MB)..." install_pacman_pkg "glibc-2.42-0-aarch64.pkg.tar.xz" "$PREFIX/glibc" # gcc-libs-glibc provides libstdc++.so.6 needed by Node.js (~24MB) echo " Downloading gcc-libs (~24MB)..." install_pacman_pkg "gcc-libs-glibc-14.2.1-1-aarch64.pkg.tar.xz" "$PREFIX/glibc" # Verify linker if [ ! -f "$GLIBC_LDSO" ]; then echo -e " ${RED}✗${NC} glibc linker not found at $GLIBC_LDSO" exit 1 fi chmod +x "$GLIBC_LDSO" mkdir -p "$OCA_DIR" touch "$OCA_DIR/.glibc-arch" echo -e " ${GREEN}✓${NC} glibc installed" fi # Install supplementary glibc libraries (libcap etc.) _GLIBC_LIBS_SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/patches/glibc-libs" if [ -d "$PREFIX/glibc/lib" ] && [ -d "$_GLIBC_LIBS_SRC" ]; then for _lib in "$_GLIBC_LIBS_SRC"/*.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}✓${NC} Installed $_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}✓${NC} Created glibc /etc/hosts" fi echo -e " Linker: $GLIBC_LDSO" # ─── [3/7] Node.js ────────────────────────── echo -e "▸ ${YELLOW}[3/7]${NC} Installing Node.js v${NODE_VERSION}..." mkdir -p "$NODE_DIR/bin" _NODE_CMD="" if [ -x "$BIN_DIR/node" ]; then _NODE_CMD="$BIN_DIR/node" elif [ -f "$NODE_DIR/bin/node.real" ] && [ -x "$NODE_DIR/bin/node" ]; then _NODE_CMD="$NODE_DIR/bin/node" fi if [ -n "$_NODE_CMD" ] && "$_NODE_CMD" --version &>/dev/null; then INSTALLED_VER=$("$_NODE_CMD" --version 2>/dev/null || echo "") echo -e " ${GREEN}[SKIP]${NC} Node.js already installed ($INSTALLED_VER)" # Repair wrappers in BIN_DIR (safe from npm overwrites) mkdir -p "$BIN_DIR" if [ -f "$NODE_DIR/lib/node_modules/npm/bin/npm-cli.js" ]; then cat > "$BIN_DIR/npm" << NPMWRAP #!$PREFIX/bin/bash "$BIN_DIR/node" "$NODE_DIR/lib/node_modules/npm/bin/npm-cli.js" "\$@" _npm_exit=\$? case "\$*" in *-g*openclaw*|*--global*openclaw*|*openclaw*-g*|*openclaw*--global*) _oc_bin="$PREFIX/bin/openclaw" _oc_mjs="$PREFIX/lib/node_modules/openclaw/openclaw.mjs" if [ -f "\$_oc_mjs" ]; then [ -L "\$_oc_bin" ] && rm -f "\$_oc_bin" printf '#!$PREFIX/bin/bash\nexec "$BIN_DIR/node" "%s" "\$@"\n' "\$_oc_mjs" > "\$_oc_bin" chmod +x "\$_oc_bin" fi ;; esac # Re-patch codex CLI wrapper after global install/update (DioNanos fork launcher fix) case "\$*" in *codex-cli-termux*) _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 '#!$PREFIX/bin/bash\nPKG_BIN="%s"\nexport LD_LIBRARY_PATH="\$PKG_BIN:\${LD_LIBRARY_PATH:-}"\nexec "\$PKG_BIN/codex.bin" "\$@"\n' "\$_codex_pkg" > "\$_codex_bin" chmod +x "\$_codex_bin" fi ;; esac # Fix shebangs in npm global CLI entry points after global install case "\$*" in *-g*|*--global*) for _js in $PREFIX/lib/node_modules/*/bin/*.js \ $PREFIX/lib/node_modules/@*/*/bin/*.js; do [ -f "\$_js" ] || continue head -1 "\$_js" | grep -q '^#!/usr/bin/env node\$' || continue sed -i "1s|#!/usr/bin/env node|#!$BIN_DIR/node|" "\$_js" done ;; esac exit \$_npm_exit NPMWRAP chmod +x "$BIN_DIR/npm" fi if [ -f "$NODE_DIR/lib/node_modules/npm/bin/npx-cli.js" ]; then cat > "$BIN_DIR/npx" << NPXWRAP #!$PREFIX/bin/bash exec "$BIN_DIR/node" "$NODE_DIR/lib/node_modules/npm/bin/npx-cli.js" "\$@" NPXWRAP chmod +x "$BIN_DIR/npx" fi if [ -f "$NODE_DIR/bin/corepack" ] && head -1 "$NODE_DIR/bin/corepack" 2>/dev/null | grep -q '#!/usr/bin/env node'; then sed -i "1s|#!/usr/bin/env node|#!$BIN_DIR/node|" "$NODE_DIR/bin/corepack" fi else NODE_TAR="node-v${NODE_VERSION}-linux-arm64" echo " Downloading Node.js v${NODE_VERSION} (~25MB)..." curl -fSL --max-time 300 \ "https://nodejs.org/dist/v${NODE_VERSION}/${NODE_TAR}.tar.xz" \ -o "$TMPDIR/${NODE_TAR}.tar.xz" echo " Extracting..." tar -xJf "$TMPDIR/${NODE_TAR}.tar.xz" -C "$NODE_DIR" --strip-components=1 # Move original binary → node.real if [ -f "$NODE_DIR/bin/node" ] && [ ! -L "$NODE_DIR/bin/node" ]; then mv "$NODE_DIR/bin/node" "$NODE_DIR/bin/node.real" fi rm -f "$TMPDIR/${NODE_TAR}.tar.xz" # Create grun-style node wrapper in BIN_DIR (safe from npm overwrites) mkdir -p "$BIN_DIR" cat > "$BIN_DIR/node" << WRAPPER #!${PREFIX}/bin/bash [ -n "\$LD_PRELOAD" ] && export _OA_ORIG_LD_PRELOAD="\$LD_PRELOAD" unset LD_PRELOAD export _OA_WRAPPER_PATH="$BIN_DIR/node" _OA_COMPAT="\$HOME/.openclaw-android/patches/glibc-compat.js" if [ -f "\$_OA_COMPAT" ]; then case "\${NODE_OPTIONS:-}" in *"\$_OA_COMPAT"*) ;; *) export NODE_OPTIONS="\${NODE_OPTIONS:+\$NODE_OPTIONS }-r \$_OA_COMPAT" ;; esac fi _LEADING_OPTS="" _COUNT=0 for _arg in "\$@"; do case "\$_arg" in --*) _COUNT=\$((_COUNT + 1)) ;; *) break ;; esac done if [ \$_COUNT -gt 0 ] && [ \$_COUNT -lt \$# ]; then while [ \$# -gt 0 ]; do case "\$1" in --*) _LEADING_OPTS="\${_LEADING_OPTS:+\$_LEADING_OPTS }\$1"; shift ;; *) break ;; esac done export NODE_OPTIONS="\${NODE_OPTIONS:+\$NODE_OPTIONS }\$_LEADING_OPTS" fi exec "$GLIBC_LDSO" --library-path "$PREFIX/glibc/lib" "$NODE_DIR/bin/node.real" "\$@" WRAPPER chmod +x "$BIN_DIR/node" # Create npm/npx wrappers in BIN_DIR if [ -f "$NODE_DIR/lib/node_modules/npm/bin/npm-cli.js" ]; then cat > "$BIN_DIR/npm" << NPMWRAP #!$PREFIX/bin/bash "$BIN_DIR/node" "$NODE_DIR/lib/node_modules/npm/bin/npm-cli.js" "\$@" _npm_exit=\$? case "\$*" in *-g*openclaw*|*--global*openclaw*|*openclaw*-g*|*openclaw*--global*) _oc_bin="$PREFIX/bin/openclaw" _oc_mjs="$PREFIX/lib/node_modules/openclaw/openclaw.mjs" if [ -f "\$_oc_mjs" ]; then [ -L "\$_oc_bin" ] && rm -f "\$_oc_bin" printf '#!$PREFIX/bin/bash\nexec "$BIN_DIR/node" "%s" "\$@"\n' "\$_oc_mjs" > "\$_oc_bin" chmod +x "\$_oc_bin" fi ;; esac # Re-patch codex CLI wrapper after global install/update (DioNanos fork launcher fix) case "\$*" in *codex-cli-termux*) _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 '#!$PREFIX/bin/bash\nPKG_BIN="%s"\nexport LD_LIBRARY_PATH="\$PKG_BIN:\${LD_LIBRARY_PATH:-}"\nexec "\$PKG_BIN/codex.bin" "\$@"\n' "\$_codex_pkg" > "\$_codex_bin" chmod +x "\$_codex_bin" fi ;; esac # Fix shebangs in npm global CLI entry points after global install case "\$*" in *-g*|*--global*) for _js in $PREFIX/lib/node_modules/*/bin/*.js \ $PREFIX/lib/node_modules/@*/*/bin/*.js; do [ -f "\$_js" ] || continue head -1 "\$_js" | grep -q '^#!/usr/bin/env node\$' || continue sed -i "1s|#!/usr/bin/env node|#!$BIN_DIR/node|" "\$_js" done ;; esac exit \$_npm_exit NPMWRAP chmod +x "$BIN_DIR/npm" fi if [ -f "$NODE_DIR/lib/node_modules/npm/bin/npx-cli.js" ]; then cat > "$BIN_DIR/npx" << NPXWRAP #!$PREFIX/bin/bash exec "$BIN_DIR/node" "$NODE_DIR/lib/node_modules/npm/bin/npx-cli.js" "\$@" NPXWRAP chmod +x "$BIN_DIR/npx" fi # corepack: shebang patch only if [ -f "$NODE_DIR/bin/corepack" ] && head -1 "$NODE_DIR/bin/corepack" 2>/dev/null | grep -q '#!/usr/bin/env node'; then sed -i "1s|#!/usr/bin/env node|#!$BIN_DIR/node|" "$NODE_DIR/bin/corepack" fi # Configure npm export PATH="$BIN_DIR:$NODE_DIR/bin:$PATH" "$BIN_DIR/npm" config set script-shell "$PREFIX/bin/sh" 2>/dev/null || true # Verify NODE_VER=$("$BIN_DIR/node" --version 2>/dev/null) || { echo -e " ${RED}✗${NC} Node.js verification failed" exit 1 } echo -e " ${GREEN}✓${NC} Node.js $NODE_VER (glibc)" fi # ─── [4/7] OpenClaw ───────────────────────── echo -e "▸ ${YELLOW}[4/7]${NC} Installing OpenClaw..." export PATH="$BIN_DIR:$NODE_DIR/bin:$PATH" # Auto-detect GitHub mirror for restricted networks resolve_repo_base # Auto-detect npm registry (session-scoped via NPM_CONFIG_REGISTRY env var). # Does NOT write to ~/.npmrc — see CHANGELOG v1.0.24. resolve_npm_registry || true # Force git to use HTTPS instead of SSH (no SSH client available). # Preserve any existing user .gitconfig (name, email, aliases); only set our keys. touch "$HOME/.gitconfig" git config --global http.sslCAInfo "$PREFIX/etc/tls/cert.pem" git config --global --unset-all url."https://github.com/".insteadOf 2>/dev/null || true git config --global --add url."https://github.com/".insteadOf "ssh://git@github.com/" git config --global --add url."https://github.com/".insteadOf "git@github.com:" # Git wrapper: replace $PREFIX/bin/git with a wrapper that: # 1. Strips --recurse-submodules (triggers open() on hardcoded com.termux path) # 2. Cleans existing target dirs before clone (npm's withTempDir creates dir first) # npm caches git path at module load via which.sync('git'), so we must replace the binary. # $PREFIX/bin/git is a symlink -> ../libexec/git-core/git (the real ELF binary). REAL_GIT="$PREFIX/libexec/git-core/git" if [ -f "$REAL_GIT" ] && [ ! -f "$PREFIX/bin/git.wrapper-installed" ]; then echo " Installing git wrapper (strips --recurse-submodules)..." rm -f "$PREFIX/bin/git" # Write shebang with absolute path (no LD_PRELOAD = no /bin/bash rewrite) echo "#!${PREFIX}/bin/bash" > "$PREFIX/bin/git" cat >> "$PREFIX/bin/git" << 'ENDWRAP' filtered=() is_clone=false for a in "$@"; do case "$a" in --recurse-submodules) ;; clone) is_clone=true; filtered+=("$a") ;; *) filtered+=("$a") ;; esac done if $is_clone; then for a in "${filtered[@]}"; do case "$a" in clone|--*|-*|http*|ssh*|git*|[0-9]) ;; *) [ -d "$a" ] && rm -rf "$a" ;; esac done fi ENDWRAP echo "exec \"$REAL_GIT\" \"\${filtered[@]}\"" >> "$PREFIX/bin/git" chmod +x "$PREFIX/bin/git" touch "$PREFIX/bin/git.wrapper-installed" echo -e " ${GREEN}\u2713${NC} git wrapper installed" else if [ -f "$PREFIX/bin/git.wrapper-installed" ]; then echo -e " ${GREEN}[SKIP]${NC} git wrapper already installed" else echo -e " ${RED}\u2717${NC} Real git not found at $REAL_GIT" exit 1 fi fi if command -v openclaw &>/dev/null 2>&1; then OC_VER=$(openclaw --version 2>/dev/null || echo "unknown") echo -e " ${GREEN}[SKIP]${NC} OpenClaw already installed ($OC_VER)" else # Clean npm cache tmp dir (leftover from previous failed installs) rm -rf "$HOME/.npm/_cacache/tmp" 2>/dev/null || true npm install -g openclaw@latest --ignore-scripts 2>&1 OC_VER=$(openclaw --version 2>/dev/null || echo "installed") echo -e " ${GREEN}✓${NC} OpenClaw $OC_VER" fi # Restore optional/channel deps that --ignore-scripts skips. # Uses npm_config_ignore_scripts=true so sharp's native build doesn't block. OPENCLAW_DIR="$(npm root -g)/openclaw" if [ -d "$OPENCLAW_DIR" ]; then echo " Restoring optional dependencies..." (cd "$OPENCLAW_DIR" && npm_config_ignore_scripts=true node scripts/postinstall-bundled-plugins.mjs 2>/dev/null) || true fi # Install clawdhub (skill manager) echo " Installing clawdhub..." if npm install -g clawdhub --no-fund --no-audit; then echo -e " ${GREEN}✓${NC} clawdhub installed" CLAWHUB_DIR="$(npm root -g)/clawdhub" if [ -d "$CLAWHUB_DIR" ] && ! (cd "$CLAWHUB_DIR" && node -e "require('undici')" 2>/dev/null); then echo " Installing undici dependency for clawdhub..." (cd "$CLAWHUB_DIR" && npm install undici --no-fund --no-audit) || true fi else echo -e " ${YELLOW}[WARN]${NC} clawdhub installation failed (non-critical)" fi # PyYAML (for .skill packaging) command -v python &>/dev/null && { python -c "import yaml" 2>/dev/null || pip install pyyaml -q || true; } # Run openclaw update (builds native modules like sharp) echo " Running: openclaw update (this may take 5-10 minutes)..." openclaw update || true # ─── [5/7] Patches ────────────────────────── echo -e "▸ ${YELLOW}[5/7]${NC} Applying patches..." # Copy glibc-compat.js from project (bundled alongside this script) COMPAT_SRC="$(dirname "$0")/glibc-compat.js" if [ -f "$COMPAT_SRC" ]; then cp "$COMPAT_SRC" "$OCA_DIR/patches/glibc-compat.js" else # Fallback: download from repo curl -fsSL "$REPO_BASE/patches/glibc-compat.js" \ -o "$OCA_DIR/patches/glibc-compat.js" 2>/dev/null || true fi # systemctl stub printf '#!%s/bin/bash\nexit 0\n' "$PREFIX" > "$PREFIX/bin/systemctl" chmod +x "$PREFIX/bin/systemctl" # sharp WASM fallback (prebuilt native binaries don't load on Android) if [ -d "$OPENCLAW_DIR/node_modules/sharp" ]; then if ! node -e "require('$OPENCLAW_DIR/node_modules/sharp')" 2>/dev/null; then echo " Installing sharp WebAssembly runtime..." (cd "$OPENCLAW_DIR" && npm install @img/sharp-wasm32 --force --no-audit --no-fund 2>&1 | tail -3) || true fi fi echo -e " ${GREEN}✓${NC} Patches applied" # ─── [6/7] Environment ────────────────────── echo -e "▸ ${YELLOW}[6/7]${NC} Configuring environment..." cat > "$HOME/.bashrc" << BASHRC # OpenClaw Android environment export PREFIX="$PREFIX" export HOME="$HOME" export TMPDIR="$TMPDIR" export PATH="$BIN_DIR:$NODE_DIR/bin:\$PREFIX/bin:\$PATH" export LD_LIBRARY_PATH="$PREFIX/lib" export LD_PRELOAD="$PREFIX/lib/libtermux-exec.so" export TERMUX__PREFIX="$PREFIX" export TERMUX_PREFIX="$PREFIX" export LANG=en_US.UTF-8 export TERM=xterm-256color export OA_GLIBC=1 export CONTAINER=1 export SSL_CERT_FILE="$PREFIX/etc/tls/cert.pem" export CURL_CA_BUNDLE="$PREFIX/etc/tls/cert.pem" export GIT_SSL_CAINFO="$PREFIX/etc/tls/cert.pem" export GIT_CONFIG_NOSYSTEM=1 export GIT_EXEC_PATH="$PREFIX/libexec/git-core" export GIT_TEMPLATE_DIR="$PREFIX/share/git-core/templates" export CLAWDHUB_WORKDIR="$HOME/.openclaw/workspace" export CPATH="$PREFIX/include/glib-2.0:$PREFIX/lib/glib-2.0/include" # npm registry (auto-detected by OpenClaw Android, safe to override manually) [ -z "\${NPM_CONFIG_REGISTRY:-}" ] && [ -s "\$HOME/.openclaw-android/.npm-registry" ] && \\ export NPM_CONFIG_REGISTRY="\$(cat "\$HOME/.openclaw-android/.npm-registry")" BASHRC echo -e " ${GREEN}✓${NC} ~/.bashrc configured" # oa CLI (enables oa --update, oa --backup, etc.) if curl -fsSL "$REPO_BASE/oa.sh" \ -o "$PREFIX/bin/oa" 2>/dev/null; then chmod +x "$PREFIX/bin/oa" echo -e " ${GREEN}✓${NC} oa CLI installed" else echo -e " ${YELLOW}[WARN]${NC} oa CLI installation failed (non-critical)" fi # ─── [7/7] Optional Tools ────────────────── TOOL_CONF="$OCA_DIR/tool-selections.conf" if [ -f "$TOOL_CONF" ]; then # shellcheck source=/dev/null source "$TOOL_CONF" HAS_TOOLS=false for var in INSTALL_TMUX INSTALL_TTYD INSTALL_DUFS INSTALL_CODE_SERVER INSTALL_PLAYWRIGHT INSTALL_CLAUDE_CODE INSTALL_GEMINI_CLI INSTALL_CODEX_CLI; do eval "val=\${$var:-false}" # shellcheck disable=SC2154 [ "$val" = "true" ] && HAS_TOOLS=true && break done if $HAS_TOOLS; then echo -e "▸ ${YELLOW}[7/7]${NC} Installing optional tools..." # Helper: install .deb with direct dependencies install_with_deps() { local pkg="$1" local deps deps=$(awk -v pkg="$pkg" ' /^Package: / { found = ($2 == pkg) } found && /^Depends:/ { gsub(/^Depends: /, "") gsub(/ *\([^)]*\)/, "") gsub(/, /, "\n") print; exit } ' "$PACKAGES_FILE") while IFS= read -r dep; do dep=$(echo "$dep" | tr -d ' ') [ -z "$dep" ] && continue local dep_file dep_file=$(get_deb_filename "$dep") if [ -n "$dep_file" ]; then install_deb "$dep_file" 2>/dev/null || true; fi done <<< "$deps" local filename filename=$(get_deb_filename "$pkg") [ -n "$filename" ] && install_deb "$filename" } # Termux packages [ "${INSTALL_TMUX:-false}" = "true" ] && { echo " Installing tmux..." install_with_deps tmux echo -e " ${GREEN}✓${NC} tmux" } [ "${INSTALL_TTYD:-false}" = "true" ] && { echo " Installing ttyd..." install_with_deps ttyd echo -e " ${GREEN}✓${NC} ttyd" } [ "${INSTALL_DUFS:-false}" = "true" ] && { echo " Installing dufs..." install_with_deps dufs echo -e " ${GREEN}✓${NC} dufs" } # npm packages [ "${INSTALL_CODE_SERVER:-false}" = "true" ] && { echo " Installing code-server (this may take a while)..." npm install -g code-server 2>&1 || true echo -e " ${GREEN}✓${NC} code-server" } [ "${INSTALL_PLAYWRIGHT:-false}" = "true" ] && { echo " Installing Playwright (playwright-core)..." npm install -g playwright-core 2>&1 || true # Set Playwright environment variables if Chromium is available CHROMIUM_BIN="" for bin in "$PREFIX/bin/chromium-browser" "$PREFIX/bin/chromium"; do [ -x "$bin" ] && CHROMIUM_BIN="$bin" && break done if [ -n "$CHROMIUM_BIN" ]; then PW_MARKER_START="# >>> Playwright >>>" PW_MARKER_END="# <<< Playwright <<<" if ! grep -qF "$PW_MARKER_START" "$HOME/.bashrc"; then cat >> "$HOME/.bashrc" << PWENV ${PW_MARKER_START} export PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH="$CHROMIUM_BIN" export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 ${PW_MARKER_END} PWENV fi echo -e " ${GREEN}✓${NC} Playwright (env: PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=$CHROMIUM_BIN)" else echo -e " ${GREEN}✓${NC} Playwright (install Chromium later via 'oa --install' for full setup)" fi } [ "${INSTALL_CLAUDE_CODE:-false}" = "true" ] && { echo " Installing Claude Code..." npm install -g @anthropic-ai/claude-code 2>&1 || true echo -e " ${GREEN}✓${NC} Claude Code" } [ "${INSTALL_GEMINI_CLI:-false}" = "true" ] && { echo " Installing Gemini CLI..." npm install -g @google/gemini-cli 2>&1 || true echo -e " ${GREEN}✓${NC} Gemini CLI" } [ "${INSTALL_CODEX_CLI:-false}" = "true" ] && { echo " Installing Codex CLI (Termux)..." npm install -g @mmmbuto/codex-cli-termux 2>&1 || true # Create 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 echo -e " ${GREEN}✓${NC} Codex CLI (Termux)" } # Fix shebangs in npm global CLIs (kept in sync with scripts/lib.sh fix_npm_global_shebangs()) for _js in "$PREFIX/lib/node_modules"/*/bin/*.js \ "$PREFIX/lib/node_modules"/@*/*/bin/*.js; do [ -f "$_js" ] || continue head -1 "$_js" | grep -q '^#!/usr/bin/env node$' || continue sed -i "1s|#!/usr/bin/env node|#!$BIN_DIR/node|" "$_js" done else echo -e "▸ ${YELLOW}[7/7]${NC} No optional tools selected" fi else echo -e "▸ ${YELLOW}[7/7]${NC} No optional tools selected" fi # ─── Cleanup ──────────────────────────────── rm -rf "$DEB_DIR" "$PKG_DIR" "$PACKAGES_FILE" "$TMPDIR/gpkg.db" 2>/dev/null || true # ─── Done ──────────────────────────────────── touch "$MARKER" echo "" echo "══════════════════════════════════════════════" echo -e " ${GREEN}✓ Installation complete!${NC}" echo "══════════════════════════════════════════════" echo "" echo " Loading environment..." source "$HOME/.bashrc" echo "" echo " Starting OpenClaw onboard..." echo "" openclaw onboard