chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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 ""
|
||||
}
|
||||
Executable
+116
@@ -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
|
||||
Executable
+64
@@ -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
|
||||
Executable
+36
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+157
@@ -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"
|
||||
Executable
+26
@@ -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}"
|
||||
Executable
+352
@@ -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)"
|
||||
@@ -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"
|
||||
Executable
+136
@@ -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
@@ -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
|
||||
}
|
||||
Executable
+57
@@ -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
|
||||
Executable
+21
@@ -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}"
|
||||
Reference in New Issue
Block a user