chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:16:30 +08:00
commit a06359fc30
216 changed files with 32971 additions and 0 deletions
+404
View File
@@ -0,0 +1,404 @@
#!/usr/bin/env bash
# backup.sh — oa --backup / oa --restore implementation
# Sourced by oa.sh after lib.sh is loaded.
# ── Constants ──
BACKUP_DIR="$PROJECT_DIR/backup"
BACKUP_SCHEMA_VERSION=1
# ── Helpers ──
# Build an ISO-8601 timestamp matching OpenClaw's naming rule:
# colons replaced with dashes, e.g. 2026-03-14T00-00-00.000Z
_backup_timestamp() {
date -u +"%Y-%m-%dT%H-%M-%S.000Z"
}
# Return the archive-root name embedded inside the tarball.
# OpenClaw uses the bare filename (without .tar.gz) as archiveRoot.
_backup_archive_root() {
local basename="$1" # e.g. 2026-…-openclaw-backup
echo "$basename"
}
# Collect all paths that exist under a given data dir and return them
# as a bash array (by name). Skips missing paths silently.
# Usage: _collect_assets <data_dir> <array_name>
_collect_assets() {
local data_dir="$1"
local -n _arr="$2" # nameref — bash 4.3+
local candidates=(
"openclaw.json5"
"openclaw.json"
".env"
"secrets.json"
"credentials"
"identity"
"auth"
"sessions"
"workspace"
)
# Agents directory — dynamic enumeration
if [ -d "$data_dir/agents" ]; then
while IFS= read -r agent_path; do
local rel="${agent_path#"$data_dir/"}"
candidates+=("$rel")
done < <(find "$data_dir/agents" -mindepth 1 -maxdepth 2 -name "agent" 2>/dev/null)
fi
_arr=()
for rel in "${candidates[@]}"; do
local full="$data_dir/$rel"
if [ -e "$full" ]; then
_arr+=("$rel")
fi
done
}
# Detect which runtime owns a backup by inspecting its manifest.json.
# Echoes the platform name (e.g. "openclaw"), or "" on failure.
_detect_backup_platform() {
local archive="$1"
# Extract manifest.json from the tarball without unpacking everything
local manifest
manifest=$(gzip -dc "$archive" 2>/dev/null | tar -xf - --wildcards "*/manifest.json" -O 2>/dev/null | head -c 65536)
if [ -z "$manifest" ]; then
echo ""
return 1
fi
# Quick heuristic: look for known platform fingerprints in sourcePath values
if echo "$manifest" | grep -q '"\.openclaw"'; then
echo "openclaw"
return 0
fi
if echo "$manifest" | grep -q '\.openclaw'; then
echo "openclaw"
return 0
fi
echo ""
return 1
}
# Return the restore root directory for a given platform name.
_restore_root_for_platform() {
local platform="$1"
case "$platform" in
openclaw)
echo "$HOME/.openclaw"
;;
*)
# Future platforms: extend here
echo ""
;;
esac
}
# ── cmd_backup ──────────────────────────────────────────────────────────────
cmd_backup() {
if ! command -v gzip &>/dev/null; then
echo " Installing gzip..."
pkg install -y gzip 2>/dev/null || { echo -e "${RED}[FAIL]${NC} gzip not found and could not be installed"; exit 1; }
fi
local output_dir="${1:-}"
# Resolve output directory
if [ -z "$output_dir" ]; then
output_dir="$BACKUP_DIR"
fi
echo ""
echo -e "${BOLD}OpenClaw on Android — Backup${NC}"
echo -e "────────────────────────────────────────"
# Load platform config to get PLATFORM_DATA_DIR
local platform
platform=$(detect_platform 2>/dev/null) || platform=""
if [ -z "$platform" ]; then
echo -e "${RED}[FAIL]${NC} Could not detect installed platform."
exit 1
fi
load_platform_config "$platform" "$PROJECT_DIR" 2>/dev/null || {
echo -e "${RED}[FAIL]${NC} Could not load platform config for: $platform"
exit 1
}
local data_dir="$PLATFORM_DATA_DIR"
if [ ! -d "$data_dir" ]; then
echo -e "${RED}[FAIL]${NC} Platform data directory not found: $data_dir"
exit 1
fi
# Collect assets
local assets=()
_collect_assets "$data_dir" assets
if [ ${#assets[@]} -eq 0 ]; then
echo -e "${YELLOW}[WARN]${NC} No backup targets found in $data_dir"
exit 1
fi
echo -e " Platform: $platform"
echo -e " Source: $data_dir"
echo -e " Destination: $output_dir"
echo -e " Assets: ${#assets[@]} item(s)"
echo ""
# Create output directory
mkdir -p "$output_dir"
# Build filename — OpenClaw naming rule
local ts
ts=$(_backup_timestamp)
local basename="${ts}-openclaw-backup"
local archive_filename="${basename}.tar.gz"
local archive_path="$output_dir/$archive_filename"
local archive_root
archive_root=$(_backup_archive_root "$basename")
# Build manifest.json in a temp dir, then pack everything
local tmpdir
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/oa-backup.XXXXXX")
trap 'rm -rf "'"$tmpdir"'"' EXIT
local staging="$tmpdir/$archive_root"
local payload_dir="$staging/payload"
mkdir -p "$payload_dir"
# Copy each asset into payload/, preserving relative structure
echo -e "Collecting files…"
local manifest_assets_json=""
local sep=""
for rel in "${assets[@]}"; do
local src="$data_dir/$rel"
local dst="$payload_dir/$rel"
# Determine kind
local kind="state"
case "$rel" in
openclaw.json5|openclaw.json) kind="config" ;;
.env|secrets.json|credentials|identity|auth) kind="config" ;;
workspace*) kind="workspace" ;;
agents*) kind="workspace" ;;
sessions*) kind="state" ;;
esac
local archive_path_rel="$archive_root/payload/$rel"
if [ -d "$src" ]; then
mkdir -p "$dst"
cp -a "$src/." "$dst/"
else
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst"
fi
# Append to manifest assets JSON array
manifest_assets_json+="${sep}"
manifest_assets_json+=$(printf ' {\n "kind": "%s",\n "sourcePath": "%s",\n "archivePath": "%s"\n }' \
"$kind" "$src" "$archive_path_rel")
sep=$',\n'
done
# Generate manifest.json
local node_version runtime_version
node_version=$(node --version 2>/dev/null || echo "unknown")
runtime_version=$(openclaw --version 2>/dev/null | head -1 || echo "unknown")
cat > "$staging/manifest.json" <<MANIFEST_EOF
{
"schemaVersion": $BACKUP_SCHEMA_VERSION,
"createdAt": "$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")",
"archiveRoot": "$archive_root",
"runtimeVersion": "$runtime_version",
"platform": "linux",
"nodeVersion": "$node_version",
"assets": [
$manifest_assets_json
]
}
MANIFEST_EOF
# Pack the archive using tar + gzip pipe (Android safe)
# Piping through gzip explicitly ensures the shell resolves gzip via PATH,
# avoiding "gzip: Cannot exec" errors when tar can't find gzip internally.
echo -e "Packing archive…"
if ! tar -cf - -C "$tmpdir" "$archive_root" | gzip > "$archive_path"; then
echo -e "${RED}[FAIL]${NC} Failed to create archive: $archive_path"
exit 1
fi
echo -e "${GREEN}[OK]${NC} Archive created: $archive_path"
echo ""
# ── Integrity verification ──
echo -e "Verifying integrity…"
# Try openclaw backup verify first (preferred — full manifest check)
if command -v openclaw &>/dev/null && openclaw backup verify "$archive_path" &>/dev/null 2>&1; then
echo -e "${GREEN}[OK]${NC} Integrity check passed (openclaw backup verify)"
else
# Fallback: tar -tzf structural check
local file_count
file_count=$(gzip -dc "$archive_path" 2>/dev/null | tar -tf - 2>/dev/null | wc -l)
if [ "$file_count" -gt 0 ]; then
echo -e "${GREEN}[OK]${NC} Integrity check passed (tar structural, $file_count entries)"
else
echo -e "${RED}[FAIL]${NC} Integrity check failed — archive may be corrupt"
exit 1
fi
fi
echo ""
echo -e "${GREEN}Backup complete.${NC}"
echo -e " File: $archive_path"
echo -e " Size: $(du -sh "$archive_path" | cut -f1)"
echo ""
}
# ── cmd_restore ─────────────────────────────────────────────────────────────
cmd_restore() {
if ! command -v gzip &>/dev/null; then
echo " Installing gzip..."
pkg install -y gzip 2>/dev/null || { echo -e "${RED}[FAIL]${NC} gzip not found and could not be installed"; exit 1; }
fi
echo ""
echo -e "${BOLD}OpenClaw on Android — Restore${NC}"
echo -e "────────────────────────────────────────"
# Collect backup files
if [ ! -d "$BACKUP_DIR" ]; then
echo -e "${RED}[FAIL]${NC} Backup directory not found: $BACKUP_DIR"
echo -e " Run ${BOLD}oa --backup${NC} first."
exit 1
fi
local -a backups=()
while IFS= read -r f; do
backups+=("$f")
done < <(ls -t "$BACKUP_DIR"/*.tar.gz 2>/dev/null)
if [ ${#backups[@]} -eq 0 ]; then
echo -e "${RED}[FAIL]${NC} No backup files found in $BACKUP_DIR"
echo -e " Run ${BOLD}oa --backup${NC} first."
exit 1
fi
# Display numbered list
echo -e "Available backups:"
echo ""
local idx=1
for f in "${backups[@]}"; do
local fname size
fname=$(basename "$f")
size=$(du -sh "$f" 2>/dev/null | cut -f1)
printf " ${BOLD}[%d]${NC} %s ${YELLOW}(%s)${NC}\n" "$idx" "$fname" "$size"
idx=$((idx + 1))
done
echo ""
local choice
if (echo -n "" > /dev/tty) 2>/dev/null; then
read -rp "Select backup to restore [1-${#backups[@]}]: " choice < /dev/tty
else
read -rp "Select backup to restore [1-${#backups[@]}]: " choice
fi
# Validate input
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#backups[@]}" ]; then
echo -e "${RED}[FAIL]${NC} Invalid selection: $choice"
exit 1
fi
local selected="${backups[$((choice - 1))]}"
echo ""
echo -e " Selected: ${BOLD}$(basename "$selected")${NC}"
# Detect platform from manifest
echo -e " Detecting platform…"
local platform
platform=$(_detect_backup_platform "$selected")
if [ -z "$platform" ]; then
echo -e "${RED}[FAIL]${NC} Could not determine backup platform from manifest."
exit 1
fi
local restore_root
restore_root=$(_restore_root_for_platform "$platform")
if [ -z "$restore_root" ]; then
echo -e "${RED}[FAIL]${NC} Unsupported platform in backup: $platform"
exit 1
fi
echo -e " Platform: $platform"
echo -e " Restore to: $restore_root"
echo ""
# ── Warning ──
echo -e "${YELLOW}┌─────────────────────────────────────────────────┐${NC}"
echo -e "${YELLOW}│ WARNING: This will overwrite your current │${NC}"
echo -e "${YELLOW}│ configuration and data in: │${NC}"
echo -e "${YELLOW}│ │${NC}"
echo -e "${YELLOW}$restore_root${NC}"
echo -e "${YELLOW}│ │${NC}"
echo -e "${YELLOW}│ Existing files will be replaced. This cannot │${NC}"
echo -e "${YELLOW}│ be undone unless you have another backup. │${NC}"
echo -e "${YELLOW}└─────────────────────────────────────────────────┘${NC}"
echo ""
if ! ask_yn "Continue with restore?"; then
echo -e "Restore cancelled."
exit 0
fi
echo ""
echo -e "Restoring…"
# Extract archive: only the payload/ contents go to restore_root
# The archive structure is: <archiveRoot>/payload/<rel_path>
# We strip the first two components (<archiveRoot>/payload) and restore to restore_root
# Get archiveRoot from manifest
local archive_root
archive_root=$(gzip -dc "$selected" 2>/dev/null | tar -xf - --wildcards "*/manifest.json" -O 2>/dev/null \
| grep '"archiveRoot"' \
| sed 's/.*"archiveRoot"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
if [ -z "$archive_root" ]; then
echo -e "${RED}[FAIL]${NC} Could not read archiveRoot from manifest."
exit 1
fi
mkdir -p "$restore_root"
# Extract payload files, stripping <archiveRoot>/payload/ prefix
if ! gzip -dc "$selected" 2>/dev/null | tar -xf - \
--strip-components=2 \
--exclude="${archive_root}/manifest.json" \
-C "$restore_root" \
"${archive_root}/payload/" 2>/dev/null; then
echo -e "${RED}[FAIL]${NC} Extraction failed."
exit 1
fi
echo -e "${GREEN}[OK]${NC} Restore complete."
echo ""
echo -e " Restored to: $restore_root"
echo ""
echo -e "${YELLOW}[NOTE]${NC} Restart the OpenClaw gateway for changes to take effect."
echo ""
}
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# build-sharp.sh - Enable sharp image processing on Android (Termux)
#
# Strategy:
# 1. Check if sharp already works → skip
# 2. Install WebAssembly fallback (@img/sharp-wasm32)
# Native sharp binaries are built for glibc Linux. Android's Bionic libc
# cannot dlopen glibc-linked .node addons, so the prebuilt linux-arm64
# binding never loads. The WASM build uses Emscripten and runs entirely
# in V8 — zero native dependencies.
# 3. If WASM fails → attempt native rebuild as last resort
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
echo "=== Building sharp (image processing) ==="
echo ""
# Ensure required environment variables are set (for standalone use)
export TMPDIR="${TMPDIR:-$PREFIX/tmp}"
export TMP="$TMPDIR"
export TEMP="$TMPDIR"
export CONTAINER="${CONTAINER:-1}"
# Locate openclaw install directory
OPENCLAW_DIR="$(npm root -g)/openclaw"
if [ ! -d "$OPENCLAW_DIR" ]; then
echo -e "${RED}[FAIL]${NC} OpenClaw directory not found: $OPENCLAW_DIR"
exit 0
fi
# Skip rebuild if sharp is already working (e.g. WASM installed on prior run)
if [ -d "$OPENCLAW_DIR/node_modules/sharp" ]; then
if node -e "require('$OPENCLAW_DIR/node_modules/sharp')" 2>/dev/null; then
echo -e "${GREEN}[OK]${NC} sharp is already working — skipping rebuild"
exit 0
fi
fi
# ── Strategy 1: WebAssembly fallback (recommended for Android) ──────────
# sharp's JS loader tries these paths in order:
# 1. ../src/build/Release/sharp-{platform}.node (source build)
# 2. ../src/build/Release/sharp-wasm32.node (source build)
# 3. @img/sharp-{platform}/sharp.node (prebuilt native)
# 4. @img/sharp-wasm32/sharp.node (prebuilt WASM) ← this
# By installing @img/sharp-wasm32, path 4 catches the fallback automatically.
echo "Installing sharp WebAssembly runtime..."
if (cd "$OPENCLAW_DIR" && npm install @img/sharp-wasm32 --force --no-audit --no-fund 2>&1 | tail -3); then
if node -e "require('$OPENCLAW_DIR/node_modules/sharp')" 2>/dev/null; then
echo ""
echo -e "${GREEN}[OK]${NC} sharp enabled via WebAssembly — image processing ready"
exit 0
else
echo -e "${YELLOW}[WARN]${NC} WASM package installed but sharp still not loading"
fi
else
echo -e "${YELLOW}[WARN]${NC} Failed to install WASM package"
fi
# ── Strategy 2: Native rebuild (last resort) ────────────────────────────
echo ""
echo "Attempting native rebuild as fallback..."
# Install required packages
echo "Installing build dependencies..."
if ! pkg install -y libvips binutils; then
echo -e "${YELLOW}[WARN]${NC} Failed to install build dependencies"
echo " Image processing will not be available, but OpenClaw will work normally."
exit 0
fi
echo -e "${GREEN}[OK]${NC} libvips and binutils installed"
# Create ar symlink if missing (Termux provides llvm-ar but not ar)
if [ ! -e "$PREFIX/bin/ar" ] && [ -x "$PREFIX/bin/llvm-ar" ]; then
ln -s "$PREFIX/bin/llvm-ar" "$PREFIX/bin/ar"
echo -e "${GREEN}[OK]${NC} Created ar → llvm-ar symlink"
fi
# Install node-gyp globally
echo "Installing node-gyp..."
if ! npm install -g node-gyp; then
echo -e "${YELLOW}[WARN]${NC} Failed to install node-gyp"
echo " Image processing will not be available, but OpenClaw will work normally."
exit 0
fi
echo -e "${GREEN}[OK]${NC} node-gyp installed"
# Set build environment variables
# On glibc architecture, these are handled by glibc's standard headers.
# On Bionic (legacy), we need explicit compatibility flags.
if [ ! -f "$HOME/.openclaw-android/.glibc-arch" ]; then
export CFLAGS="-Wno-error=implicit-function-declaration"
export CXXFLAGS="-include $HOME/.openclaw-android/patches/termux-compat.h"
export GYP_DEFINES="OS=linux android_ndk_path=$PREFIX"
fi
export CPATH="$PREFIX/include/glib-2.0:$PREFIX/lib/glib-2.0/include"
echo "Rebuilding sharp in $OPENCLAW_DIR..."
echo "This may take several minutes..."
echo ""
if (cd "$OPENCLAW_DIR" && npm rebuild sharp); then
echo ""
echo -e "${GREEN}[OK]${NC} sharp built successfully — image processing enabled"
else
echo ""
echo -e "${YELLOW}[WARN]${NC} sharp could not be enabled (non-critical)"
echo " Image processing will not be available, but OpenClaw will work normally."
echo " You can retry later: bash ~/.openclaw-android/scripts/build-sharp.sh"
fi
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib.sh"
ERRORS=0
echo "=== OpenClaw on Android - Environment Check ==="
echo ""
if [ -z "${PREFIX:-}" ]; then
echo -e "${RED}[FAIL]${NC} Not running in Termux (\$PREFIX not set)"
echo " This script is designed for Termux on Android."
exit 1
else
echo -e "${GREEN}[OK]${NC} Termux detected (PREFIX=$PREFIX)"
fi
ARCH=$(uname -m)
echo -n " Architecture: $ARCH"
if [ "$ARCH" = "aarch64" ]; then
echo -e " ${GREEN}(recommended)${NC}"
elif [ "$ARCH" = "armv7l" ] || [ "$ARCH" = "arm" ]; then
echo -e " ${YELLOW}(supported, but aarch64 recommended)${NC}"
elif [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "i686" ]; then
echo -e " ${YELLOW}(emulator detected)${NC}"
else
echo -e " ${YELLOW}(unknown, may not work)${NC}"
fi
AVAILABLE_MB=$(df "$PREFIX" 2>/dev/null | awk 'NR==2 {print int($4/1024)}')
if [ -n "$AVAILABLE_MB" ] && [ "$AVAILABLE_MB" -lt 1000 ]; then
echo -e "${RED}[FAIL]${NC} Insufficient disk space: ${AVAILABLE_MB}MB available (need 1000MB+)"
ERRORS=$((ERRORS + 1))
else
echo -e "${GREEN}[OK]${NC} Disk space: ${AVAILABLE_MB:-unknown}MB available"
fi
if command -v node &>/dev/null; then
NODE_VER=$(node -v 2>/dev/null || echo "unknown")
echo -e "${GREEN}[OK]${NC} Node.js found: $NODE_VER"
NODE_MAJOR="${NODE_VER%%.*}"
NODE_MAJOR="${NODE_MAJOR#v}"
if [ "$NODE_MAJOR" -lt 22 ] 2>/dev/null; then
echo -e "${YELLOW}[WARN]${NC} Node.js >= 22 required. Will be upgraded during install."
fi
else
echo -e "${YELLOW}[INFO]${NC} Node.js not found. Will be installed via glibc environment."
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
echo ""
if [ "$ERRORS" -gt 0 ]; then
echo -e "${RED}Environment check failed with $ERRORS error(s).${NC}"
exit 1
else
echo -e "${GREEN}Environment check passed.${NC}"
fi
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# install-build-tools.sh - Install build tools for native module compilation (L2 conditional)
# Extracted from install-deps.sh — build tools only.
# Called by orchestrator when config.env PLATFORM_NEEDS_BUILD_TOOLS=true.
#
# Installs: python, make, cmake, clang, binutils
# These are required for node-gyp (native C/C++ addon compilation).
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "=== Installing Build Tools ==="
echo ""
PACKAGES=(
python
make
cmake
clang
binutils
)
echo "Installing packages: ${PACKAGES[*]}"
echo " (This may take a few minutes depending on network speed)"
pkg install -y "${PACKAGES[@]}"
# Create ar symlink if missing (Termux provides llvm-ar but not ar)
if [ ! -e "$PREFIX/bin/ar" ] && [ -x "$PREFIX/bin/llvm-ar" ]; then
ln -s "$PREFIX/bin/llvm-ar" "$PREFIX/bin/ar"
echo -e "${GREEN}[OK]${NC} Created ar → llvm-ar symlink"
fi
echo ""
echo -e "${GREEN}Build tools installed.${NC}"
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# install-chromium.sh - Install Chromium for OpenClaw browser automation
# Usage: bash install-chromium.sh [install|update]
#
# What it does:
# 1. Install x11-repo (Termux X11 packages repository)
# 2. Install chromium package
# 3. Configure OpenClaw browser settings in openclaw.json
# 4. Verify installation
#
# Browser automation allows OpenClaw to control a headless Chromium browser
# for web scraping, screenshots, and automated browsing tasks.
#
# This script is WARN-level: failure does not abort the parent installer.
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
MODE="${1:-install}"
# ── Helper ────────────────────────────────────
fail_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
exit 0
}
# ── Detect Chromium binary path ───────────────
detect_chromium_bin() {
for bin in "$PREFIX/bin/chromium-browser" "$PREFIX/bin/chromium"; do
if [ -x "$bin" ]; then
echo "$bin"
return 0
fi
done
return 1
}
# ── Pre-checks ────────────────────────────────
if [ -z "${PREFIX:-}" ]; then
fail_warn "Not running in Termux (\$PREFIX not set)"
fi
# ── Check current installation ────────────────
SKIP_PKG_INSTALL=false
if CHROMIUM_BIN=$(detect_chromium_bin); then
if [ "$MODE" = "install" ]; then
echo -e "${GREEN}[SKIP]${NC} Chromium already installed ($CHROMIUM_BIN)"
SKIP_PKG_INSTALL=true
fi
fi
# ── Step 1: Install x11-repo + Chromium ───────
if [ "$SKIP_PKG_INSTALL" = false ]; then
echo "Installing x11-repo (Termux X11 packages)..."
if ! pkg install -y x11-repo; then
fail_warn "Failed to install x11-repo"
fi
echo -e "${GREEN}[OK]${NC} x11-repo installed"
echo "Installing Chromium..."
echo " (This is a large package (~400MB) — may take several minutes)"
if ! pkg install -y chromium; then
fail_warn "Failed to install Chromium"
fi
echo -e "${GREEN}[OK]${NC} Chromium installed"
fi
# ── Step 2: Detect binary path ────────────────
if ! CHROMIUM_BIN=$(detect_chromium_bin); then
fail_warn "Chromium binary not found after installation"
fi
# ── Step 3: Configure OpenClaw browser settings
echo "Configuring OpenClaw browser settings..."
if command -v node &>/dev/null; then
export CHROMIUM_BIN
if node << 'NODESCRIPT'
const fs = require('fs');
const path = require('path');
const configDir = path.join(process.env.HOME, '.openclaw');
const configPath = path.join(configDir, 'openclaw.json');
let config = {};
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch {
// File doesn't exist or invalid — start fresh
}
if (!config.browser) config.browser = {};
config.browser.executablePath = process.env.CHROMIUM_BIN;
if (config.browser.headless === undefined) config.browser.headless = true;
if (config.browser.noSandbox === undefined) config.browser.noSandbox = true;
fs.mkdirSync(configDir, { recursive: true });
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
console.log(' Written to ' + configPath);
NODESCRIPT
then
echo -e "${GREEN}[OK]${NC} openclaw.json browser settings configured"
else
echo -e "${YELLOW}[WARN]${NC} Could not update openclaw.json automatically"
echo " Add this to ~/.openclaw/openclaw.json manually:"
echo " \"browser\": {\"executablePath\": \"$CHROMIUM_BIN\", \"headless\": true, \"noSandbox\": true}"
fi
else
echo -e "${YELLOW}[INFO]${NC} Node.js not available — manual browser configuration needed"
echo " After running 'openclaw onboard', add to ~/.openclaw/openclaw.json:"
echo " \"browser\": {\"executablePath\": \"$CHROMIUM_BIN\", \"headless\": true, \"noSandbox\": true}"
fi
# ── Step 4: Verify ────────────────────────────
echo ""
if [ -x "$CHROMIUM_BIN" ]; then
CHROMIUM_VER=$("$CHROMIUM_BIN" --version 2>/dev/null || echo "unknown version")
echo -e "${GREEN}[OK]${NC} $CHROMIUM_VER"
echo " Binary: $CHROMIUM_BIN"
echo ""
echo -e "${YELLOW}[NOTE]${NC} Chromium uses ~300-500MB RAM at runtime."
echo " Devices with less than 4GB RAM may experience slowdowns."
else
fail_warn "Chromium verification failed — binary not executable"
fi
# ── Step 5: Ensure image processing works ────
#
# Browser screenshots require sharp for image optimization before sending
# to Discord/Slack. Run build-sharp.sh to enable it (idempotent — skips
# if sharp is already working).
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ -f "$SCRIPT_DIR/build-sharp.sh" ]; then
echo ""
bash "$SCRIPT_DIR/build-sharp.sh" || true
elif [ -f "$HOME/.openclaw-android/scripts/build-sharp.sh" ]; then
echo ""
bash "$HOME/.openclaw-android/scripts/build-sharp.sh" || true
fi
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env bash
# install-code-server.sh - Install or update code-server (browser IDE) on Termux
# Usage: bash install-code-server.sh [install|update]
#
# Workarounds applied:
# 1. Replace bundled glibc node with Termux node
# 2. Patch argon2 native module with JS stub (--auth none makes it unused)
# 3. Ignore tar hard link errors (Android restriction) and recover .node files
#
# This script is WARN-level: failure does not abort the parent installer.
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
MODE="${1:-install}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_DIR="$HOME/.local/lib"
BIN_DIR="$HOME/.local/bin"
# ── Helper ────────────────────────────────────
fail_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
exit 0
}
# ── Pre-checks ────────────────────────────────
if [ -z "${PREFIX:-}" ]; then
fail_warn "Not running in Termux (\$PREFIX not set)"
fi
if ! command -v node &>/dev/null; then
fail_warn "node not found — code-server requires Node.js"
fi
if ! command -v curl &>/dev/null; then
fail_warn "curl not found — cannot download code-server"
fi
# ── Check current installation ────────────────
CURRENT_VERSION=""
if [ -x "$BIN_DIR/code-server" ]; then
# code-server --version may include i18next ad text before the version line.
# Use regex to extract X.Y.Z directly, ignoring any noise lines.
CURRENT_VERSION=$("$BIN_DIR/code-server" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
fi
# ── Determine target version ──────────────────
if [ "$MODE" = "install" ] && [ -n "$CURRENT_VERSION" ]; then
echo -e "${GREEN}[SKIP]${NC} code-server already installed ($CURRENT_VERSION)"
exit 0
fi
# Fetch latest version from GitHub API
echo "Checking latest code-server version..."
LATEST_VERSION=$(curl -sfL --max-time 10 \
"https://api.github.com/repos/coder/code-server/releases/latest" \
| grep '"tag_name"' | head -1 | sed 's/.*"v\([^"]*\)".*/\1/') || true
if [ -z "$LATEST_VERSION" ]; then
fail_warn "Failed to fetch latest code-server version from GitHub"
fi
echo " Latest: v$LATEST_VERSION"
if [ "$MODE" = "update" ] && [ -n "$CURRENT_VERSION" ]; then
if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
echo -e "${GREEN}[SKIP]${NC} code-server $CURRENT_VERSION is already the latest"
exit 0
fi
echo " Current: v$CURRENT_VERSION → updating to v$LATEST_VERSION"
fi
VERSION="$LATEST_VERSION"
# ── Download ──────────────────────────────────
TARBALL="code-server-${VERSION}-linux-arm64.tar.gz"
DOWNLOAD_URL="https://github.com/coder/code-server/releases/download/v${VERSION}/${TARBALL}"
TMP_DIR=$(mktemp -d "$PREFIX/tmp/code-server-install.XXXXXX") || fail_warn "Failed to create temp directory"
trap 'rm -rf "$TMP_DIR"' EXIT
echo "Downloading code-server v${VERSION}..."
echo " (File size ~121MB — this may take several minutes depending on network speed)"
if ! curl -fL --max-time 300 "$DOWNLOAD_URL" -o "$TMP_DIR/$TARBALL"; then
fail_warn "Failed to download code-server v${VERSION}"
fi
echo -e "${GREEN}[OK]${NC} Downloaded $TARBALL"
# ── Extract (ignore hard link errors) ─────────
echo "Extracting code-server... (this may take a moment)"
# Android's filesystem does not support hard links, so tar will report errors
# for hardlinked .node files. We extract what we can and recover them below.
tar -xzf "$TMP_DIR/$TARBALL" -C "$TMP_DIR" 2>/dev/null || true
EXTRACTED_DIR="$TMP_DIR/code-server-${VERSION}-linux-arm64"
if [ ! -d "$EXTRACTED_DIR" ]; then
fail_warn "Extraction failed — directory not found"
fi
# ── Recover hard-linked .node files ───────────
# The obj.target/ directories contain the original .node files that tar
# couldn't hard-link into Release/. Copy them manually.
find "$EXTRACTED_DIR" -path "*/obj.target/*.node" -type f 2>/dev/null | while read -r OBJ_FILE; do
# obj.target/foo.node → Release/foo.node
RELEASE_DIR="$(dirname "$(dirname "$OBJ_FILE")")/Release"
BASENAME="$(basename "$OBJ_FILE")"
if [ -d "$RELEASE_DIR" ] && [ ! -f "$RELEASE_DIR/$BASENAME" ]; then
cp "$OBJ_FILE" "$RELEASE_DIR/$BASENAME"
fi
done
echo -e "${GREEN}[OK]${NC} Extracted and recovered .node files"
# ── Install to ~/.local/lib ───────────────────
mkdir -p "$INSTALL_DIR" "$BIN_DIR"
# Remove previous code-server versions
rm -rf "$INSTALL_DIR"/code-server-*
# Move extracted directory to install location
mv "$EXTRACTED_DIR" "$INSTALL_DIR/code-server-${VERSION}"
echo -e "${GREEN}[OK]${NC} Installed to $INSTALL_DIR/code-server-${VERSION}"
CS_DIR="$INSTALL_DIR/code-server-${VERSION}"
# ── Replace bundled node with Termux node ─────
# The standalone release bundles a glibc-linked node binary that cannot
# run on Termux (Bionic libc). Swap it with the system node.
if [ -f "$CS_DIR/lib/node" ] || [ -L "$CS_DIR/lib/node" ]; then
rm -f "$CS_DIR/lib/node"
fi
ln -s "$PREFIX/bin/node" "$CS_DIR/lib/node"
echo -e "${GREEN}[OK]${NC} Replaced bundled node → Termux node"
# ── Patch argon2 native module ────────────────
# argon2 ships a .node binary compiled against glibc. Since we run
# code-server with --auth none, argon2 is never called. Replace the
# module entry point with a JS stub.
ARGON2_STUB=""
# Check multiple possible locations for the stub
if [ -f "$SCRIPT_DIR/../patches/argon2-stub.js" ]; then
ARGON2_STUB="$SCRIPT_DIR/../patches/argon2-stub.js"
elif [ -f "$HOME/.openclaw-android/patches/argon2-stub.js" ]; then
ARGON2_STUB="$HOME/.openclaw-android/patches/argon2-stub.js"
fi
if [ -n "$ARGON2_STUB" ]; then
# Find argon2 module entry point in code-server
# Entry point varies by version: argon2.cjs (v4.109+), argon2.js, or index.js
ARGON2_INDEX=""
for PATTERN in "*/argon2/argon2.cjs" "*/argon2/argon2.js" "*/node_modules/argon2/index.js"; do
ARGON2_INDEX=$(find "$CS_DIR" -path "$PATTERN" -type f 2>/dev/null | head -1 || true)
[ -n "$ARGON2_INDEX" ] && break
done
if [ -n "$ARGON2_INDEX" ]; then
cp "$ARGON2_STUB" "$ARGON2_INDEX"
echo -e "${GREEN}[OK]${NC} Patched argon2 module with JS stub ($(basename "$ARGON2_INDEX"))"
else
echo -e "${YELLOW}[WARN]${NC} argon2 module not found in code-server (may not be needed)"
fi
else
echo -e "${YELLOW}[WARN]${NC} argon2-stub.js not found — skipping argon2 patch"
fi
# ── Create symlink ────────────────────────────
rm -f "$BIN_DIR/code-server"
ln -s "$CS_DIR/bin/code-server" "$BIN_DIR/code-server"
echo -e "${GREEN}[OK]${NC} Symlinked $BIN_DIR/code-server"
# ── Verify ────────────────────────────────────
# Add ~/.local/bin to PATH for this session so we can verify with just "code-server"
export PATH="$BIN_DIR:$PATH"
echo ""
if code-server --version &>/dev/null; then
INSTALLED_VER=$(code-server --version 2>/dev/null | head -1 || true)
echo -e "${GREEN}[OK]${NC} code-server ${INSTALLED_VER:-unknown} installed successfully"
else
echo -e "${YELLOW}[WARN]${NC} code-server installed but --version check failed"
echo " This may work once ~/.local/bin is on PATH (restart shell or: source ~/.bashrc)"
fi
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env bash
# install-glibc.sh - Install glibc-runner (L2 conditional)
# Extracted from install-glibc-env.sh — glibc runtime only, no Node.js.
# Called by orchestrator when config.env PLATFORM_NEEDS_GLIBC=true.
#
# What it does:
# 1. Install pacman package
# 2. Initialize pacman and install glibc-runner
# 3. Verify glibc dynamic linker
# 4. Create marker file
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
OPENCLAW_DIR="$HOME/.openclaw-android"
GLIBC_LDSO="$PREFIX/glibc/lib/ld-linux-aarch64.so.1"
PACMAN_CONF="$PREFIX/etc/pacman.conf"
echo "=== Installing glibc Runtime ==="
echo ""
# ── Pre-checks ───────────────────────────────
if [ -z "${PREFIX:-}" ]; then
echo -e "${RED}[FAIL]${NC} Not running in Termux (\$PREFIX not set)"
exit 1
fi
ARCH=$(uname -m)
if [ "$ARCH" != "aarch64" ]; then
echo -e "${RED}[FAIL]${NC} glibc environment requires aarch64 (got: $ARCH)"
exit 1
fi
# ── Install supplementary glibc libraries (always runs) ──
# glibc-runner provides core libraries but not all libraries that
# third-party binaries may need (e.g., libcap.so.2 for codex-acp).
# This runs on both fresh install and update to ensure libraries are current.
GLIBC_LIB_DIR="$PREFIX/glibc/lib"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GLIBC_LIBS_SRC="$SCRIPT_DIR/../patches/glibc-libs"
if [ -d "$GLIBC_LIB_DIR" ] && [ -d "$GLIBC_LIBS_SRC" ]; then
for lib in "$GLIBC_LIBS_SRC"/*.so.*; do
[ -f "$lib" ] || continue
filename=$(basename "$lib")
soname=$(echo "$filename" | sed -E 's/^(lib[^.]+\.so\.[0-9]+)\..*/\1/')
if [ ! -f "$GLIBC_LIB_DIR/$filename" ]; then
cp "$lib" "$GLIBC_LIB_DIR/$filename"
[ "$soname" != "$filename" ] && ln -sf "$filename" "$GLIBC_LIB_DIR/$soname"
echo -e "${GREEN}[OK]${NC} Installed $soname"
fi
done
fi
# ── Ensure glibc /etc/hosts exists (always runs) ──
# glibc's getaddrinfo reads $PREFIX/glibc/etc/hosts for localhost resolution.
# Neither glibc nor glibc-runner packages include this file; it comes from
# resolv-conf (via openssl-glibc) which may not be installed.
# Without it, dns.lookup('localhost') can return 0.0.0.0 → gateway bind failure.
GLIBC_ETC="$PREFIX/glibc/etc"
if [ -d "$GLIBC_ETC" ] && [ ! -f "$GLIBC_ETC/hosts" ]; then
cat > "$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
# Check if already installed
if [ -f "$OPENCLAW_DIR/.glibc-arch" ] && [ -x "$GLIBC_LDSO" ]; then
echo -e "${GREEN}[SKIP]${NC} glibc-runner already installed"
exit 0
fi
# ── Step 1: Install pacman ────────────────────
echo "Installing pacman..."
if ! pkg install -y pacman; then
echo -e "${RED}[FAIL]${NC} Failed to install pacman"
exit 1
fi
echo -e "${GREEN}[OK]${NC} pacman installed"
# ── Step 2: Initialize pacman ─────────────────
echo ""
echo "Initializing pacman..."
echo " (This may take a few minutes for GPG key generation)"
# SigLevel workaround: Some devices have a GPGME crypto engine bug
# that prevents signature verification. Temporarily set SigLevel = Never.
SIGLEVEL_PATCHED=false
if [ -f "$PACMAN_CONF" ]; then
if ! grep -q "^SigLevel = Never" "$PACMAN_CONF"; then
cp "$PACMAN_CONF" "${PACMAN_CONF}.bak"
sed -i 's/^SigLevel\s*=.*/SigLevel = Never/' "$PACMAN_CONF"
SIGLEVEL_PATCHED=true
echo -e "${YELLOW}[INFO]${NC} Applied SigLevel = Never workaround (GPGME bug)"
fi
fi
# Initialize pacman keyring (may hang on low-entropy devices)
pacman-key --init 2>/dev/null || true
pacman-key --populate 2>/dev/null || true
# ── Step 3: Install glibc-runner ──────────────
echo ""
echo "Installing glibc-runner..."
# --assume-installed: these packages are provided by Termux's apt but pacman
# doesn't know about them, causing dependency resolution failures
if pacman -Sy glibc-runner --noconfirm --assume-installed bash,patchelf,resolv-conf 2>&1; then
echo -e "${GREEN}[OK]${NC} glibc-runner installed"
else
echo -e "${RED}[FAIL]${NC} Failed to install glibc-runner"
if [ "$SIGLEVEL_PATCHED" = true ] && [ -f "${PACMAN_CONF}.bak" ]; then
mv "${PACMAN_CONF}.bak" "$PACMAN_CONF"
fi
exit 1
fi
# Restore SigLevel after successful install
if [ "$SIGLEVEL_PATCHED" = true ] && [ -f "${PACMAN_CONF}.bak" ]; then
mv "${PACMAN_CONF}.bak" "$PACMAN_CONF"
echo -e "${GREEN}[OK]${NC} Restored pacman SigLevel"
fi
# ── Verify ────────────────────────────────────
if [ ! -x "$GLIBC_LDSO" ]; then
echo -e "${RED}[FAIL]${NC} glibc dynamic linker not found at $GLIBC_LDSO"
exit 1
fi
echo -e "${GREEN}[OK]${NC} glibc dynamic linker available"
if command -v grun &>/dev/null; then
echo -e "${GREEN}[OK]${NC} grun command available"
else
echo -e "${YELLOW}[WARN]${NC} grun command not found (will use ld.so directly)"
fi
# ── Create marker file ────────────────────────
mkdir -p "$OPENCLAW_DIR"
touch "$OPENCLAW_DIR/.glibc-arch"
echo -e "${GREEN}[OK]${NC} glibc architecture marker created"
echo ""
echo -e "${GREEN}glibc runtime installed successfully.${NC}"
echo " ld.so: $GLIBC_LDSO"
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# install-infra-deps.sh - Install core infrastructure packages (L1)
# Extracted from install-deps.sh — infrastructure only.
# Always runs regardless of platform selection.
#
# Installs: git (+ pkg update/upgrade)
set -euo pipefail
GREEN='\033[0;32m'
NC='\033[0m'
echo "=== Installing Infrastructure Dependencies ==="
echo ""
# Update and upgrade package repos
echo "Updating package repositories..."
echo " (This may take a minute depending on mirror speed)"
pkg update -y
pkg upgrade -y
# Install core infrastructure packages
echo "Installing git..."
pkg install -y git
echo ""
echo -e "${GREEN}Infrastructure dependencies installed.${NC}"
+352
View File
@@ -0,0 +1,352 @@
#!/usr/bin/env bash
# install-nodejs.sh - Install Node.js linux-arm64 with grun wrapper (L2 conditional)
# Extracted from install-glibc-env.sh — Node.js only, assumes glibc already installed.
# Called by orchestrator when config.env PLATFORM_NEEDS_NODEJS=true.
#
# What it does:
# 1. Download Node.js linux-arm64 LTS
# 2. Create grun-style wrapper scripts (ld.so direct execution)
# 3. Configure npm
# 4. Verify everything works
#
# patchelf is NOT used — Android seccomp causes SIGSEGV on patchelf'd binaries.
# All glibc binaries are executed via: exec ld.so binary "$@"
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
OPENCLAW_DIR="$HOME/.openclaw-android"
NODE_DIR="$OPENCLAW_DIR/node"
BIN_DIR="$OPENCLAW_DIR/bin"
GLIBC_LDSO="$PREFIX/glibc/lib/ld-linux-aarch64.so.1"
# Node.js LTS version to install
NODE_VERSION="22.22.0"
NODE_TARBALL="node-v${NODE_VERSION}-linux-arm64.tar.xz"
NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/${NODE_TARBALL}"
echo "=== Installing Node.js (glibc) ==="
echo ""
# ── Pre-checks ───────────────────────────────
if [ -z "${PREFIX:-}" ]; then
echo -e "${RED}[FAIL]${NC} Not running in Termux (\$PREFIX not set)"
exit 1
fi
if [ ! -x "$GLIBC_LDSO" ]; then
echo -e "${RED}[FAIL]${NC} glibc dynamic linker not found — run install-glibc.sh first"
exit 1
fi
# Check if already installed (check BIN_DIR wrapper first, fall back to NODE_DIR)
_NODE_CMD=""
if [ -x "$BIN_DIR/node" ]; then
_NODE_CMD="$BIN_DIR/node"
elif [ -x "$NODE_DIR/bin/node" ]; then
_NODE_CMD="$NODE_DIR/bin/node"
fi
if [ -n "$_NODE_CMD" ]; then
if "$_NODE_CMD" --version &>/dev/null; then
INSTALLED_VER=$("$_NODE_CMD" --version 2>/dev/null | sed 's/^v//')
if [ "$INSTALLED_VER" = "$NODE_VERSION" ]; then
echo -e "${GREEN}[SKIP]${NC} Node.js already installed (v${INSTALLED_VER})"
# Repair wrappers — ensure they exist in BIN_DIR (npm-safe location)
mkdir -p "$BIN_DIR"
_any_fixed=false
# Ensure node wrapper exists in BIN_DIR (may be missing for pre-v1.0.16 installs)
if [ ! -x "$BIN_DIR/node" ]; then
# Ensure node.real exists
if [ -f "$NODE_DIR/bin/node" ] && [ ! -L "$NODE_DIR/bin/node" ] && file "$NODE_DIR/bin/node" 2>/dev/null | grep -q ELF; then
mv "$NODE_DIR/bin/node" "$NODE_DIR/bin/node.real"
fi
if [ -f "$NODE_DIR/bin/node.real" ]; then
cat > "$BIN_DIR/node" << NODEWRAP
#!${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" "\$@"
NODEWRAP
chmod +x "$BIN_DIR/node"
_any_fixed=true
fi
fi
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
sed -i "s|__PREFIX__|$PREFIX|g; s|__BIN_DIR__|$BIN_DIR|g; s|__NODE_DIR__|$NODE_DIR|g" "$BIN_DIR/npm"
chmod +x "$BIN_DIR/npm"
_any_fixed=true
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
sed -i "s|__PREFIX__|$PREFIX|g; s|__BIN_DIR__|$BIN_DIR|g; s|__NODE_DIR__|$NODE_DIR|g" "$BIN_DIR/npx"
chmod +x "$BIN_DIR/npx"
_any_fixed=true
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"
_any_fixed=true
fi
if [ "$_any_fixed" = true ]; then
echo -e "${YELLOW}[FIX]${NC} wrappers repaired in $BIN_DIR"
fi
exit 0
fi
LOWEST=$(printf '%s\n%s\n' "$INSTALLED_VER" "$NODE_VERSION" | sort -V | head -1)
if [ "$LOWEST" = "$INSTALLED_VER" ] && [ "$INSTALLED_VER" != "$NODE_VERSION" ]; then
echo -e "${YELLOW}[INFO]${NC} Node.js v${INSTALLED_VER} -> v${NODE_VERSION} (upgrading)"
else
echo -e "${GREEN}[SKIP]${NC} Node.js v${INSTALLED_VER} is newer than target v${NODE_VERSION}"
exit 0
fi
else
echo -e "${YELLOW}[INFO]${NC} Node.js exists but broken — reinstalling"
fi
fi
# ── Step 1: Download Node.js linux-arm64 ──────
echo "Downloading Node.js v${NODE_VERSION} (linux-arm64)..."
echo " (File size ~25MB — may take a few minutes depending on network speed)"
mkdir -p "$NODE_DIR"
TMP_DIR=$(mktemp -d "$PREFIX/tmp/node-install.XXXXXX") || {
echo -e "${RED}[FAIL]${NC} Failed to create temp directory"
exit 1
}
trap 'rm -rf "$TMP_DIR"' EXIT
if ! curl -fL --max-time 300 "$NODE_URL" -o "$TMP_DIR/$NODE_TARBALL"; then
echo -e "${RED}[FAIL]${NC} Failed to download Node.js v${NODE_VERSION}"
exit 1
fi
echo -e "${GREEN}[OK]${NC} Downloaded $NODE_TARBALL"
# Extract
echo "Extracting Node.js... (this may take a moment)"
if ! tar -xJf "$TMP_DIR/$NODE_TARBALL" -C "$NODE_DIR" --strip-components=1; then
echo -e "${RED}[FAIL]${NC} Failed to extract Node.js"
exit 1
fi
echo -e "${GREEN}[OK]${NC} Extracted to $NODE_DIR"
# ── Step 2: Create wrapper scripts ────────────
#
# Wrappers are placed in BIN_DIR (~/.openclaw-android/bin/), separate from
# NODE_DIR/bin/ which npm manages. This prevents npm from overwriting our
# wrappers when users run 'npm install -g npm' or similar commands.
echo ""
echo "Creating wrapper scripts (grun-style, no patchelf)..."
mkdir -p "$BIN_DIR"
# Move original node binary to 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
# Create node wrapper script in BIN_DIR
# This uses grun-style execution: ld.so directly loads the binary
# LD_PRELOAD must be unset to prevent Bionic libtermux-exec.so from
# being loaded into the glibc process (causes version mismatch crash)
# glibc-compat.js is auto-loaded to fix Android kernel quirks (os.cpus() returns 0,
# os.networkInterfaces() throws EACCES) that affect native module builds and runtime.
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"
echo -e "${GREEN}[OK]${NC} node wrapper created ($BIN_DIR/node)"
# ── Step 2.5: Create npm/npx wrapper scripts in BIN_DIR ──
#
# npm/npx wrappers go in BIN_DIR (not NODE_DIR/bin/) so npm can't overwrite them.
echo "Creating npm/npx wrapper scripts..."
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=$?
# Re-patch openclaw CLI wrapper after global install/update
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
sed -i "s|__PREFIX__|$PREFIX|g; s|__BIN_DIR__|$BIN_DIR|g; s|__NODE_DIR__|$NODE_DIR|g" "$BIN_DIR/npm"
chmod +x "$BIN_DIR/npm"
echo -e "${GREEN}[OK]${NC} npm wrapper created ($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
sed -i "s|__PREFIX__|$PREFIX|g; s|__BIN_DIR__|$BIN_DIR|g; s|__NODE_DIR__|$NODE_DIR|g" "$BIN_DIR/npx"
chmod +x "$BIN_DIR/npx"
echo -e "${GREEN}[OK]${NC} npx wrapper created ($BIN_DIR/npx)"
fi
# corepack uses a different structure — shebang patch is sufficient
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"
echo -e "${GREEN}[OK]${NC} corepack shebang patched"
fi
# ── Step 3: Configure npm ─────────────────────
echo ""
echo "Configuring npm..."
# Set script-shell to ensure npm lifecycle scripts use the correct shell
# On Android 9+, /bin/sh exists. On 7-8 it doesn't.
# Using $PREFIX/bin/sh is always safe.
export PATH="$BIN_DIR:$NODE_DIR/bin:$PATH"
"$BIN_DIR/npm" config set script-shell "$PREFIX/bin/sh" 2>/dev/null || true
echo -e "${GREEN}[OK]${NC} npm script-shell set to $PREFIX/bin/sh"
# ── Step 4: Verify ────────────────────────────
echo ""
echo "Verifying glibc Node.js..."
NODE_VER=$("$BIN_DIR/node" --version 2>/dev/null) || {
echo -e "${RED}[FAIL]${NC} Node.js verification failed — wrapper script may be broken"
exit 1
}
echo -e "${GREEN}[OK]${NC} Node.js $NODE_VER (glibc, grun wrapper)"
NPM_VER=$("$BIN_DIR/npm" --version 2>/dev/null) || {
echo -e "${YELLOW}[WARN]${NC} npm verification failed"
}
if [ -n "${NPM_VER:-}" ]; then
echo -e "${GREEN}[OK]${NC} npm $NPM_VER"
fi
# Quick platform check
PLATFORM=$("$BIN_DIR/node" -e "console.log(process.platform)" 2>/dev/null) || true
if [ "$PLATFORM" = "linux" ]; then
echo -e "${GREEN}[OK]${NC} platform: linux (correct)"
else
echo -e "${YELLOW}[WARN]${NC} platform: ${PLATFORM:-unknown} (expected: linux)"
fi
echo ""
echo -e "${GREEN}Node.js installed successfully.${NC}"
echo " Node.js: $NODE_VER ($BIN_DIR/node)"
+237
View File
@@ -0,0 +1,237 @@
#!/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"
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
# install-playwright.sh - Install Playwright for browser automation
# Usage: bash install-playwright.sh [install|update]
#
# What it does:
# 1. Ensure Chromium is installed (dependency)
# 2. Install playwright-core via npm global
# 3. Set Playwright environment variables in .bashrc
# 4. Print usage guide
#
# This script is WARN-level: failure does not abort the parent installer.
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
NC='\033[0m'
MODE="${1:-install}"
# ── Helper ────────────────────────────────────
fail_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
exit 0
}
# ── Detect Chromium binary path ───────────────
detect_chromium_bin() {
for bin in "$PREFIX/bin/chromium-browser" "$PREFIX/bin/chromium"; do
if [ -x "$bin" ]; then
echo "$bin"
return 0
fi
done
return 1
}
# ── Pre-checks ────────────────────────────────
if [ -z "${PREFIX:-}" ]; then
fail_warn "Not running in Termux (\$PREFIX not set)"
fi
if ! command -v npm &>/dev/null; then
fail_warn "npm not found — Node.js is required for Playwright"
fi
# ── Step 1: Ensure Chromium is installed ──────
if ! CHROMIUM_BIN=$(detect_chromium_bin); then
echo "Chromium is required for Playwright. Installing..."
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ -f "$SCRIPT_DIR/install-chromium.sh" ]; then
if ! bash "$SCRIPT_DIR/install-chromium.sh" install; then
fail_warn "Chromium installation failed — cannot proceed with Playwright"
fi
else
fail_warn "install-chromium.sh not found — install Chromium first"
fi
if ! CHROMIUM_BIN=$(detect_chromium_bin); then
fail_warn "Chromium binary not found after installation"
fi
fi
echo -e "${GREEN}[OK]${NC} Chromium found: $CHROMIUM_BIN"
# ── Step 2: Install playwright-core ───────────
if [ "$MODE" = "install" ]; then
if npm list -g playwright-core &>/dev/null; then
echo -e "${GREEN}[SKIP]${NC} playwright-core already installed"
else
echo "Installing playwright-core..."
if ! npm install -g playwright-core; then
fail_warn "Failed to install playwright-core"
fi
echo -e "${GREEN}[OK]${NC} playwright-core installed"
fi
elif [ "$MODE" = "update" ]; then
echo "Updating playwright-core..."
if ! npm install -g playwright-core@latest; then
fail_warn "Failed to update playwright-core"
fi
echo -e "${GREEN}[OK]${NC} playwright-core updated"
fi
# ── Step 3: Set environment variables ─────────
BASHRC="$HOME/.bashrc"
PW_MARKER_START="# >>> Playwright >>>"
PW_MARKER_END="# <<< Playwright <<<"
PW_BLOCK="${PW_MARKER_START}
export PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=\"$CHROMIUM_BIN\"
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
${PW_MARKER_END}"
touch "$BASHRC"
if grep -qF "$PW_MARKER_START" "$BASHRC"; then
sed -i "/${PW_MARKER_START//\//\\/}/,/${PW_MARKER_END//\//\\/}/d" "$BASHRC"
fi
echo "" >> "$BASHRC"
echo "$PW_BLOCK" >> "$BASHRC"
echo -e "${GREEN}[OK]${NC} Environment variables set in .bashrc"
echo " PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=$CHROMIUM_BIN"
echo " PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1"
# ── Step 4: Usage guide ──────────────────────
echo ""
echo -e "${BOLD} Playwright is ready!${NC}"
echo ""
echo " To use in your project:"
echo ""
echo " npm install playwright-core # add to your project"
echo ""
echo " Example code:"
echo ""
echo " const { chromium } = require('playwright-core');"
echo ""
echo " const browser = await chromium.launch();"
echo " const page = await browser.newPage();"
echo " await page.goto('https://example.com');"
echo " await page.screenshot({ path: 'screenshot.png' });"
echo " await browser.close();"
echo ""
echo -e " ${YELLOW}[NOTE]${NC} Environment variables are set. No need to specify"
echo " executablePath or --no-sandbox manually."
echo ""
echo " To apply environment variables in current session:"
echo " source ~/.bashrc"
echo ""
Executable
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
# lib.sh — Shared function library for all orchestrators
# Usage: source "$SCRIPT_DIR/scripts/lib.sh" (from repo)
# source "$PROJECT_DIR/scripts/lib.sh" (from installed copy)
# ── Color constants ──
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
NC='\033[0m'
# ── Project constants ──
PROJECT_DIR="$HOME/.openclaw-android"
BIN_DIR="$PROJECT_DIR/bin"
PLATFORM_MARKER="$PROJECT_DIR/.platform"
REPO_BASE_ORIGIN="https://raw.githubusercontent.com/AidanPark/openclaw-android/main"
REPO_BASE_MIRRORS=(
"https://ghfast.top/https://raw.githubusercontent.com/AidanPark/openclaw-android/main"
"https://ghproxy.net/https://raw.githubusercontent.com/AidanPark/openclaw-android/main"
"https://mirror.ghproxy.com/https://raw.githubusercontent.com/AidanPark/openclaw-android/main"
)
NPM_REGISTRY_ORIGIN="https://registry.npmjs.org/"
NPM_REGISTRY_MIRROR="https://registry.npmmirror.com/"
NPM_REGISTRY_CACHE="$PROJECT_DIR/.npm-registry"
# Detect reachable REPO_BASE (origin first, then mirrors)
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
for mirror in "${REPO_BASE_MIRRORS[@]}"; do
if curl -sI --connect-timeout 3 "$mirror/oa.sh" >/dev/null 2>&1; then
echo -e " ${YELLOW}[MIRROR]${NC} Using mirror: ${mirror%%/oa.sh*}"
REPO_BASE="$mirror"
return 0
fi
done
# Fallback to origin even if unreachable
REPO_BASE="$REPO_BASE_ORIGIN"
return 1
}
# Detect reachable npm registry and export NPM_CONFIG_REGISTRY (origin first, then mirror)
resolve_npm_registry() {
local choice
local cache_file="$NPM_REGISTRY_CACHE"
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
}
# Fix shebangs in npm globally-installed CLI entry points
# Rewrites #!/usr/bin/env node → #!$BIN_DIR/node so CLIs work on Android
fix_npm_global_shebangs() {
local _js
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
}
# Initialize REPO_BASE
REPO_BASE="$REPO_BASE_ORIGIN"
BASHRC_MARKER_START="# >>> OpenClaw on Android >>>"
BASHRC_MARKER_END="# <<< OpenClaw on Android <<<"
OA_VERSION="1.0.27"
# ── Platform detection ──
# 1. Explicit marker file (new install and after first update)
# 2. Legacy detection (v1.0.2 and below, one-time)
# 3. Detection failure
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
}
# ── Platform name validation ──
validate_platform_name() {
local name="$1"
if [ -z "$name" ]; then
echo -e "${RED}[FAIL]${NC} Platform name is empty"
return 1
fi
# Only lowercase alphanumeric + hyphens/underscores allowed
if [[ ! "$name" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then
echo -e "${RED}[FAIL]${NC} Invalid platform name: $name"
return 1
fi
return 0
}
# ── User confirmation prompt ──
# Reads from /dev/tty so it works even in curl|bash mode.
ask_yn() {
local prompt="$1"
local reply
if (echo -n "" > /dev/tty) 2>/dev/null; then
read -rp "$prompt [Y/n] " reply < /dev/tty
else
read -rp "$prompt [Y/n] " reply
fi
[[ "${reply:-}" =~ ^[Nn]$ ]] && return 1
return 0
}
# ── Load platform config.env ──
# $1: platform name, $2: base directory (parent of platforms/)
load_platform_config() {
local platform="$1"
local base_dir="$2"
local config_path="$base_dir/platforms/$platform/config.env"
validate_platform_name "$platform" || return 1
if [ ! -f "$config_path" ]; then
echo -e "${RED}[FAIL]${NC} Platform config not found: $config_path"
return 1
fi
# shellcheck source=/dev/null
source "$config_path"
return 0
}
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/lib.sh"
BASHRC="$HOME/.bashrc"
PLATFORM=$(detect_platform) || true
INFRA_VARS="export TMPDIR=\"\$PREFIX/tmp\"
export TMP=\"\$TMPDIR\"
export TEMP=\"\$TMPDIR\"
export OA_GLIBC=1"
# npm registry re-injection — reads cache file written by resolve_npm_registry.
# Literal \$HOME, \${NPM_CONFIG_REGISTRY:-}, and \$(cat ...) are preserved for
# runtime expansion in each new shell. -z guard lets users override manually.
NPM_REGISTRY_INJECT="# 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\")\""
PATH_LINE="export PATH=\"\$HOME/.local/bin:\$PATH\""
if [ -n "$PLATFORM" ]; then
load_platform_config "$PLATFORM" "$(dirname "$(dirname "$0")")" 2>/dev/null || true
if [ "${PLATFORM_NEEDS_NODEJS:-}" = true ]; then
PATH_LINE="export PATH=\"\$HOME/.openclaw-android/bin:\$HOME/.openclaw-android/node/bin:\$HOME/.local/bin:\$PATH\""
fi
fi
PLATFORM_VARS=""
PLATFORM_ENV_SCRIPT="$(dirname "$(dirname "$0")")/platforms/$PLATFORM/env.sh"
if [ -n "$PLATFORM" ] && [ -f "$PLATFORM_ENV_SCRIPT" ]; then
PLATFORM_VARS=$(bash "$PLATFORM_ENV_SCRIPT")
fi
ENV_BLOCK="${BASHRC_MARKER_START}
# platform: ${PLATFORM:-none}
${PATH_LINE}
${INFRA_VARS}"
if [ -n "$PLATFORM_VARS" ]; then
ENV_BLOCK="${ENV_BLOCK}
${PLATFORM_VARS}"
fi
ENV_BLOCK="${ENV_BLOCK}
${NPM_REGISTRY_INJECT}
${BASHRC_MARKER_END}"
touch "$BASHRC"
if grep -qF "$BASHRC_MARKER_START" "$BASHRC"; then
sed -i "/${BASHRC_MARKER_START//\//\\/}/,/${BASHRC_MARKER_END//\//\\/}/d" "$BASHRC"
fi
echo "" >> "$BASHRC"
echo "$ENV_BLOCK" >> "$BASHRC"
if [ ! -e "$PREFIX/bin/ar" ] && [ -x "$PREFIX/bin/llvm-ar" ]; then
ln -s "$PREFIX/bin/llvm-ar" "$PREFIX/bin/ar"
fi
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/lib.sh"
echo "=== Setting Up Paths ==="
echo ""
mkdir -p "$PREFIX/tmp"
echo -e "${GREEN}[OK]${NC} Created $PREFIX/tmp"
mkdir -p "$PROJECT_DIR/patches"
echo -e "${GREEN}[OK]${NC} Created $PROJECT_DIR/patches"
echo ""
echo "Standard path mappings (via \$PREFIX):"
echo " /bin/sh -> $PREFIX/bin/sh"
echo " /usr/bin/env -> $PREFIX/bin/env"
echo " /tmp -> $PREFIX/tmp"
echo ""
echo -e "${GREEN}Path setup complete.${NC}"