Android Termux安装最新版Claude Code的两种方案

Android Termux安装最新版Claude Code的两种方案
Android Termux安装最新版Claude Code的两种方案

我发现最新Claude code已无法直接在Android termux用npm安装来直接使用,会有报错,肯定是Termux环境的兼容问题,毕竟不是标准的Linux。如何解决:

1.非proot方案(推荐)

#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail

readonly SCRIPT_NAME="$(basename "$0")"
readonly PREFIX_DIR="${PREFIX:-/data/data/com.termux/files/usr}"
readonly STATE_DIR="${CLAUDE_CODE_HOME:-$HOME/.claude-code-termux}"
readonly NODE_DIR="$STATE_DIR/node"
readonly WRAPPER_BIN_DIR="$STATE_DIR/bin"
readonly PATCH_DIR="$STATE_DIR/patches"
readonly GLOBAL_PREFIX_DIR="$STATE_DIR/npm-global"
readonly GLOBAL_BIN_DIR="$GLOBAL_PREFIX_DIR/bin"
readonly NPM_CACHE_DIR="$STATE_DIR/npm-cache"
readonly TMP_ROOT_DIR="${TMPDIR:-$PREFIX_DIR/tmp}"
readonly GLIBC_LDSO="$PREFIX_DIR/glibc/lib/ld-linux-aarch64.so.1"
readonly GLIBC_RUNNER_BIN="$PREFIX_DIR/bin/grun"
readonly GLIBC_MARKER="$STATE_DIR/.glibc-arch"
readonly HOST_CLAUDE_PATH="$PREFIX_DIR/bin/claude"
readonly BACKUP_DIR="$STATE_DIR/backups"
readonly CLAUDE_PACKAGE_NAME="@anthropic-ai/claude-code"
readonly CLAUDE_PACKAGE_VERSION="${CLAUDE_CODE_VERSION:-latest}"
readonly NODE_VERSION="${CLAUDE_CODE_NODE_VERSION:-22.22.0}"
readonly NODE_TARBALL="node-v${NODE_VERSION}-linux-arm64.tar.xz"
readonly NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/${NODE_TARBALL}"
readonly COMPAT_PATCH_PATH="$PATCH_DIR/claude-glibc-compat.js"
readonly CLAUDE_EXE_PATH="$GLOBAL_PREFIX_DIR/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe"
readonly HOST_WRAPPER_MARKER="# claude-code-termux-nonproot-wrapper"

readonly C_BOLD_BLUE="\033[1;34m"
readonly C_BOLD_GREEN="\033[1;32m"
readonly C_BOLD_YELLOW="\033[1;33m"
readonly C_BOLD_RED="\033[1;31m"
readonly C_RESET="\033[0m"

info() {
    printf '%b[INFO]%b %s\n' "$C_BOLD_BLUE" "$C_RESET" "$*"
}

success() {
    printf '%b[ OK ]%b %s\n' "$C_BOLD_GREEN" "$C_RESET" "$*"
}

warn() {
    printf '%b[WARN]%b %s\n' "$C_BOLD_YELLOW" "$C_RESET" "$*" >&2
}

die() {
    printf '%b[ERR ]%b %s\n' "$C_BOLD_RED" "$C_RESET" "$*" >&2
    exit 1
}

usage() {
    cat <<EOF
Usage:
  bash $SCRIPT_NAME

What it does:
  1. Installs Termux dependencies needed for a glibc-based Node runtime.
  2. Installs glibc-runner through pacman (no proot distro).
  3. Downloads official Node.js ${NODE_VERSION} linux-arm64.
  4. Wraps node/npm with ld.so so they run on Termux.
  5. Installs ${CLAUDE_PACKAGE_NAME} and exposes it as:
       $HOST_CLAUDE_PATH

Environment overrides:
  CLAUDE_CODE_HOME          install state dir, default: $STATE_DIR
  CLAUDE_CODE_VERSION       npm package version/tag, default: $CLAUDE_PACKAGE_VERSION
  CLAUDE_CODE_NODE_VERSION  Node.js linux-arm64 version, default: $NODE_VERSION

Notes:
  - This follows the non-proot glibc-wrapper approach used by openclaw-android.
  - Only aarch64 Termux is supported.
  - Existing $HOST_CLAUDE_PATH will be backed up if it is not already managed.
EOF
}

