refactor(claude-update): añadir preflight, backup, rollback y dry-run
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) <noreply@anthropic.com>
This commit is contained in:
parent
c6d54ea5c9
commit
858e1772a6
|
|
@ -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 <<EOF
|
||||
Uso: claude-update [--check|--dry-run|--force|--rollback|--version X.Y.Z|-h]
|
||||
|
||||
# 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"
|
||||
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
|
||||
fi
|
||||
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} $*"; }
|
||||
|
||||
if version_gt "$LATEST" "$MAX_VERSION"; then
|
||||
echo -e "${YELLOW}Latest (v$LATEST) exceeds ceiling.${NC}"
|
||||
echo "Installing ceiling version v$MAX_VERSION instead..."
|
||||
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"
|
||||
else
|
||||
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
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# 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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue