238 lines
7.5 KiB
Bash
238 lines
7.5 KiB
Bash
#!/usr/bin/env bash
|
|
# install-opencode.sh - Install OpenCode on Termux
|
|
# Uses proot + ld.so concatenation for Bun standalone binaries.
|
|
#
|
|
# This script is NON-CRITICAL: failure does not affect OpenClaw.
|
|
#
|
|
# Why proot + ld.so concatenation?
|
|
# 1. Bun uses raw syscalls (LD_PRELOAD shims don't work)
|
|
# 2. patchelf causes SIGSEGV on Android (seccomp)
|
|
# 3. Bun standalone reads embedded JS via /proc/self/exe offset
|
|
# → grun makes /proc/self/exe point to ld.so, breaking this
|
|
# → concatenating ld.so + binary data fixes the offset math
|
|
set -euo pipefail
|
|
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
NC='\033[0m'
|
|
|
|
OPENCLAW_DIR="$HOME/.openclaw-android"
|
|
GLIBC_LDSO="$PREFIX/glibc/lib/ld-linux-aarch64.so.1"
|
|
PROOT_ROOT="$OPENCLAW_DIR/proot-root"
|
|
|
|
fail_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
exit 0
|
|
}
|
|
|
|
echo "=== Installing OpenCode ==="
|
|
echo ""
|
|
|
|
# ── Pre-checks ───────────────────────────────
|
|
|
|
if [ ! -f "$OPENCLAW_DIR/.glibc-arch" ]; then
|
|
fail_warn "glibc environment not installed — skipping OpenCode install"
|
|
fi
|
|
|
|
if [ ! -x "$GLIBC_LDSO" ]; then
|
|
fail_warn "glibc dynamic linker not found — skipping OpenCode install"
|
|
fi
|
|
|
|
if ! command -v proot &>/dev/null; then
|
|
echo "Installing proot..."
|
|
if ! pkg install -y proot; then
|
|
fail_warn "Failed to install proot — skipping OpenCode install"
|
|
fi
|
|
fi
|
|
|
|
# ── Helper: Create ld.so concatenation ───────
|
|
|
|
# Bun standalone binaries store embedded JS at the end of the file.
|
|
# The last 8 bytes contain the original file size as a LE u64.
|
|
# Bun calculates: embedded_offset = current_file_size - stored_size
|
|
# By prepending ld.so, current_file_size increases, and the offset
|
|
# shifts correctly to find the embedded data after ld.so.
|
|
create_ldso_concat() {
|
|
local bin_path="$1"
|
|
local output_path="$2"
|
|
local name="$3"
|
|
|
|
if [ ! -f "$bin_path" ]; then
|
|
echo -e "${RED}[FAIL]${NC} $name binary not found at $bin_path"
|
|
return 1
|
|
fi
|
|
|
|
echo " Creating ld.so concatenation for $name..."
|
|
echo " (Copying large binary files — this may take a minute)"
|
|
cp "$GLIBC_LDSO" "$output_path"
|
|
cat "$bin_path" >> "$output_path"
|
|
chmod +x "$output_path"
|
|
|
|
# Verify the Bun magic marker exists at the end
|
|
local marker
|
|
marker=$(tail -c 32 "$output_path" | strings 2>/dev/null | grep -o "Bun" || true)
|
|
if [ -n "$marker" ]; then
|
|
echo -e "${GREEN}[OK]${NC} $name ld.so concatenation created ($(du -h "$output_path" | cut -f1))"
|
|
else
|
|
echo -e "${YELLOW}[WARN]${NC} $name ld.so concatenation created but Bun marker not found"
|
|
fi
|
|
}
|
|
|
|
# ── Helper: Create proot wrapper script ──────
|
|
|
|
create_proot_wrapper() {
|
|
local wrapper_path="$1"
|
|
local ldso_path="$2"
|
|
local bin_path="$3"
|
|
local name="$4"
|
|
|
|
cat > "$wrapper_path" << WRAPPER
|
|
#!/data/data/com.termux/files/usr/bin/bash
|
|
# $name wrapper — proot + ld.so concatenation
|
|
# proot: intercepts raw syscalls (Bun uses inline asm, not glibc calls)
|
|
# ld.so concat: fixes /proc/self/exe offset for embedded JS
|
|
# unset LD_PRELOAD: prevents Bionic libtermux-exec.so version mismatch
|
|
unset LD_PRELOAD
|
|
exec proot \\
|
|
-R "$PROOT_ROOT" \\
|
|
-b "\$PREFIX:\$PREFIX" \\
|
|
-b /system:/system \\
|
|
-b /apex:/apex \\
|
|
-w "\$(pwd)" \\
|
|
"$ldso_path" "$bin_path" "\$@"
|
|
WRAPPER
|
|
chmod +x "$wrapper_path"
|
|
echo -e "${GREEN}[OK]${NC} $name wrapper script created"
|
|
}
|
|
|
|
# ── Step 1: Create minimal proot rootfs ──────
|
|
|
|
echo "Setting up proot minimal rootfs..."
|
|
mkdir -p "$PROOT_ROOT/data/data/com.termux/files"
|
|
echo -e "${GREEN}[OK]${NC} proot rootfs created at $PROOT_ROOT"
|
|
|
|
# ── Step 2: Install Bun (package manager) ────
|
|
|
|
echo ""
|
|
echo "Installing Bun..."
|
|
echo " (Downloading and installing Bun runtime — this may take a few minutes)"
|
|
BUN_BIN="$HOME/.bun/bin/bun"
|
|
if [ -x "$BUN_BIN" ]; then
|
|
echo -e "${GREEN}[OK]${NC} Bun already installed"
|
|
else
|
|
# Install bun via the official installer
|
|
# Bun is needed to download the opencode package
|
|
if curl -fsSL https://bun.sh/install | bash 2>/dev/null; then
|
|
echo -e "${GREEN}[OK]${NC} Bun installed"
|
|
else
|
|
fail_warn "Failed to install Bun — cannot install OpenCode"
|
|
fi
|
|
BUN_BIN="$HOME/.bun/bin/bun"
|
|
fi
|
|
|
|
# Bun itself needs grun to run (it's a glibc binary)
|
|
# Create a temporary wrapper for bun
|
|
BUN_WRAPPER=$(mktemp "$PREFIX/tmp/bun-wrapper.XXXXXX")
|
|
cat > "$BUN_WRAPPER" << WRAPPER
|
|
#!/data/data/com.termux/files/usr/bin/bash
|
|
unset LD_PRELOAD
|
|
exec "$GLIBC_LDSO" "$BUN_BIN" "\$@"
|
|
WRAPPER
|
|
chmod +x "$BUN_WRAPPER"
|
|
|
|
# Verify bun works
|
|
BUN_VER=$("$BUN_WRAPPER" --version 2>/dev/null) || {
|
|
rm -f "$BUN_WRAPPER"
|
|
fail_warn "Bun verification failed"
|
|
}
|
|
echo -e "${GREEN}[OK]${NC} Bun $BUN_VER verified"
|
|
|
|
# ── Step 3: Install OpenCode ────────────────
|
|
|
|
echo ""
|
|
echo "Installing OpenCode..."
|
|
echo " (Downloading package — this may take a few minutes)"
|
|
# Use bun to install opencode-ai package
|
|
# Note: bun may exit non-zero due to optional platform packages (windows, darwin)
|
|
# failing to install, but the linux-arm64 binary is still installed successfully.
|
|
"$BUN_WRAPPER" install -g opencode-ai 2>&1 || true
|
|
echo -e "${GREEN}[OK]${NC} opencode-ai package install attempted"
|
|
|
|
# Remove bun's global shim — it tries to spawnSync the native binary directly,
|
|
# which fails on Termux (missing /lib/ld-linux-aarch64.so.1).
|
|
# Our proot wrapper at $PREFIX/bin/opencode will be used instead.
|
|
rm -f "$HOME/.bun/bin/opencode"
|
|
|
|
# Find the OpenCode binary
|
|
OPENCODE_BIN=""
|
|
for pattern in \
|
|
"$HOME/.bun/install/cache/opencode-linux-arm64@*/bin/opencode" \
|
|
"$HOME/.bun/install/global/node_modules/opencode-linux-arm64/bin/opencode"; do
|
|
# shellcheck disable=SC2012,SC2086
|
|
FOUND=$(ls $pattern 2>/dev/null | sort -V | tail -1 || true)
|
|
if [ -n "$FOUND" ] && [ -f "$FOUND" ]; then
|
|
OPENCODE_BIN="$FOUND"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$OPENCODE_BIN" ]; then
|
|
rm -f "$BUN_WRAPPER"
|
|
fail_warn "OpenCode binary not found after installation"
|
|
fi
|
|
echo -e "${GREEN}[OK]${NC} OpenCode binary found: $OPENCODE_BIN"
|
|
|
|
# Create ld.so concatenation
|
|
mkdir -p "$OPENCLAW_DIR/bin"
|
|
LDSO_OPENCODE="$OPENCLAW_DIR/bin/ld.so.opencode"
|
|
create_ldso_concat "$OPENCODE_BIN" "$LDSO_OPENCODE" "OpenCode" || {
|
|
rm -f "$BUN_WRAPPER"
|
|
fail_warn "Failed to create OpenCode ld.so concatenation"
|
|
}
|
|
|
|
# Create wrapper script
|
|
create_proot_wrapper "$PREFIX/bin/opencode" "$LDSO_OPENCODE" "$OPENCODE_BIN" "OpenCode"
|
|
|
|
# Verify
|
|
echo ""
|
|
echo "Verifying OpenCode..."
|
|
OC_VER=$("$PREFIX/bin/opencode" --version 2>/dev/null) || true
|
|
if [ -n "$OC_VER" ]; then
|
|
echo -e "${GREEN}[OK]${NC} OpenCode v$OC_VER verified"
|
|
else
|
|
echo -e "${YELLOW}[WARN]${NC} OpenCode --version check failed (may work in interactive mode)"
|
|
fi
|
|
|
|
|
|
# ── Step 4: Create OpenCode config ───────────
|
|
|
|
echo ""
|
|
echo "Setting up OpenCode configuration..."
|
|
|
|
OPENCODE_CONFIG_DIR="$HOME/.config/opencode"
|
|
OPENCODE_CONFIG="$OPENCODE_CONFIG_DIR/opencode.json"
|
|
mkdir -p "$OPENCODE_CONFIG_DIR"
|
|
|
|
if [ ! -f "$OPENCODE_CONFIG" ]; then
|
|
cat > "$OPENCODE_CONFIG" << 'CONFIG'
|
|
{
|
|
"$schema": "https://opencode.ai/config.json"
|
|
}
|
|
CONFIG
|
|
echo -e "${GREEN}[OK]${NC} OpenCode config created"
|
|
else
|
|
echo -e "${GREEN}[OK]${NC} OpenCode config already exists"
|
|
fi
|
|
|
|
# ── Cleanup ──────────────────────────────────
|
|
|
|
rm -f "$BUN_WRAPPER"
|
|
|
|
echo ""
|
|
echo -e "${GREEN}OpenCode installation complete.${NC}"
|
|
if [ -n "${OC_VER:-}" ]; then
|
|
echo " OpenCode: v$OC_VER"
|
|
fi
|
|
echo " Run: opencode"
|