command_exists() {
    command -v "$1" >/dev/null 2>&1
}

require_termux() {
    [ -d "$PREFIX_DIR" ] || die "This script must run in Termux."
    command_exists pkg || die "pkg not found. This script must run in Termux."
}

ensure_tmp_root() {
    mkdir -p "$TMP_ROOT_DIR"
}

ensure_state_dirs() {
    mkdir -p "$STATE_DIR" "$WRAPPER_BIN_DIR" "$PATCH_DIR" "$GLOBAL_PREFIX_DIR" \
        "$GLOBAL_BIN_DIR" "$NPM_CACHE_DIR" "$BACKUP_DIR"
}

ensure_termux_package() {
    local package_name="$1"

    if dpkg -s "$package_name" >/dev/null 2>&1; then
        success "Termux package already installed: $package_name"
        return 0
    fi

    info "Installing Termux package: $package_name"
    pkg install -y "$package_name"
    success "Installed Termux package: $package_name"
}

ensure_glibc_runner() {
    local arch
    local pacman_conf
    local siglevel_patched=0

    arch="$(uname -m)"
    [ "$arch" = "aarch64" ] || die "glibc mode only supports aarch64, got: $arch"

    if [ -f "$GLIBC_MARKER" ] && [ -x "$GLIBC_LDSO" ]; then
        success "glibc-runner already available"
        return 0
    fi

    ensure_termux_package "pacman"

    pacman_conf="$PREFIX_DIR/etc/pacman.conf"

    info "Initializing pacman for glibc-runner"
    if [ -f "$pacman_conf" ] && ! grep -q '^SigLevel = Never' "$pacman_conf"; then
        cp "$pacman_conf" "${pacman_conf}.bak"
        sed -i 's/^SigLevel\s*=.*/SigLevel = Never/' "$pacman_conf"
        siglevel_patched=1
        warn "Applied temporary pacman SigLevel workaround"
    fi

    pacman-key --init 2>/dev/null || true
    pacman-key --populate 2>/dev/null || true

    info "Installing glibc-runner"
    if ! pacman -Sy glibc-runner --noconfirm --assume-installed bash,patchelf,resolv-conf; then
        if [ "$siglevel_patched" -eq 1 ] && [ -f "${pacman_conf}.bak" ]; then
            mv "${pacman_conf}.bak" "$pacman_conf"
        fi
        die "Failed to install glibc-runner"
    fi

    if [ "$siglevel_patched" -eq 1 ] && [ -f "${pacman_conf}.bak" ]; then
        mv "${pacman_conf}.bak" "$pacman_conf"
        success "Restored pacman SigLevel"
    fi

    [ -x "$GLIBC_LDSO" ] || die "glibc dynamic linker not found at $GLIBC_LDSO"

    touch "$GLIBC_MARKER"
    success "glibc-runner is ready"
}

write_compat_patch() {
    info "Writing Node compatibility patch"

    cat >"$COMPAT_PATCH_PATH" <<'EOF'
'use strict';

const childProcess = require('child_process');
const dns = require('dns');
const fs = require('fs');
const os = require('os');
const path = require('path');

const prefix = process.env.PREFIX || '/data/data/com.termux/files/usr';
const home = process.env.HOME || '/data/data/com.termux/files/home';
const wrapperPath = process.env._CLAUDE_WRAPPER_PATH || path.join(home, '.claude-code-termux', 'bin', 'node');
const termuxExec = path.join(prefix, 'lib', 'libtermux-exec-ld-preload.so');
const termuxShell = path.join(prefix, 'bin', 'sh');

try {
    if (fs.existsSync(wrapperPath)) {
        Object.defineProperty(process, 'execPath', {
            value: wrapperPath,
            writable: true,
            configurable: true,
        });
    }
} catch {}

if (process.env._CLAUDE_ORIG_LD_PRELOAD) {
    process.env.LD_PRELOAD = process.env._CLAUDE_ORIG_LD_PRELOAD;
    delete process.env._CLAUDE_ORIG_LD_PRELOAD;
} else if (!process.env.LD_PRELOAD) {
    try {
        if (fs.existsSync(termuxExec)) {
            process.env.LD_PRELOAD = termuxExec;
        }
    } catch {}
}

const originalCpus = os.cpus;
os.cpus = function cpus() {
    try {
        const result = originalCpus.call(os);
        if (Array.isArray(result) && result.length > 0) {
            return result;
        }
    } catch {}
    return [{
        model: 'unknown',
        speed: 0,
        times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 },
    }];
};

