claude-code-termux/bin/claude-update

134 lines
4.8 KiB
Bash
Executable File

#!/data/data/com.termux/files/usr/bin/bash
# claude-update: Actualizador seguro de Claude Code en Termux
# Respeta techo v2.1.112 (v2.1.113+ usa binarios nativos incompatibles con Bionic)
# Alternativa para versiones nuevas: github.com/ferrumclaudepilgrim/claude-code-android
set -euo pipefail
readonly MAX_VERSION="2.1.112"
readonly PKG="@anthropic-ai/claude-code"
readonly CLAUDE_DIR="$PREFIX/lib/node_modules/${PKG}"
readonly BACKUP_ROOT="$HOME/.cache/claude-updates"
readonly LOG="$BACKUP_ROOT/history.log"
readonly SETTINGS="$HOME/.claude/settings.json"
readonly R="\033[0;31m" G="\033[0;32m" Y="\033[1;33m" C="\033[0;36m" N="\033[0m"
CHECK_ONLY=0; DRY_RUN=0; FORCE=0; ROLLBACK=0; TARGET=""
usage() {
cat <<EOF
Uso: claude-update [--check|--dry-run|--force|--rollback|--version X.Y.Z|-h]
Actualiza Claude Code respetando el techo v${MAX_VERSION}.
v2.1.113+ usa SEA/glibc → incompatible con Bionic libc de Termux.
Path arriesgado para versiones nuevas (~450 MB, glibc-runner):
github.com/ferrumclaudepilgrim/claude-code-android
EOF
exit 0
}
log() { mkdir -p "$BACKUP_ROOT"; echo "[$(date -Iseconds)] $*" >> "$LOG"; }
die() { echo -e "${R}ERROR:${N} $*" >&2; log "ERROR: $*"; exit 1; }
info() { echo -e "${C}==>${N} $*"; }
warn() { echo -e "${Y}!${N} $*"; }
ok() { echo -e "${G}[OK]${N} $*"; }
preflight() {
[[ -n "${PREFIX:-}" && -d "$PREFIX" ]] || die "\$PREFIX inválido: no parece Termux"
[[ "$(uname -m)" == "aarch64" ]] || die "Solo aarch64 soportado (uname -m: $(uname -m))"
command -v npm &>/dev/null || die "npm no encontrado (pkg install nodejs)"
npm view "$PKG" version &>/dev/null || die "sin acceso a npm registry (¿sin red?)"
if [[ -f "$SETTINGS" ]] && ! grep -q '"DISABLE_AUTOUPDATER"' "$SETTINGS"; then
warn "settings.json sin DISABLE_AUTOUPDATER=1"
fi
mkdir -p "$BACKUP_ROOT"
}
version_gt() { [[ "$1" != "$2" && "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -1)" == "$1" ]]; }
get_current() { claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true; }
verify_node_compat() {
local ver="$1" files
info "Verificando v$ver es Node.js puro (busca cli.js, rechaza cli-wrapper.cjs)..."
files=$(npm pack "$PKG@$ver" --dry-run 2>&1 || true)
echo "$files" | grep -q 'cli-wrapper.cjs' && die "v$ver es binario nativo → incompatible"
echo "$files" | grep -q 'cli.js' || warn "v$ver: no se detectó cli.js explícito"
}
do_backup() {
[[ -d "$CLAUDE_DIR" ]] || return 0
local dest="$BACKUP_ROOT/backup-$(date +%s)-v$(get_current)"
info "Backup: $CLAUDE_DIR$dest"
cp -a "$CLAUDE_DIR" "$dest"
log "backup $dest"
ls -1dt "$BACKUP_ROOT"/backup-* 2>/dev/null | tail -n +4 | xargs -r rm -rf
}
do_rollback() {
local last
last=$(ls -1dt "$BACKUP_ROOT"/backup-* 2>/dev/null | head -1)
[[ -n "$last" ]] || die "sin backups en $BACKUP_ROOT"
info "Rollback: $last$CLAUDE_DIR"
[[ -d "$CLAUDE_DIR" ]] && { chmod -R u+w "$CLAUDE_DIR"; rm -rf "$CLAUDE_DIR"; }
cp -a "$last" "$CLAUDE_DIR"
chmod -R a-w "$CLAUDE_DIR"
ok "Restaurado a v$(get_current)"; log "rollback from $last"; exit 0
}
do_install() {
local ver="$1"
(( DRY_RUN )) && { info "[DRY-RUN] npm install -g $PKG@$ver (con backup + protección)"; exit 0; }
do_backup
info "Desprotegiendo (chmod u+w)..."
[[ -d "$CLAUDE_DIR" ]] && chmod -R u+w "$CLAUDE_DIR"
info "Instalando v$ver..."
npm install -g "$PKG@$ver"
info "Reprotegiendo (chmod a-w) contra auto-updater..."
chmod -R a-w "$CLAUDE_DIR"
local new; new=$(get_current)
[[ "$new" == "$ver" ]] || die "instalación inconsistente: esperaba $ver, obtuvo $new"
ok "Claude Code v$new instalado y protegido"; log "installed v$ver"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--check) CHECK_ONLY=1 ;;
--dry-run) DRY_RUN=1 ;;
--force) FORCE=1 ;;
--rollback) ROLLBACK=1 ;;
--version) shift; TARGET="${1:-}" ;;
-h|--help) usage ;;
*) die "opción desconocida: $1 (--help para ayuda)" ;;
esac; shift
done
preflight
(( ROLLBACK )) && do_rollback
CURRENT=$(get_current)
LATEST=$(npm view "$PKG" version 2>/dev/null)
echo -e "${C}Actual:${N} v${CURRENT:-<no instalado>}"
echo -e "${C}Última:${N} v${LATEST}"
echo -e "${C}Techo:${N} v${MAX_VERSION}"
if [[ -z "$TARGET" ]]; then
if version_gt "$LATEST" "$MAX_VERSION"; then
TARGET="$MAX_VERSION"
warn "Última v$LATEST > techo → objetivo v$MAX_VERSION"
else
TARGET="$LATEST"
fi
elif version_gt "$TARGET" "$MAX_VERSION"; then
die "v$TARGET excede techo v$MAX_VERSION (ver --help para Path A)"
fi
echo -e "${C}Objetivo:${N} v${TARGET}"
(( CHECK_ONLY )) && exit 0
if [[ "$CURRENT" == "$TARGET" ]] && (( ! FORCE )); then
ok "Ya en v$TARGET (usar --force para reinstalar)"; exit 0
fi
verify_node_compat "$TARGET"
do_install "$TARGET"