feat: setup inicial para Claude Code en Termux
Scripts, configuraciones y documentación para ejecutar Claude Code CLI en Termux/Android. Incluye wrapper con termux-chroot para /tmp, actualizador seguro con techo de versión v2.1.112, resolver mDNS puro en Python y ProxyCommand SSH con fallback Tailscale. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
0d5d099dee
|
|
@ -0,0 +1,3 @@
|
|||
*.bak
|
||||
*.tmp
|
||||
.DS_Store
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Claude Code for Termux
|
||||
|
||||
Run [Claude Code](https://claude.ai/code) CLI on Android via Termux.
|
||||
|
||||
## Why
|
||||
|
||||
Claude Code v2.1.113+ switched to native binaries, breaking Termux/Android compatibility. This repo provides:
|
||||
|
||||
- **v2.1.112** (last pure Node.js release) with auto-update protection
|
||||
- **termux-chroot wrapper** that fixes the `/tmp` permission issue
|
||||
- **mDNS resolver** for SSH without hardcoded IPs
|
||||
- **Safe updater** that respects the version ceiling
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Termux](https://f-droid.org/en/packages/com.termux/) (F-Droid recommended)
|
||||
- Android 10+ (ARM64)
|
||||
- ~500MB free storage
|
||||
- An Anthropic API key or Claude subscription
|
||||
|
||||
## Quick Install
|
||||
|
||||
```bash
|
||||
git clone <your-gitea-url>/andres/claude-code-termux.git
|
||||
cd claude-code-termux
|
||||
bash install.sh
|
||||
```
|
||||
|
||||
Or dry-run first:
|
||||
```bash
|
||||
bash install.sh --dry-run
|
||||
```
|
||||
|
||||
## What Gets Installed
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| Claude Code v2.1.112 | npm global | The CLI itself |
|
||||
| `clauded` | `~/.local/bin/` | Wrapper with termux-chroot for /tmp |
|
||||
| `claude-check-env` | `~/.local/bin/` | Environment validator |
|
||||
| `claude-update` | `~/.local/bin/` | Safe updater (ceiling-aware) |
|
||||
| `mdns-resolve` | `~/.local/bin/` | Pure Python mDNS resolver |
|
||||
| `ssh-mdns-proxy` | `~/.local/bin/` | SSH ProxyCommand with mDNS+Tailscale |
|
||||
| `settings.json` | `~/.claude/` | Disables auto-updater |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Start Claude Code (always use this, not `claude` directly)
|
||||
clauded
|
||||
|
||||
# Check environment
|
||||
claude-check-env
|
||||
|
||||
# Update (safe, respects version ceiling)
|
||||
claude-update
|
||||
```
|
||||
|
||||
## SSH with Dynamic Resolution
|
||||
|
||||
The included `ssh-mdns-proxy` resolves hostnames via mDNS first, then Tailscale DNS, then cache. See `config/ssh_config.example` for setup.
|
||||
|
||||
```bash
|
||||
# Set your Tailscale domain (optional, has a default)
|
||||
export TAILSCALE_DOMAIN="your-tailnet.ts.net"
|
||||
```
|
||||
|
||||
## Version Ceiling
|
||||
|
||||
**v2.1.112 is the last compatible version.** Starting v2.1.113, Claude Code ships native binaries requiring glibc/musl linkers that don't exist on Android (Bionic libc).
|
||||
|
||||
`claude-update` enforces this automatically. See `docs/TROUBLESHOOTING.md` for details.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for:
|
||||
- `/tmp` permission errors
|
||||
- Native binary errors
|
||||
- Auto-updater issues
|
||||
- SSH resolution problems
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# claude-check-env: Verify Claude Code environment on Termux
|
||||
#
|
||||
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
CYAN="\033[0;36m"
|
||||
NC="\033[0m"
|
||||
|
||||
echo -e "${CYAN}=== Claude Code - Environment Check ===${NC}"
|
||||
echo ""
|
||||
|
||||
# 1. Claude version
|
||||
CLAUDE_VER=$(claude --version 2>/dev/null | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" | head -1)
|
||||
if [[ -n "$CLAUDE_VER" ]]; then
|
||||
echo -e "${GREEN}[OK]${NC} Claude Code: v$CLAUDE_VER"
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} Claude Code not installed"
|
||||
fi
|
||||
|
||||
# 2. Node.js
|
||||
NODE_VER=$(node --version 2>/dev/null)
|
||||
if [[ -n "$NODE_VER" ]]; then
|
||||
echo -e "${GREEN}[OK]${NC} Node.js: $NODE_VER"
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} Node.js not installed"
|
||||
fi
|
||||
|
||||
# 3. proot/termux-chroot
|
||||
if command -v termux-chroot &>/dev/null; then
|
||||
echo -e "${GREEN}[OK]${NC} termux-chroot available"
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} termux-chroot not available (pkg install proot)"
|
||||
fi
|
||||
|
||||
# 4. Sharp WASM (image support)
|
||||
SHARP_WASM=$(npm list -g @img/sharp-wasm32 2>/dev/null | grep sharp-wasm32)
|
||||
if [[ -n "$SHARP_WASM" ]]; then
|
||||
echo -e "${GREEN}[OK]${NC} Sharp WASM: installed (image support)"
|
||||
else
|
||||
echo -e "${YELLOW}[WARN]${NC} Sharp WASM not installed (no image support)"
|
||||
echo " Install: npm install -g @img/sharp-wasm32 sharp --force"
|
||||
fi
|
||||
|
||||
# 5. Credentials
|
||||
if [[ -f ~/.claude/.credentials.json ]]; then
|
||||
echo -e "${GREEN}[OK]${NC} Credentials: configured"
|
||||
else
|
||||
echo -e "${RED}[ERROR]${NC} No credentials (run: claude auth)"
|
||||
fi
|
||||
|
||||
# 6. TMPDIR
|
||||
echo -e "${GREEN}[OK]${NC} TMPDIR: ${TMPDIR:-not set}"
|
||||
|
||||
# 7. Tailscale (generic check via interface)
|
||||
if ip link show tailscale0 &>/dev/null 2>&1; then
|
||||
echo -e "${GREEN}[OK]${NC} Tailscale: connected"
|
||||
else
|
||||
echo -e "${YELLOW}[WARN]${NC} Tailscale: not connected"
|
||||
fi
|
||||
|
||||
# 8. Disk space
|
||||
SPACE=$(df -h "$PREFIX" 2>/dev/null | tail -1 | awk '{print $4}')
|
||||
echo -e "${GREEN}[OK]${NC} Free space: $SPACE"
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}=== Available Commands ===${NC}"
|
||||
echo " clauded - Start Claude (interactive mode)"
|
||||
echo " claude-update - Update Claude Code (safe)"
|
||||
echo " claude-check-env - This check"
|
||||
echo ""
|
||||
echo -e "${CYAN}=== Known Limitations ===${NC}"
|
||||
echo " - Background tasks may fail (use termux-chroot via clauded)"
|
||||
echo " - Version ceiling: v2.1.112 (last pure Node.js release)"
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# claude-update: Safe updater for Claude Code on Termux
|
||||
# Respects version ceiling (v2.1.112 = last pure Node.js release)
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
NC="\033[0m"
|
||||
|
||||
MAX_VERSION="2.1.112"
|
||||
|
||||
CURRENT=$(claude --version 2>/dev/null | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" | head -1)
|
||||
echo "Current: v${CURRENT:-not installed}"
|
||||
|
||||
# Check what latest available is
|
||||
LATEST=$(npm view @anthropic-ai/claude-code version 2>/dev/null)
|
||||
echo "Latest available: v${LATEST:-unknown}"
|
||||
echo "Termux ceiling: v$MAX_VERSION"
|
||||
echo ""
|
||||
|
||||
# Compare versions (is latest > ceiling?)
|
||||
version_gt() {
|
||||
test "$(printf '%s\n' "$1" "$2" | sort -V | tail -1)" != "$2"
|
||||
}
|
||||
|
||||
if [[ "$CURRENT" == "$MAX_VERSION" ]]; then
|
||||
echo -e "${GREEN}Already at ceiling version v$MAX_VERSION${NC}"
|
||||
echo "Versions above this use native binaries (incompatible with Termux/Android)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if version_gt "$LATEST" "$MAX_VERSION"; then
|
||||
echo -e "${YELLOW}Latest (v$LATEST) exceeds ceiling.${NC}"
|
||||
echo "Installing ceiling version v$MAX_VERSION instead..."
|
||||
TARGET="$MAX_VERSION"
|
||||
else
|
||||
TARGET="$LATEST"
|
||||
fi
|
||||
|
||||
# Verify target is pure Node.js (has cli.js, not cli-wrapper.cjs)
|
||||
echo "Verifying v$TARGET is Node.js compatible..."
|
||||
FILES=$(npm pack "@anthropic-ai/claude-code@$TARGET" --dry-run 2>&1 || true)
|
||||
if echo "$FILES" | grep -q "cli-wrapper.cjs"; then
|
||||
echo -e "${RED}ERROR: v$TARGET uses native binaries (cli-wrapper.cjs)${NC}"
|
||||
echo "This version is NOT compatible with Termux/Android."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Unprotect if previously locked
|
||||
CLAUDE_DIR="$PREFIX/lib/node_modules/@anthropic-ai/claude-code"
|
||||
if [[ -d "$CLAUDE_DIR" ]]; then
|
||||
chmod -R u+w "$CLAUDE_DIR" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "Installing v$TARGET..."
|
||||
npm install -g "@anthropic-ai/claude-code@$TARGET"
|
||||
|
||||
# Re-protect against auto-updater
|
||||
chmod -R a-w "$CLAUDE_DIR" 2>/dev/null || true
|
||||
|
||||
NEW=$(claude --version 2>/dev/null | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" | head -1)
|
||||
echo ""
|
||||
echo -e "${GREEN}Updated: v$CURRENT -> v$NEW${NC}"
|
||||
echo "Package protected against auto-updater (chmod a-w)"
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# clauded: Wrapper for Claude Code on Termux
|
||||
# Uses termux-chroot for working /tmp in all child processes
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
TERMUX_TMP="$PREFIX/tmp"
|
||||
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
NC="\033[0m"
|
||||
|
||||
# Verify termux-chroot
|
||||
if ! command -v termux-chroot &>/dev/null; then
|
||||
echo -e "${RED}Error: termux-chroot not available${NC}"
|
||||
echo "Install with: pkg install proot"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify claude
|
||||
if ! command -v claude &>/dev/null; then
|
||||
echo -e "${RED}Error: claude is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean orphan Claude temp files (older than 1 day)
|
||||
find "$TERMUX_TMP" -name "claude-*-cwd" -mtime +1 -delete 2>/dev/null || true
|
||||
|
||||
# Check Tailscale connectivity (generic: look for tailscale0 interface)
|
||||
if ! ip link show tailscale0 &>/dev/null 2>&1; then
|
||||
echo -e "${YELLOW}Tailscale not connected${NC} - SSH to remote hosts will fail"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
CLAUDE_VER=$(claude --version 2>/dev/null | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" | head -1)
|
||||
echo -e "${GREEN}Claude Code v$CLAUDE_VER (termux-chroot)${NC}"
|
||||
echo -e " /tmp -> /usr/tmp (FHS)"
|
||||
echo ""
|
||||
|
||||
exec termux-chroot claude --dangerously-skip-permissions "$@"
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Resolve hostname.local via mDNS multicast (pure Python, no dependencies).
|
||||
Usage: mdns-resolve <hostname.local> [timeout_seconds]
|
||||
Returns: IP address on stdout, or exit 1 on failure.
|
||||
"""
|
||||
import sys
|
||||
import socket
|
||||
import struct
|
||||
import select
|
||||
import time
|
||||
|
||||
|
||||
def build_mdns_query(hostname: str) -> bytes:
|
||||
header = struct.pack('>HHHHHH', 0, 0, 1, 0, 0, 0)
|
||||
question = b''
|
||||
for part in hostname.rstrip('.').split('.'):
|
||||
question += bytes([len(part)]) + part.encode()
|
||||
question += b'\x00'
|
||||
question += struct.pack('>HH', 1, 1) # Type A, Class IN
|
||||
return header + question
|
||||
|
||||
|
||||
def parse_mdns_response(data: bytes) -> str | None:
|
||||
if len(data) < 12:
|
||||
return None
|
||||
qdcount = struct.unpack('>H', data[4:6])[0]
|
||||
ancount = struct.unpack('>H', data[6:8])[0]
|
||||
if ancount == 0:
|
||||
return None
|
||||
|
||||
offset = 12
|
||||
for _ in range(qdcount):
|
||||
while offset < len(data) and data[offset] != 0:
|
||||
if data[offset] & 0xc0 == 0xc0:
|
||||
offset += 2
|
||||
break
|
||||
offset += data[offset] + 1
|
||||
else:
|
||||
offset += 1
|
||||
offset += 4
|
||||
|
||||
for _ in range(ancount):
|
||||
while offset < len(data):
|
||||
if data[offset] & 0xc0 == 0xc0:
|
||||
offset += 2
|
||||
break
|
||||
elif data[offset] == 0:
|
||||
offset += 1
|
||||
break
|
||||
else:
|
||||
offset += data[offset] + 1
|
||||
if offset + 10 > len(data):
|
||||
break
|
||||
rtype, rclass, ttl, rdlength = struct.unpack('>HHIH', data[offset:offset+10])
|
||||
offset += 10
|
||||
if rtype == 1 and rdlength == 4 and offset + 4 <= len(data):
|
||||
return socket.inet_ntoa(data[offset:offset+4])
|
||||
offset += rdlength
|
||||
return None
|
||||
|
||||
|
||||
def resolve_mdns(hostname: str, timeout: float = 2.0) -> str | None:
|
||||
if not hostname.endswith('.local'):
|
||||
hostname += '.local'
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.setblocking(False)
|
||||
sock.sendto(build_mdns_query(hostname), ('224.0.0.251', 5353))
|
||||
end_time = time.time() + timeout
|
||||
while time.time() < end_time:
|
||||
ready, _, _ = select.select([sock], [], [], 0.1)
|
||||
if ready:
|
||||
try:
|
||||
data, _ = sock.recvfrom(4096)
|
||||
ip = parse_mdns_response(data)
|
||||
if ip:
|
||||
sock.close()
|
||||
return ip
|
||||
except Exception:
|
||||
pass
|
||||
sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: mdns-resolve <hostname.local> [timeout]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
hostname = sys.argv[1]
|
||||
timeout = float(sys.argv[2]) if len(sys.argv) > 2 else 2.0
|
||||
ip = resolve_mdns(hostname, timeout)
|
||||
if ip:
|
||||
print(ip)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
# ssh-mdns-proxy: Resolve .local via mDNS with fallbacks, then connect
|
||||
#
|
||||
# Resolution order: mDNS → Tailscale DNS → cached IP
|
||||
# Usage: ssh-mdns-proxy <hostname> [port]
|
||||
#
|
||||
# Configure your Tailscale domain:
|
||||
TAILSCALE_DOMAIN="${TAILSCALE_DOMAIN:-tailb0bb74.ts.net}"
|
||||
|
||||
HOST="$1"
|
||||
PORT="${2:-22}"
|
||||
CACHE="$HOME/.ssh/mdns-cache"
|
||||
QUERY="${HOST%.local}"
|
||||
|
||||
# Alias mapping (short name -> mDNS hostname)
|
||||
declare -A ALIASES=(
|
||||
[dell]="dell-latitude3400"
|
||||
[lenovo]="lenovo-ideapad"
|
||||
)
|
||||
[[ -n "${ALIASES[$QUERY]}" ]] && QUERY="${ALIASES[$QUERY]}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# 1. mDNS resolution
|
||||
IP=$("$SCRIPT_DIR/mdns-resolve" "${QUERY}.local" 2 2>/dev/null)
|
||||
|
||||
# 2. Fallback: Tailscale DNS
|
||||
if [[ -z "$IP" ]]; then
|
||||
IP=$(getent hosts "${QUERY}.${TAILSCALE_DOMAIN}" 2>/dev/null | awk '{print $1}')
|
||||
fi
|
||||
|
||||
# 3. Fallback: cached IP
|
||||
if [[ -z "$IP" && -f "$CACHE" ]]; then
|
||||
IP=$(grep "^${QUERY} " "$CACHE" 2>/dev/null | awk '{print $2}')
|
||||
fi
|
||||
|
||||
if [[ -z "$IP" ]]; then
|
||||
echo "Error: cannot resolve ${HOST}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Save to cache
|
||||
mkdir -p "$(dirname "$CACHE")"
|
||||
grep -v "^${QUERY} " "$CACHE" > "${CACHE}.tmp" 2>/dev/null || true
|
||||
echo "${QUERY} ${IP}" >> "${CACHE}.tmp"
|
||||
mv "${CACHE}.tmp" "$CACHE"
|
||||
|
||||
exec nc "$IP" "$PORT"
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# === Claude Code for Termux - .bashrc additions ===
|
||||
# Append these lines to your ~/.bashrc
|
||||
|
||||
# Node-gyp / native compilation config
|
||||
export CFLAGS="-I$PREFIX/include"
|
||||
export LDFLAGS="-L$PREFIX/lib"
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
export npm_config_nodedir=$PREFIX
|
||||
|
||||
# Add local bin to PATH (for clauded, claude-check-env, etc.)
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# Prevent Claude Code auto-updater (enforced at shell level too)
|
||||
export DISABLE_AUTOUPDATER=1
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"env": {
|
||||
"DISABLE_AUTOUPDATER": "1"
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# SSH Config for Termux with mDNS/Tailscale resolution
|
||||
# Copy relevant sections to ~/.ssh/config
|
||||
|
||||
Host *
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
ConnectTimeout 10
|
||||
|
||||
# === mDNS (.local) - Dynamic resolution ===
|
||||
Host *.local
|
||||
User <your-user>
|
||||
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
|
||||
|
||||
# === Short hostnames -> mDNS first, then Tailscale ===
|
||||
Host <host-1>
|
||||
User <your-user>
|
||||
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
|
||||
|
||||
Host <host-2>
|
||||
User <your-user>
|
||||
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
|
||||
|
||||
# === ProxyJump (hosts without Tailscale, via jump host) ===
|
||||
# Host <lan-only-host>
|
||||
# HostName <lan-only-host>.local
|
||||
# User <user>
|
||||
# ProxyJump <jump-host>
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
# Troubleshooting
|
||||
|
||||
## /tmp Permission Denied
|
||||
|
||||
**Error:** `EACCES: permission denied, mkdir '/tmp/claude/...'`
|
||||
|
||||
**Cause:** Claude Code hardcodes `/tmp` for background tasks. Termux doesn't have `/tmp`.
|
||||
|
||||
**Fix:** Always start Claude via `clauded`, which uses `termux-chroot` to provide a working `/tmp`.
|
||||
|
||||
```bash
|
||||
# Wrong
|
||||
claude
|
||||
|
||||
# Right
|
||||
clauded
|
||||
```
|
||||
|
||||
**Why not proot?** `proot -b $PREFIX/tmp:/tmp` only affects the main process. Child processes (Bash tool, agents) run in separate shells without the binding. `termux-chroot` simulates a full FHS where `/tmp -> /usr/tmp` works for ALL processes.
|
||||
|
||||
---
|
||||
|
||||
## Native Binary Error (v2.1.113+)
|
||||
|
||||
**Error:** `claude native binary not installed`
|
||||
|
||||
**Cause:** Starting with v2.1.113, Anthropic migrated from `cli.js` (pure Node.js) to native binaries (SEA). Termux reports `process.platform === 'android'` which is not supported.
|
||||
|
||||
**Why no workaround:**
|
||||
- `npm install --force` of linux-arm64 downloads the binary but needs `/lib/ld-linux-aarch64.so.1` (glibc) or `/lib/ld-musl-aarch64.so.1` (musl)
|
||||
- Termux uses Bionic libc (Android), neither linker exists
|
||||
- `termux-chroot` and `proot` don't help because the linkers simply don't exist
|
||||
|
||||
**Fix:** Stay on v2.1.112 (last pure Node.js release).
|
||||
|
||||
```bash
|
||||
claude-update # Automatically respects the version ceiling
|
||||
```
|
||||
|
||||
**How to verify a version is compatible:**
|
||||
```bash
|
||||
npm pack @anthropic-ai/claude-code@VERSION --dry-run 2>&1 | grep cli.js
|
||||
# If cli.js (~13MB) appears -> compatible
|
||||
# If cli-wrapper.cjs (~4KB) appears -> native binary, NOT compatible
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auto-updater Bypasses Settings
|
||||
|
||||
**Problem:** Claude Code may auto-update despite `DISABLE_AUTOUPDATER=1`.
|
||||
|
||||
**Fix:** Protect the installation directory:
|
||||
```bash
|
||||
chmod -R a-w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/
|
||||
```
|
||||
|
||||
To update later, unprotect first:
|
||||
```bash
|
||||
chmod -R u+w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/
|
||||
claude-update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSH .local Resolution Fails
|
||||
|
||||
**Error:** `ssh hostname.local` fails with "Could not resolve hostname"
|
||||
|
||||
**Cause:** mDNS (.local) does NOT work natively in Termux (Android's Bionic libc doesn't support it).
|
||||
|
||||
**Fix:** Use the included `ssh-mdns-proxy` as ProxyCommand in `~/.ssh/config`:
|
||||
```
|
||||
Host *.local
|
||||
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
|
||||
```
|
||||
|
||||
This uses pure Python multicast UDP to resolve mDNS, with Tailscale DNS fallback.
|
||||
|
||||
---
|
||||
|
||||
## Sharp/Image Support
|
||||
|
||||
**Warning:** `Sharp WASM not installed (no image support)`
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
npm install -g @img/sharp-wasm32 sharp --force
|
||||
```
|
||||
|
||||
This enables Claude Code to process images (screenshots, diagrams, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Alternative: proot-distro
|
||||
|
||||
For running newer Claude Code versions (v2.1.113+), you can use proot-distro with Ubuntu, which provides real glibc:
|
||||
|
||||
```bash
|
||||
pkg install proot-distro
|
||||
proot-distro install ubuntu
|
||||
proot-distro login ubuntu
|
||||
# Inside Ubuntu: install node, npm, claude-code@latest
|
||||
```
|
||||
|
||||
**Trade-offs:** +2GB disk, slight performance overhead, but full Linux compatibility.
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# install.sh: Set up Claude Code on Termux
|
||||
#
|
||||
# Usage: bash install.sh [--dry-run]
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
CYAN="\033[0;36m"
|
||||
NC="\033[0m"
|
||||
|
||||
DRY_RUN=false
|
||||
[[ "$1" == "--dry-run" ]] && DRY_RUN=true
|
||||
|
||||
CLAUDE_VERSION="2.1.112"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BIN_DIR="$HOME/.local/bin"
|
||||
|
||||
step() { echo -e "\n${CYAN}[$1/$TOTAL] $2${NC}"; }
|
||||
run() {
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${YELLOW}[dry-run]${NC} $*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Verify we're on Termux
|
||||
if [[ -z "$PREFIX" || ! -d "$PREFIX" ]]; then
|
||||
echo -e "${RED}Error: Not running on Termux${NC}"
|
||||
echo "This installer is designed for Termux on Android."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOTAL=8
|
||||
echo -e "${GREEN}=== Claude Code for Termux - Installer ===${NC}"
|
||||
echo "Version ceiling: v$CLAUDE_VERSION"
|
||||
$DRY_RUN && echo -e "${YELLOW}(dry-run mode - no changes will be made)${NC}"
|
||||
|
||||
# 1. Install system packages
|
||||
step 1 "Installing system packages..."
|
||||
run pkg install -y nodejs python git proot
|
||||
|
||||
# 2. Install Claude Code
|
||||
step 2 "Installing Claude Code v$CLAUDE_VERSION..."
|
||||
run npm install -g "@anthropic-ai/claude-code@$CLAUDE_VERSION"
|
||||
|
||||
# 3. Protect against auto-updater
|
||||
step 3 "Protecting against auto-updater..."
|
||||
CLAUDE_DIR="$PREFIX/lib/node_modules/@anthropic-ai/claude-code"
|
||||
if [[ -d "$CLAUDE_DIR" ]]; then
|
||||
run chmod -R a-w "$CLAUDE_DIR"
|
||||
fi
|
||||
|
||||
# 4. Install scripts
|
||||
step 4 "Installing scripts to $BIN_DIR..."
|
||||
run mkdir -p "$BIN_DIR"
|
||||
for script in clauded claude-check-env claude-update mdns-resolve ssh-mdns-proxy; do
|
||||
run cp "$SCRIPT_DIR/bin/$script" "$BIN_DIR/$script"
|
||||
run chmod +x "$BIN_DIR/$script"
|
||||
done
|
||||
|
||||
# 5. Claude settings
|
||||
step 5 "Configuring Claude settings..."
|
||||
run mkdir -p "$HOME/.claude"
|
||||
if [[ ! -f "$HOME/.claude/settings.json" ]]; then
|
||||
run cp "$SCRIPT_DIR/config/settings.json" "$HOME/.claude/settings.json"
|
||||
else
|
||||
echo " ~/.claude/settings.json already exists, skipping"
|
||||
fi
|
||||
|
||||
# 6. Bashrc additions
|
||||
step 6 "Updating ~/.bashrc..."
|
||||
if ! grep -q "DISABLE_AUTOUPDATER" "$HOME/.bashrc" 2>/dev/null; then
|
||||
if ! $DRY_RUN; then
|
||||
echo "" >> "$HOME/.bashrc"
|
||||
cat "$SCRIPT_DIR/config/bashrc.snippet" >> "$HOME/.bashrc"
|
||||
fi
|
||||
echo " Added Claude-related exports to ~/.bashrc"
|
||||
else
|
||||
echo " ~/.bashrc already configured, skipping"
|
||||
fi
|
||||
|
||||
# 7. Install sharp-wasm32 (image support)
|
||||
step 7 "Installing Sharp WASM (image support)..."
|
||||
run npm install -g @img/sharp-wasm32 sharp --force 2>/dev/null || {
|
||||
echo -e " ${YELLOW}Sharp install failed (optional, images won't work)${NC}"
|
||||
}
|
||||
|
||||
# 8. Done
|
||||
step 8 "Verifying installation..."
|
||||
if ! $DRY_RUN; then
|
||||
echo ""
|
||||
"$BIN_DIR/claude-check-env"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}=== Installation Complete ===${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Restart your shell: source ~/.bashrc"
|
||||
echo " 2. Authenticate: claude auth"
|
||||
echo " 3. Start Claude: clauded"
|
||||
echo ""
|
||||
echo "See docs/TROUBLESHOOTING.md for common issues."
|
||||
Loading…
Reference in New Issue