const originalNetworkInterfaces = os.networkInterfaces;
os.networkInterfaces = function networkInterfaces() {
    try {
        return originalNetworkInterfaces.call(os);
    } catch {
        return {
            lo: [{
                address: '127.0.0.1',
                netmask: '255.0.0.0',
                family: 'IPv4',
                mac: '00:00:00:00:00:00',
                internal: true,
                cidr: '127.0.0.1/8',
            }],
        };
    }
};

if (!fs.existsSync('/bin/sh') && fs.existsSync(termuxShell)) {
    const originalExec = childProcess.exec;
    const originalExecSync = childProcess.execSync;

    childProcess.exec = function exec(command, options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        options = options || {};
        if (!options.shell) {
            options.shell = termuxShell;
        }
        return originalExec.call(childProcess, command, options, callback);
    };

    childProcess.execSync = function execSync(command, options) {
        options = options || {};
        if (!options.shell) {
            options.shell = termuxShell;
        }
        return originalExecSync.call(childProcess, command, options);
    };
}

try {
    let dnsServers = ['8.8.8.8', '8.8.4.4'];

    try {
        const resolvConf = fs.readFileSync(path.join(prefix, 'etc', 'resolv.conf'), 'utf8');
        const matches = resolvConf.match(/^nameserver\s+(.+)$/gm);
        if (matches && matches.length > 0) {
            dnsServers = matches.map((line) => line.replace(/^nameserver\s+/, '').trim());
        }
    } catch {}

    try {
        dns.setServers(dnsServers);
    } catch {}

    const originalLookup = dns.lookup;
    const originalLookupPromise = dns.promises.lookup;

    dns.lookup = function lookup(hostname, options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        const originalOptions = options;
        const opts = typeof options === 'number' ? { family: options } : (options || {});
        const wantAll = opts.all === true;
        const family = opts.family || 0;

        const resolveWith = (fam, done) => {
            const resolver = fam === 6 ? dns.resolve6 : dns.resolve4;
            resolver(hostname, done);
        };

        const tryResolve = (fam) => {
            resolveWith(fam, (error, addresses) => {
                if (!error && Array.isArray(addresses) && addresses.length > 0) {
                    const resolvedFamily = fam === 6 ? 6 : 4;
                    if (wantAll) {
                        callback(null, addresses.map((address) => ({
                            address,
                            family: resolvedFamily,
                        })));
                        return;
                    }
                    callback(null, addresses[0], resolvedFamily);
                    return;
                }

                if (family === 0 && fam === 4) {
                    tryResolve(6);
                    return;
                }

                originalLookup.call(dns, hostname, originalOptions, callback);
            });
        };

        tryResolve(family === 6 ? 6 : 4);
    };

    dns.promises.lookup = async function lookup(hostname, options) {
        const opts = typeof options === 'number' ? { family: options } : (options || {});
        const wantAll = opts.all === true;
        const family = opts.family || 0;

        const resolveWith = family === 6 ? dns.promises.resolve6 : dns.promises.resolve4;

        try {
            const addresses = await resolveWith(hostname);
            if (addresses.length > 0) {
                const resolvedFamily = family === 6 ? 6 : 4;
                if (wantAll) {
                    return addresses.map((address) => ({
                        address,
                        family: resolvedFamily,
                    }));
                }
                return {
                    address: addresses[0],
                    family: resolvedFamily,
                };
            }
        } catch {}

        if (family === 0) {
            try {
                const addresses = await dns.promises.resolve6(hostname);
                if (addresses.length > 0) {
                    if (wantAll) {
                        return addresses.map((address) => ({ address, family: 6 }));
                    }
                    return {
                        address: addresses[0],
                        family: 6,
                    };
                }
            } catch {}
        }

        return originalLookupPromise.call(dns.promises, hostname, options);
    };
} catch {}
EOF

    success "Compatibility patch written to $COMPAT_PATCH_PATH"
}

