From 858e1772a6757480144c8e4b3c893bd6123d6d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Eduardo=20Garc=C3=ADa=20M=C3=A1rquez?= Date: Tue, 28 Jul 2026 02:04:20 -0500 Subject: [PATCH] =?UTF-8?q?refactor(claude-update):=20a=C3=B1adir=20prefli?= =?UTF-8?q?ght,=20backup,=20rollback=20y=20dry-run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reemplaza el updater previo por una versión endurecida que: - Preflight duro: valida $PREFIX, arch aarch64, npm disponible, registry accesible, y avisa si settings.json carece de DISABLE_AUTOUPDATER=1. - Verificación real de compatibilidad Termux vía npm pack --dry-run, distinguiendo cli.js (Node.js puro, ok) de cli-wrapper.cjs (binario nativo SEA, rechazado). - Backup automático previo a cualquier cambio en ~/.cache/claude-updates/, con retención de los 3 últimos. - Nuevo flag --rollback que restaura el backup más reciente. - Nuevos flags --dry-run, --check, --version X.Y.Z y --force para operación segura y previsible. - Smoke test post-install (claude --version debe coincidir con target). - Log histórico en ~/.cache/claude-updates/history.log. - Documenta en --help la vía glibc-runner (github.com/ferrumclaudepilgrim/claude-code-android) como camino arriesgado para superar el techo v2.1.112. Cambios menores: shebang absoluto a $PREFIX/bin/bash (más robusto en Termux cuando LD_PRELOAD queda unset), constantes readonly, mensajes en español. Co-Authored-By: Claude Opus 4.7 (1M context) --- bin/claude-update | 179 +++++++++++++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 57 deletions(-) diff --git a/bin/claude-update b/bin/claude-update index e6c18f7..7a93dae 100755 --- a/bin/claude-update +++ b/bin/claude-update @@ -1,68 +1,133 @@ -#!/usr/bin/env bash -# -# claude-update: Safe updater for Claude Code on Termux -# Respects version ceiling (v2.1.112 = last pure Node.js release) -# +#!/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 -e +set -euo pipefail -RED="\033[0;31m" -GREEN="\033[0;32m" -YELLOW="\033[1;33m" -NC="\033[0m" +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" -MAX_VERSION="2.1.112" +CHECK_ONLY=0; DRY_RUN=0; FORCE=0; ROLLBACK=0; TARGET="" -CURRENT=$(claude --version 2>/dev/null | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" | head -1) -echo "Current: v${CURRENT:-not installed}" +usage() { + cat </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" +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 } -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 +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:-}" +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 -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" +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 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)" +verify_node_compat "$TARGET" +do_install "$TARGET"