write_node_wrappers() {
    local node_bin_path
    local node_real_path

    node_bin_path="$NODE_DIR/bin/node"
    node_real_path="$NODE_DIR/bin/node.real"

    if [ -f "$node_real_path" ]; then
        :
    elif [ -f "$node_bin_path" ]; then
        mv "$node_bin_path" "$node_real_path"
    else
        die "Node binary missing at $node_bin_path"
    fi

    info "Writing node/npm wrappers"

    cat >"$WRAPPER_BIN_DIR/node" <<EOF
#!$PREFIX_DIR/bin/bash
[ -n "\${LD_PRELOAD:-}" ] && export _CLAUDE_ORIG_LD_PRELOAD="\$LD_PRELOAD"
unset LD_PRELOAD
export _CLAUDE_WRAPPER_PATH="$WRAPPER_BIN_DIR/node"
export TMPDIR="\${TMPDIR:-$TMP_ROOT_DIR}"
_CLAUDE_COMPAT="$COMPAT_PATCH_PATH"
if [ -f "\$_CLAUDE_COMPAT" ]; then
    case "\${NODE_OPTIONS:-}" in
        *"\$_CLAUDE_COMPAT"*) ;;
        *) export NODE_OPTIONS="\${NODE_OPTIONS:+\$NODE_OPTIONS }-r \$_CLAUDE_COMPAT" ;;
    esac
fi
exec "$GLIBC_LDSO" --library-path "$PREFIX_DIR/glibc/lib" "$NODE_DIR/bin/node.real" "\$@"
EOF

    cat >"$WRAPPER_BIN_DIR/npm" <<EOF
#!$PREFIX_DIR/bin/bash
export PATH="$WRAPPER_BIN_DIR:$NODE_DIR/bin:\$PATH"
export TMPDIR="\${TMPDIR:-$TMP_ROOT_DIR}"
export NPM_CONFIG_PREFIX="$GLOBAL_PREFIX_DIR"
export npm_config_prefix="$GLOBAL_PREFIX_DIR"
export NPM_CONFIG_CACHE="$NPM_CACHE_DIR"
export npm_config_cache="$NPM_CACHE_DIR"
export NPM_CONFIG_SCRIPT_SHELL="$PREFIX_DIR/bin/sh"
export npm_config_script_shell="$PREFIX_DIR/bin/sh"
exec "$WRAPPER_BIN_DIR/node" "$NODE_DIR/lib/node_modules/npm/bin/npm-cli.js" "\$@"
EOF

    cat >"$WRAPPER_BIN_DIR/npx" <<EOF
#!$PREFIX_DIR/bin/bash
export PATH="$WRAPPER_BIN_DIR:$NODE_DIR/bin:\$PATH"
export TMPDIR="\${TMPDIR:-$TMP_ROOT_DIR}"
export NPM_CONFIG_PREFIX="$GLOBAL_PREFIX_DIR"
export npm_config_prefix="$GLOBAL_PREFIX_DIR"
export NPM_CONFIG_CACHE="$NPM_CACHE_DIR"
export npm_config_cache="$NPM_CACHE_DIR"
export NPM_CONFIG_SCRIPT_SHELL="$PREFIX_DIR/bin/sh"
export npm_config_script_shell="$PREFIX_DIR/bin/sh"
exec "$WRAPPER_BIN_DIR/node" "$NODE_DIR/lib/node_modules/npm/bin/npx-cli.js" "\$@"
EOF

    chmod 755 "$WRAPPER_BIN_DIR/node" "$WRAPPER_BIN_DIR/npm" "$WRAPPER_BIN_DIR/npx"
    success "node/npm wrappers are ready"
}

install_node_runtime() {
    local installed_version
    local tmp_dir
    local extract_dir
    local fresh_dir

    ensure_termux_package "curl"
    ensure_termux_package "xz-utils"

    if [ -x "$WRAPPER_BIN_DIR/node" ]; then
        installed_version="$("$WRAPPER_BIN_DIR/node" --version 2>/dev/null | sed 's/^v//')"
        if [ "$installed_version" = "$NODE_VERSION" ]; then
            success "Node.js already installed: v$installed_version"
            write_compat_patch
            write_node_wrappers
            return 0
        fi
    fi

    info "Downloading official Node.js ${NODE_VERSION} linux-arm64"
    tmp_dir="$(mktemp -d "$TMP_ROOT_DIR/claude-node.XXXXXX")"

    curl -fL --max-time 300 "$NODE_URL" -o "$tmp_dir/$NODE_TARBALL"
    success "Downloaded $NODE_TARBALL"

    extract_dir="$tmp_dir/extract"
    fresh_dir="$tmp_dir/node-fresh"
    mkdir -p "$extract_dir" "$fresh_dir"

    tar -xJf "$tmp_dir/$NODE_TARBALL" -C "$extract_dir"
    mv "$extract_dir"/node-v"${NODE_VERSION}"-linux-arm64/* "$fresh_dir"/

    rm -rf "$NODE_DIR"
    mkdir -p "$(dirname "$NODE_DIR")"
    mv "$fresh_dir" "$NODE_DIR"

    write_compat_patch
    write_node_wrappers
    rm -rf "$tmp_dir"
    success "Node.js runtime installed in $NODE_DIR"
}

install_claude_package() {
    local package_spec

    package_spec="$CLAUDE_PACKAGE_NAME"
    if [ "$CLAUDE_PACKAGE_VERSION" != "latest" ]; then
        package_spec="${CLAUDE_PACKAGE_NAME}@${CLAUDE_PACKAGE_VERSION}"
    fi

    info "Installing $package_spec"
    PATH="$WRAPPER_BIN_DIR:$GLOBAL_BIN_DIR:$PATH" "$WRAPPER_BIN_DIR/npm" install -g "$package_spec"

    [ -e "$GLOBAL_BIN_DIR/claude" ] || die "npm install completed, but $GLOBAL_BIN_DIR/claude was not created"
    [ -x "$CLAUDE_EXE_PATH" ] || die "Claude native binary missing at $CLAUDE_EXE_PATH"
    success "Claude Code is installed under $GLOBAL_PREFIX_DIR"
}

backup_existing_launcher() {
    local backup_path

    if [ ! -e "$HOST_CLAUDE_PATH" ]; then
        return 0
    fi

    if grep -Fq "$HOST_WRAPPER_MARKER" "$HOST_CLAUDE_PATH" 2>/dev/null; then
        success "Managed host launcher already present"
        return 0
    fi

    backup_path="$BACKUP_DIR/claude.host-backup.$(date +%Y%m%d_%H%M%S)"
    cp "$HOST_CLAUDE_PATH" "$backup_path"
    success "Backed up existing launcher to $backup_path"
}

install_host_wrapper() {
    local tmp_wrapper

    tmp_wrapper="$(mktemp "$TMP_ROOT_DIR/claude-wrapper.XXXXXX")"

    cat >"$tmp_wrapper" <<EOF
#!$PREFIX_DIR/bin/bash
$HOST_WRAPPER_MARKER

export PATH="$WRAPPER_BIN_DIR:$GLOBAL_BIN_DIR:\$PATH"
export TMPDIR="\${TMPDIR:-$TMP_ROOT_DIR}"

exec "$GLIBC_RUNNER_BIN" -t "$CLAUDE_EXE_PATH" "\$@"
EOF

    chmod 755 "$tmp_wrapper"
    cp "$tmp_wrapper" "$HOST_CLAUDE_PATH"
    chmod 755 "$HOST_CLAUDE_PATH"
    rm -f "$tmp_wrapper"
    success "Installed host launcher: $HOST_CLAUDE_PATH"
}

verify_install() {
    info "Verifying Node wrapper"
    "$WRAPPER_BIN_DIR/node" --version

    info "Verifying npm wrapper"
    "$WRAPPER_BIN_DIR/npm" --version

    info "Verifying Claude Code launcher"
    "$HOST_CLAUDE_PATH" --version

    success "Non-proot Claude Code setup completed"
}

main() {
    if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
        usage
        exit 0
    fi

    require_termux
    ensure_tmp_root
    ensure_state_dirs
    ensure_glibc_runner
    install_node_runtime
    install_claude_package
    backup_existing_launcher
    install_host_wrapper
    verify_install

    cat <<EOF

Run Claude Code with:
  claude

Current configuration:
  state dir: $STATE_DIR
  node version: $NODE_VERSION
  package version: $CLAUDE_PACKAGE_VERSION
  launcher: $HOST_CLAUDE_PATH
EOF
}

main "$@"

2.proot方案 (会卡顿 卡就对了 卡了就等待)

#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail

readonly SCRIPT_NAME="$(basename "$0")"
readonly DISTRO_NAME="${CLAUDE_CODE_DISTRO:-debian}"
readonly CLAUDE_PACKAGE_NAME="@anthropic-ai/claude-code"
readonly CLAUDE_PACKAGE_VERSION="${CLAUDE_CODE_VERSION:-latest}"
readonly PREFIX_DIR="${PREFIX:-/data/data/com.termux/files/usr}"
readonly HOST_CLAUDE_PATH="$PREFIX_DIR/bin/claude"
readonly PROOT_ROOT_DIR="$PREFIX_DIR/var/lib/proot-distro/installed-rootfs"
readonly BACKUP_DIR="$HOME/.codex/tmp"
readonly WRAPPER_MARKER="# claude-code-termux-wrapper"

readonly C_BOLD_BLUE="\033[1;34m"
readonly C_BOLD_GREEN="\033[1;32m"
readonly C_BOLD_YELLOW="\033[1;33m"
readonly C_BOLD_RED="\033[1;31m"
readonly C_RESET="\033[0m"

info() {
    printf '%b[INFO]%b %s\n' "$C_BOLD_BLUE" "$C_RESET" "$*"
}

success() {
    printf '%b[ OK ]%b %s\n' "$C_BOLD_GREEN" "$C_RESET" "$*"
}

warn() {
    printf '%b[WARN]%b %s\n' "$C_BOLD_YELLOW" "$C_RESET" "$*" >&2
}

die() {
    printf '%b[ERR ]%b %s\n' "$C_BOLD_RED" "$C_RESET" "$*" >&2
    exit 1
}

usage() {
    cat <<EOF
Usage:
  bash $SCRIPT_NAME

What it does:
  1. Installs proot-distro in Termux if needed.
  2. Installs Debian userspace if needed.
  3. Installs nodejs + npm inside Debian.
  4. Installs ${CLAUDE_PACKAGE_NAME} inside Debian.
  5. Replaces Termux's claude launcher with a wrapper that forwards into Debian.

Environment overrides:
  CLAUDE_CODE_DISTRO    proot distro alias, default: ${DISTRO_NAME}
  CLAUDE_CODE_VERSION   npm package version/tag, default: ${CLAUDE_PACKAGE_VERSION}

Notes:
  - Official Claude Code npm binaries do not support Termux's android-arm64 host.
  - This script uses Debian in proot as the supported Linux runtime.
EOF
}

command_exists() {
    command -v "$1" >/dev/null 2>&1
}

require_termux() {
    [ -d "$PREFIX_DIR" ] || die "This script must run in Termux."
    command_exists pkg || die "pkg not found. This script must run in Termux."
}

ensure_termux_package() {
    local package_name="$1"

    if dpkg -s "$package_name" >/dev/null 2>&1; then
        success "Termux package already installed: $package_name"
        return 0
    fi

    info "Installing Termux package: $package_name"
    pkg install -y "$package_name"
    success "Installed Termux package: $package_name"
}

ensure_distro() {
    if [ -d "$PROOT_ROOT_DIR/$DISTRO_NAME" ]; then
        success "proot distro already installed: $DISTRO_NAME"
        return 0
    fi

    info "Installing proot distro: $DISTRO_NAME"
    proot-distro install "$DISTRO_NAME"
    success "Installed proot distro: $DISTRO_NAME"
}

run_in_distro() {
    local command_text="$1"

    proot-distro login "$DISTRO_NAME" -- bash -lc "$command_text"
}

ensure_distro_packages() {
    info "Updating apt metadata inside $DISTRO_NAME"
    run_in_distro "env DEBIAN_FRONTEND=noninteractive apt-get update"

    info "Installing nodejs and npm inside $DISTRO_NAME"
    run_in_distro "env DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs npm"
    success "nodejs and npm are ready inside $DISTRO_NAME"
}

install_claude_in_distro() {
    local package_spec="$CLAUDE_PACKAGE_NAME"

    if [ "$CLAUDE_PACKAGE_VERSION" != "latest" ]; then
        package_spec="${CLAUDE_PACKAGE_NAME}@${CLAUDE_PACKAGE_VERSION}"
    fi

    info "Installing ${package_spec} inside $DISTRO_NAME"
    run_in_distro "npm install -g ${package_spec@Q}"
    success "Claude Code is installed inside $DISTRO_NAME"
}

backup_existing_launcher() {
    local backup_path

    mkdir -p "$BACKUP_DIR"

    if [ ! -e "$HOST_CLAUDE_PATH" ]; then
        return 0
    fi

    if grep -Fq "$WRAPPER_MARKER" "$HOST_CLAUDE_PATH" 2>/dev/null; then
        success "Managed Termux launcher already present"
        return 0
    fi

    backup_path="$BACKUP_DIR/claude.host-backup.$(date +%Y%m%d_%H%M%S)"
    cp "$HOST_CLAUDE_PATH" "$backup_path"
    success "Backed up existing launcher to $backup_path"
}

install_host_wrapper() {
    local tmp_wrapper

    tmp_wrapper="$(mktemp "${TMPDIR:-/tmp}/claude-wrapper.XXXXXX")"

    cat >"$tmp_wrapper" <<EOF
#!/data/data/com.termux/files/usr/bin/sh
$WRAPPER_MARKER

work_dir=\$PWD
if [ ! -d "\$work_dir" ]; then
  work_dir=/root
fi

exec proot-distro login --shared-tmp --work-dir "\$work_dir" $DISTRO_NAME -- /usr/local/bin/claude "\$@"
EOF

    chmod 755 "$tmp_wrapper"
    cp "$tmp_wrapper" "$HOST_CLAUDE_PATH"
    chmod 755 "$HOST_CLAUDE_PATH"
    rm -f "$tmp_wrapper"
    success "Installed Termux launcher: $HOST_CLAUDE_PATH"
}

verify_install() {
    info "Verifying Claude inside $DISTRO_NAME"
    run_in_distro "claude --version"

    info "Verifying Termux launcher"
    "$HOST_CLAUDE_PATH" --version

    success "Claude Code setup completed"
}

main() {
    if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
        usage
        exit 0
    fi

    require_termux

    ensure_termux_package "proot-distro"
    ensure_distro
    ensure_distro_packages
    install_claude_in_distro
    backup_existing_launcher
    install_host_wrapper
    verify_install

    cat <<EOF

Run Claude Code with:
  claude

Current configuration:
  distro: $DISTRO_NAME
  host launcher: $HOST_CLAUDE_PATH
EOF
}

main "$@"

非proot方案参考 GitHub - AidanPark/openclaw-android: Run OpenClaw on Android with a single command — no proot, no Linux · GitHub

1 个帖子 - 1 位参与者

阅读完整话题

来源: linux.do查看原文