feat(uninstall): interactive 3-mode prompt + safe-by-default — claude/codex/gemini/qwen
After user lost 6 months of work to `rm -rf ~/.claude`, ALL 4 uninstall
scripts (claude, codex, gemini, qwen) now require explicit choice:
Modes:
1) safe (default) — remove binary + env + settings.json
KEEP projects, history, commands(skills),
plans, file-history, plugins
2) settings-only — clear settings.json only, keep binary + data
3) full — wipe everything (tar backup first, requires
typing 'WIPE' to confirm)
4) cancel — exit, do nothing
Default flow:
- Interactive prompt with PREVIEW of user data (size, project count,
command count, history line count) before any destructive op
- Cancel option always available
- Each file backed up to *.uninstall.bak.<TS> before removal
- /etc/environment + .bashrc + /etc/profile.d backed up before sed
Non-interactive (CI / scripts):
UCLAUDE_MODE=safe sudo bash uclaude_uninstall.sh
UCLAUDE_MODE=full sudo bash uclaude_uninstall.sh # creates tar backup
UCLAUDE_YES=1 # skip prompt, default to safe mode
NEVER again should an uninstaller silently destroy user data.
Per-tool env vars: UCLAUDE_MODE / UCODEX_MODE / UGEMINI_MODE / UQWEN_MODE.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Claude Code — Uninstaller
|
||||
# Removes Claude Code CLI, settings, env vars, and npm registry config.
|
||||
# Claude Code — Interactive Uninstaller
|
||||
#
|
||||
# Usage: sudo bash uclaude_uninstall.sh
|
||||
# 3 modes:
|
||||
# 1) safe (DEFAULT) — remove only Claude Code binary + env vars + settings.json
|
||||
# PRESERVE all user data: projects, history, commands,
|
||||
# plans, file-history, plugins.
|
||||
# 2) full — wipe ~/.claude/ COMPLETELY (with tar backup first).
|
||||
# 3) settings-only — just clear settings.json (keep binary + everything else).
|
||||
#
|
||||
# Modes:
|
||||
# sudo bash uclaude_uninstall.sh # interactive prompt
|
||||
# UCLAUDE_MODE=safe sudo bash uclaude_uninstall.sh
|
||||
# UCLAUDE_MODE=full sudo bash uclaude_uninstall.sh
|
||||
# UCLAUDE_MODE=settings-only sudo bash uclaude_uninstall.sh
|
||||
# UCLAUDE_MODE=safe UCLAUDE_YES=1 sudo bash uclaude_uninstall.sh # no prompts
|
||||
#
|
||||
# Designed after a user lost 6 months of work to a destructive
|
||||
# `rm -rf ~/.claude` default. NEVER again.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN="\033[92m"
|
||||
RED="\033[91m"
|
||||
CYAN="\033[96m"
|
||||
YELLOW="\033[93m"
|
||||
BOLD="\033[1m"
|
||||
@@ -13,10 +29,10 @@ RESET="\033[0m"
|
||||
|
||||
log() { echo -e "${GREEN}[+]${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}[~]${RESET} $*"; }
|
||||
err() { echo -e "${RED}[!]${RESET} $*" >&2; }
|
||||
info() { echo -e "${CYAN}[i]${RESET} $*"; }
|
||||
|
||||
# Cross-platform sed -i (macOS BSD sed requires `-i ''`, GNU sed uses `-i`).
|
||||
# Use `sedi` instead of `sed -i` everywhere in this script.
|
||||
# Cross-platform sed -i
|
||||
sedi() {
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
sed -i '' "$@"
|
||||
@@ -25,94 +41,199 @@ sedi() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------- Banner ----------
|
||||
echo -e "${BOLD}"
|
||||
echo " +--------------------------------------+"
|
||||
echo " | Claude Code — Uninstaller |"
|
||||
echo " +--------------------------------------+"
|
||||
echo -e "${RESET}"
|
||||
|
||||
# ---- Uninstall npm package ----
|
||||
# ---------- Mode selection ----------
|
||||
|
||||
if npm list -g @anthropic-ai/claude-code &>/dev/null 2>&1; then
|
||||
info "Removing @anthropic-ai/claude-code..."
|
||||
MODE="${UCLAUDE_MODE:-}"
|
||||
ASSUME_YES="${UCLAUDE_YES:-0}"
|
||||
|
||||
# Inventory user data we'll touch — show user what's at stake
|
||||
inventory_user_dirs() {
|
||||
local total_dirs=0 total_size_kb=0
|
||||
for user_home in /root /home/*; do
|
||||
local d="$user_home/.claude"
|
||||
[ -d "$d" ] || continue
|
||||
local size_kb
|
||||
size_kb=$(du -sk "$d" 2>/dev/null | awk '{print $1}')
|
||||
total_dirs=$((total_dirs + 1))
|
||||
total_size_kb=$((total_size_kb + size_kb))
|
||||
local proj_count
|
||||
proj_count=$(find "$d/projects" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
local cmd_count
|
||||
cmd_count=$(find "$d/commands" -mindepth 1 -maxdepth 1 -name '*.md' 2>/dev/null | wc -l)
|
||||
local plans_count
|
||||
plans_count=$(find "$d/plans" -mindepth 1 -maxdepth 1 2>/dev/null | wc -l)
|
||||
local hist_lines=0
|
||||
[ -f "$d/history.jsonl" ] && hist_lines=$(wc -l <"$d/history.jsonl" 2>/dev/null || echo 0)
|
||||
echo " $d ($((size_kb / 1024)) MB)"
|
||||
echo " projects: $proj_count commands(skills): $cmd_count plans: $plans_count history lines: $hist_lines"
|
||||
done
|
||||
echo
|
||||
echo " Total: $total_dirs user dir(s), $((total_size_kb / 1024)) MB"
|
||||
}
|
||||
|
||||
prompt_mode() {
|
||||
echo -e "${BOLD}What to uninstall?${RESET}"
|
||||
echo
|
||||
echo -e " ${GREEN}1) safe ${RESET}— remove Claude Code binary + env vars + settings.json"
|
||||
echo -e " ${BOLD}KEEP user data${RESET}: projects, history, commands(skills),"
|
||||
echo " plans, file-history, plugins."
|
||||
echo " (recommended — reversible)"
|
||||
echo
|
||||
echo -e " ${YELLOW}2) settings-only ${RESET}— just clear settings.json env block."
|
||||
echo " Binary stays, all user data stays."
|
||||
echo " (use to fix bad env vars without removing claude)"
|
||||
echo
|
||||
echo -e " ${RED}3) full ${RESET}— wipe entire ~/.claude/ directory + binary + env vars."
|
||||
echo -e " ${BOLD}DESTROYS all user data${RESET} (tar backup created first)."
|
||||
echo " (use only when you're SURE; backup .tar.gz lives in \$HOME)"
|
||||
echo
|
||||
echo " 4) cancel — exit, do nothing"
|
||||
echo
|
||||
echo -e "${BOLD}Your data right now:${RESET}"
|
||||
inventory_user_dirs
|
||||
echo
|
||||
while true; do
|
||||
read -r -p "$(echo -e "${BOLD}Choose [1/2/3/4] (default 1=safe):${RESET} ")" choice
|
||||
case "${choice:-1}" in
|
||||
1|safe) MODE="safe"; break ;;
|
||||
2|settings-only) MODE="settings-only"; break ;;
|
||||
3|full) MODE="full"
|
||||
# Triple-confirm full wipe
|
||||
echo
|
||||
warn "FULL mode will DELETE all conversations, custom skills, plans, history."
|
||||
warn "A tar backup will be created in each user's home directory before delete."
|
||||
read -r -p "$(echo -e "${RED}${BOLD}Type 'WIPE' to confirm full wipe:${RESET} ")" confirm
|
||||
[ "$confirm" = "WIPE" ] && break
|
||||
err "Confirmation failed — switching back to mode select"
|
||||
;;
|
||||
4|cancel|q|exit) info "Cancelled, no changes made."; exit 0 ;;
|
||||
*) echo "Invalid choice — pick 1, 2, 3 or 4." ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Decide mode: env override → CLI arg → interactive prompt
|
||||
if [ -n "$MODE" ]; then
|
||||
case "$MODE" in
|
||||
safe|settings-only|full) info "Mode (from UCLAUDE_MODE env): $MODE" ;;
|
||||
*) err "Invalid UCLAUDE_MODE='$MODE' (valid: safe, settings-only, full)"; exit 2 ;;
|
||||
esac
|
||||
elif [ "$ASSUME_YES" = "1" ]; then
|
||||
MODE="safe"
|
||||
info "UCLAUDE_YES=1 + no UCLAUDE_MODE — defaulting to 'safe'"
|
||||
else
|
||||
prompt_mode
|
||||
fi
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||
|
||||
echo
|
||||
info "Mode: ${BOLD}${MODE}${RESET}"
|
||||
echo
|
||||
|
||||
# ---------- npm package removal (all modes except settings-only) ----------
|
||||
|
||||
if [ "$MODE" != "settings-only" ]; then
|
||||
if npm list -g @anthropic-ai/claude-code &>/dev/null 2>&1 || command -v claude >/dev/null 2>&1; then
|
||||
info "Removing @anthropic-ai/claude-code npm package..."
|
||||
npm uninstall -g @anthropic-ai/claude-code 2>/dev/null || true
|
||||
log "npm package removed"
|
||||
# Cleanup symlink if npm didn't (system installs)
|
||||
for binpath in /usr/bin/claude /usr/local/bin/claude /opt/homebrew/bin/claude; do
|
||||
if [ -L "$binpath" ] || [ -f "$binpath" ]; then
|
||||
# Verify it points to OUR install before removing
|
||||
if [ -L "$binpath" ] && readlink "$binpath" | grep -q "claude-code"; then
|
||||
rm -f "$binpath"
|
||||
log "Removed symlink: $binpath"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
log "Claude Code binary removed"
|
||||
else
|
||||
warn "Claude Code not found in npm global packages"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- Remove settings ----
|
||||
# DEFAULT: PRESERVE user data (projects, history, plans, commands, plugins,
|
||||
# file-history). Only delete settings.json + cache/. User data took months
|
||||
# to accumulate — never destroy without explicit opt-in.
|
||||
#
|
||||
# Set UCLAUDE_PURGE_USER_DATA=1 to wipe the entire ~/.claude/ directory
|
||||
# (the old destructive behavior).
|
||||
|
||||
PURGE_USER_DATA="${UCLAUDE_PURGE_USER_DATA:-0}"
|
||||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||
# ---------- ~/.claude/ handling per mode ----------
|
||||
|
||||
for user_home in /root /home/*; do
|
||||
CLAUDE_DIR="$user_home/.claude"
|
||||
if [ -d "$CLAUDE_DIR" ]; then
|
||||
if [ "$PURGE_USER_DATA" = "1" ]; then
|
||||
# Explicit opt-in: tar backup first, then wipe
|
||||
BACKUP_TAR="$user_home/.claude.uninstall-backup.${TIMESTAMP}.tar.gz"
|
||||
info "PURGE mode — backing up to $BACKUP_TAR before delete..."
|
||||
tar -czf "$BACKUP_TAR" -C "$user_home" .claude 2>/dev/null && \
|
||||
log "Backup saved: $BACKUP_TAR" || \
|
||||
warn "Backup failed — aborting purge to preserve data"
|
||||
if [ -f "$BACKUP_TAR" ]; then
|
||||
rm -rf "$CLAUDE_DIR"
|
||||
log "Removed $CLAUDE_DIR (restore: tar -xzf $BACKUP_TAR -C $user_home)"
|
||||
fi
|
||||
else
|
||||
# Default: preserve user data, only remove patcher-managed files
|
||||
[ -d "$CLAUDE_DIR" ] || continue
|
||||
|
||||
case "$MODE" in
|
||||
safe)
|
||||
# Remove ONLY patcher-managed files. Preserve all user data.
|
||||
info "Cleaning $CLAUDE_DIR (preserving user data)..."
|
||||
for f in settings.json settings.local.json .patcher.config.cache.json; do
|
||||
if [ -f "$CLAUDE_DIR/$f" ]; then
|
||||
# Backup settings.json before delete (env vars are
|
||||
# easy to recreate, but user may have customized)
|
||||
cp "$CLAUDE_DIR/$f" "$CLAUDE_DIR/${f}.uninstall.bak.${TIMESTAMP}"
|
||||
cp -p "$CLAUDE_DIR/$f" "$CLAUDE_DIR/${f}.uninstall.bak.${TIMESTAMP}"
|
||||
rm -f "$CLAUDE_DIR/$f"
|
||||
log "Removed $CLAUDE_DIR/$f (backup: ${f}.uninstall.bak.${TIMESTAMP})"
|
||||
fi
|
||||
done
|
||||
# Optionally clean cache/ (safe — Claude regenerates on next run)
|
||||
# cache/ is regenerated by Claude — safe to clear
|
||||
if [ -d "$CLAUDE_DIR/cache" ]; then
|
||||
rm -rf "$CLAUDE_DIR/cache"
|
||||
log "Removed $CLAUDE_DIR/cache (will regenerate)"
|
||||
log "Cleared $CLAUDE_DIR/cache (will regenerate on next run)"
|
||||
fi
|
||||
log "PRESERVED in $CLAUDE_DIR: projects/, history.jsonl, commands/, plans/, file-history/, plugins/, etc."
|
||||
log "PRESERVED: projects/ history.jsonl commands/ plans/ file-history/ plugins/"
|
||||
;;
|
||||
|
||||
settings-only)
|
||||
# Even softer — only settings.json, nothing else
|
||||
for f in settings.json settings.local.json; do
|
||||
if [ -f "$CLAUDE_DIR/$f" ]; then
|
||||
cp -p "$CLAUDE_DIR/$f" "$CLAUDE_DIR/${f}.uninstall.bak.${TIMESTAMP}"
|
||||
rm -f "$CLAUDE_DIR/$f"
|
||||
log "Removed $CLAUDE_DIR/$f (backup: ${f}.uninstall.bak.${TIMESTAMP})"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
|
||||
full)
|
||||
# WIPE — but ALWAYS backup first.
|
||||
BACKUP_TAR="$user_home/.claude.uninstall-backup.${TIMESTAMP}.tar.gz"
|
||||
info "Creating backup: $BACKUP_TAR ..."
|
||||
if tar -czf "$BACKUP_TAR" -C "$user_home" .claude 2>/dev/null; then
|
||||
local_size=$(du -h "$BACKUP_TAR" 2>/dev/null | awk '{print $1}')
|
||||
log "Backup saved: $BACKUP_TAR ($local_size)"
|
||||
rm -rf "$CLAUDE_DIR"
|
||||
log "Removed $CLAUDE_DIR"
|
||||
log " Restore anytime: tar -xzf $BACKUP_TAR -C $user_home"
|
||||
else
|
||||
err "Backup FAILED — refusing to delete $CLAUDE_DIR (data preserved)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# ~/.claude.json (onboarding state) — backup-and-remove in full+safe modes
|
||||
CLAUDE_JSON="$user_home/.claude.json"
|
||||
if [ -f "$CLAUDE_JSON" ]; then
|
||||
# Backup before delete (.claude.json holds onboarding + recent
|
||||
# session refs — useful to keep for restore).
|
||||
cp "$CLAUDE_JSON" "${CLAUDE_JSON}.uninstall.bak.${TIMESTAMP}"
|
||||
if [ -f "$CLAUDE_JSON" ] && [ "$MODE" != "settings-only" ]; then
|
||||
cp -p "$CLAUDE_JSON" "${CLAUDE_JSON}.uninstall.bak.${TIMESTAMP}"
|
||||
rm -f "$CLAUDE_JSON"
|
||||
log "Removed $CLAUDE_JSON (backup: ${CLAUDE_JSON}.uninstall.bak.${TIMESTAMP})"
|
||||
log "Removed $CLAUDE_JSON (backup: $(basename "$CLAUDE_JSON").uninstall.bak.${TIMESTAMP})"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$PURGE_USER_DATA" != "1" ]; then
|
||||
echo
|
||||
info "User data PRESERVED. To wipe everything:"
|
||||
info " UCLAUDE_PURGE_USER_DATA=1 sudo bash uclaude_uninstall.sh"
|
||||
info "(That mode creates a tar backup first, even on purge)"
|
||||
echo
|
||||
fi
|
||||
|
||||
# ---- Remove env vars from shell rc files ----
|
||||
# ---------- env vars + shell rc files (skip in settings-only mode) ----------
|
||||
|
||||
if [ "$MODE" != "settings-only" ]; then
|
||||
for user_home in /root /home/*; do
|
||||
for rc_file in "$user_home/.bashrc" "$user_home/.zshrc"; do
|
||||
if [ -f "$rc_file" ] && grep -q 'ANTHROPIC_API_KEY\|CLAUDE_CODE\|Claude Code\|ANTHROPIC_BASE_URL' "$rc_file" 2>/dev/null; then
|
||||
info "Cleaning env vars from $rc_file..."
|
||||
if [ -f "$rc_file" ] && grep -q 'ANTHROPIC_API_KEY\|CLAUDE_CODE\|Claude Code\|ANTHROPIC_BASE_URL\|ANTHROPIC_AUTH_TOKEN' "$rc_file" 2>/dev/null; then
|
||||
# Backup rc file before mutating (sed -i destructive)
|
||||
cp -p "$rc_file" "${rc_file}.uninstall.bak.${TIMESTAMP}"
|
||||
info "Cleaning env vars from $rc_file (backup: $(basename "$rc_file").uninstall.bak.${TIMESTAMP})..."
|
||||
sedi '/# Claude Code/d' "$rc_file"
|
||||
sedi '/# UnlimitedCoding.*[Cc]laude/d' "$rc_file"
|
||||
sedi '/ANTHROPIC_API_KEY/d' "$rc_file"
|
||||
sedi '/ANTHROPIC_AUTH_TOKEN/d' "$rc_file"
|
||||
sedi '/ANTHROPIC_BASE_URL/d' "$rc_file"
|
||||
sedi '/CLAUDE_CODE/d' "$rc_file"
|
||||
log "Cleaned $rc_file"
|
||||
@@ -120,31 +241,46 @@ for user_home in /root /home/*; do
|
||||
done
|
||||
done
|
||||
|
||||
# ---- Remove /etc/profile.d script ----
|
||||
|
||||
for f in /etc/profile.d/claude-code.sh /etc/profile.d/claude_code.sh; do
|
||||
if [ -f "$f" ]; then
|
||||
cp -p "$f" "${f}.uninstall.bak.${TIMESTAMP}" 2>/dev/null
|
||||
rm -f "$f"
|
||||
log "Removed $f"
|
||||
fi
|
||||
done
|
||||
|
||||
# ---- Remove env vars from /etc/environment ----
|
||||
|
||||
if [ -f "/etc/environment" ] && grep -q 'ANTHROPIC_API_KEY\|ANTHROPIC_BASE_URL\|CLAUDE_CODE' /etc/environment 2>/dev/null; then
|
||||
info "Cleaning /etc/environment..."
|
||||
if [ -f "/etc/environment" ] && grep -q 'ANTHROPIC_API_KEY\|ANTHROPIC_AUTH_TOKEN\|ANTHROPIC_BASE_URL\|CLAUDE_CODE' /etc/environment 2>/dev/null; then
|
||||
cp -p /etc/environment "/etc/environment.uninstall.bak.${TIMESTAMP}"
|
||||
info "Cleaning /etc/environment (backup: /etc/environment.uninstall.bak.${TIMESTAMP})..."
|
||||
sedi '/ANTHROPIC_API_KEY/d' /etc/environment
|
||||
sedi '/ANTHROPIC_AUTH_TOKEN/d' /etc/environment
|
||||
sedi '/ANTHROPIC_BASE_URL/d' /etc/environment
|
||||
sedi '/CLAUDE_CODE/d' /etc/environment
|
||||
log "Cleaned /etc/environment"
|
||||
fi
|
||||
|
||||
# ---- Remove npm registry config ----
|
||||
|
||||
info "Removing npm registry config..."
|
||||
npm config delete @anthropic-ai:registry 2>/dev/null || true
|
||||
log "npm registry config removed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}${BOLD} Claude Code fully uninstalled!${RESET}"
|
||||
echo ""
|
||||
echo
|
||||
case "$MODE" in
|
||||
safe)
|
||||
echo -e "${GREEN}${BOLD} Done — Claude Code removed, your data preserved.${RESET}"
|
||||
echo
|
||||
echo " Reinstall keeps everything: just re-run uclaude_install.sh"
|
||||
echo " Backups (settings only): ~/.claude/*.uninstall.bak.${TIMESTAMP}"
|
||||
;;
|
||||
settings-only)
|
||||
echo -e "${GREEN}${BOLD} Settings cleared — binary + user data untouched.${RESET}"
|
||||
echo
|
||||
echo " Re-apply patches: re-run uclaude_install.sh or uclaude_updater.py"
|
||||
;;
|
||||
full)
|
||||
echo -e "${YELLOW}${BOLD} Full wipe complete.${RESET}"
|
||||
echo
|
||||
echo " Restore backup: tar -xzf ~/.claude.uninstall-backup.${TIMESTAMP}.tar.gz -C ~"
|
||||
;;
|
||||
esac
|
||||
echo
|
||||
|
||||
@@ -5,6 +5,57 @@
|
||||
# Usage: sudo bash ucodex_uninstall.sh
|
||||
set -euo pipefail
|
||||
|
||||
# ---- Interactive mode select (post user-data-loss incident) ----
|
||||
# Modes:
|
||||
# safe (default) — remove binary + settings, KEEP user data
|
||||
# settings-only — clear settings only, keep binary + user data
|
||||
# full — wipe everything (with tar backup first)
|
||||
|
||||
MODE="${UCODEX_MODE:-}"
|
||||
ASSUME_YES="${UCLAUDE_YES:-0}"
|
||||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||
|
||||
inventory_user_dirs_codex() {
|
||||
for user_home in /root /home/*; do
|
||||
local d="$user_home/.codex"
|
||||
[ -d "$d" ] || continue
|
||||
local size_kb
|
||||
size_kb=$(du -sk "$d" 2>/dev/null | awk '{print $1}')
|
||||
echo " $d ($((size_kb / 1024)) MB)"
|
||||
done
|
||||
}
|
||||
|
||||
if [ -z "$MODE" ] && [ "$ASSUME_YES" != "1" ]; then
|
||||
echo
|
||||
echo "What to uninstall Codex CLI?"
|
||||
echo " 1) safe — remove binary + settings, KEEP user data (recommended)"
|
||||
echo " 2) settings-only — only clear settings, keep binary + user data"
|
||||
echo " 3) full — wipe everything (tar backup created first)"
|
||||
echo " 4) cancel"
|
||||
echo
|
||||
echo "Your data right now:"
|
||||
inventory_user_dirs_codex
|
||||
echo
|
||||
while true; do
|
||||
read -r -p "Choose [1/2/3/4] (default 1=safe): " choice
|
||||
case "${choice:-1}" in
|
||||
1|safe) MODE="safe"; break ;;
|
||||
2|settings-only) MODE="settings-only"; break ;;
|
||||
3|full)
|
||||
echo "WARNING: full mode DESTROYS all Codex CLI user data."
|
||||
read -r -p "Type 'WIPE' to confirm: " confirm
|
||||
[ "$confirm" = "WIPE" ] && MODE="full" && break
|
||||
echo "Confirmation failed, pick again." ;;
|
||||
4|cancel|q|exit) echo "Cancelled."; exit 0 ;;
|
||||
*) echo "Invalid — pick 1/2/3/4." ;;
|
||||
esac
|
||||
done
|
||||
elif [ -z "$MODE" ]; then
|
||||
MODE="safe"
|
||||
fi
|
||||
|
||||
echo "Mode: $MODE"
|
||||
|
||||
OS="$(uname -s)"
|
||||
IS_MACOS=false
|
||||
[ "$OS" = "Darwin" ] && IS_MACOS=true
|
||||
@@ -62,12 +113,40 @@ fi
|
||||
|
||||
# ---- Remove config ----
|
||||
|
||||
for user_home in "${_home_dirs[@]}"; do
|
||||
CODEX_DIR="$user_home/.codex"
|
||||
for user_home in "${_home_dirs[@]}"; do CODEX_DIR="$user_home/.codex"
|
||||
if [ -d "$CODEX_DIR" ]; then
|
||||
info "Removing $CODEX_DIR..."
|
||||
rm -rf "$CODEX_DIR"
|
||||
log "Removed $CODEX_DIR"
|
||||
# PRESERVE user data by default. See uclaude_uninstall.sh history.
|
||||
case "${MODE:-safe}" in
|
||||
safe)
|
||||
# Remove only managed config files, keep everything else
|
||||
for f in settings.json config.toml config.yaml; do
|
||||
if [ -f "$user_home/.codex/$f" ]; then
|
||||
cp -p "$user_home/.codex/$f" "$user_home/.codex/$f.uninstall.bak.$TIMESTAMP"
|
||||
rm -f "$user_home/.codex/$f"
|
||||
echo " Removed $user_home/.codex/$f (backup: $f.uninstall.bak.$TIMESTAMP)"
|
||||
fi
|
||||
done
|
||||
echo " PRESERVED user data in $user_home/.codex"
|
||||
;;
|
||||
settings-only)
|
||||
for f in settings.json config.toml config.yaml; do
|
||||
if [ -f "$user_home/.codex/$f" ]; then
|
||||
cp -p "$user_home/.codex/$f" "$user_home/.codex/$f.uninstall.bak.$TIMESTAMP"
|
||||
rm -f "$user_home/.codex/$f"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
full)
|
||||
BAK="$user_home/.codex.uninstall-backup.$TIMESTAMP.tar.gz"
|
||||
if tar -czf "$BAK" -C "$user_home" .codex 2>/dev/null; then
|
||||
echo " Backup: $BAK"
|
||||
rm -rf "$user_home/.codex"
|
||||
echo " Removed $user_home/.codex (restore: tar -xzf $BAK -C $user_home)"
|
||||
else
|
||||
echo " ERROR: backup failed — refusing to delete $user_home/.codex"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
@@ -5,6 +5,57 @@
|
||||
# Usage: sudo bash ugemini_uninstall.sh
|
||||
set -euo pipefail
|
||||
|
||||
# ---- Interactive mode select (post user-data-loss incident) ----
|
||||
# Modes:
|
||||
# safe (default) — remove binary + settings, KEEP user data
|
||||
# settings-only — clear settings only, keep binary + user data
|
||||
# full — wipe everything (with tar backup first)
|
||||
|
||||
MODE="${UGEMINI_MODE:-}"
|
||||
ASSUME_YES="${UCLAUDE_YES:-0}"
|
||||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||
|
||||
inventory_user_dirs_gemini() {
|
||||
for user_home in /root /home/*; do
|
||||
local d="$user_home/.gemini"
|
||||
[ -d "$d" ] || continue
|
||||
local size_kb
|
||||
size_kb=$(du -sk "$d" 2>/dev/null | awk '{print $1}')
|
||||
echo " $d ($((size_kb / 1024)) MB)"
|
||||
done
|
||||
}
|
||||
|
||||
if [ -z "$MODE" ] && [ "$ASSUME_YES" != "1" ]; then
|
||||
echo
|
||||
echo "What to uninstall Gemini CLI?"
|
||||
echo " 1) safe — remove binary + settings, KEEP user data (recommended)"
|
||||
echo " 2) settings-only — only clear settings, keep binary + user data"
|
||||
echo " 3) full — wipe everything (tar backup created first)"
|
||||
echo " 4) cancel"
|
||||
echo
|
||||
echo "Your data right now:"
|
||||
inventory_user_dirs_gemini
|
||||
echo
|
||||
while true; do
|
||||
read -r -p "Choose [1/2/3/4] (default 1=safe): " choice
|
||||
case "${choice:-1}" in
|
||||
1|safe) MODE="safe"; break ;;
|
||||
2|settings-only) MODE="settings-only"; break ;;
|
||||
3|full)
|
||||
echo "WARNING: full mode DESTROYS all Gemini CLI user data."
|
||||
read -r -p "Type 'WIPE' to confirm: " confirm
|
||||
[ "$confirm" = "WIPE" ] && MODE="full" && break
|
||||
echo "Confirmation failed, pick again." ;;
|
||||
4|cancel|q|exit) echo "Cancelled."; exit 0 ;;
|
||||
*) echo "Invalid — pick 1/2/3/4." ;;
|
||||
esac
|
||||
done
|
||||
elif [ -z "$MODE" ]; then
|
||||
MODE="safe"
|
||||
fi
|
||||
|
||||
echo "Mode: $MODE"
|
||||
|
||||
GREEN="\033[92m"
|
||||
RED="\033[91m"
|
||||
CYAN="\033[96m"
|
||||
@@ -52,12 +103,40 @@ fi
|
||||
|
||||
# ---- Remove settings ----
|
||||
|
||||
for user_home in /root $SCAN_DIRS/*; do
|
||||
GEMINI_DIR="$user_home/.gemini"
|
||||
for user_home in /root $SCAN_DIRS/*; do GEMINI_DIR="$user_home/.gemini"
|
||||
if [ -d "$GEMINI_DIR" ]; then
|
||||
info "Removing $GEMINI_DIR..."
|
||||
rm -rf "$GEMINI_DIR"
|
||||
log "Removed $GEMINI_DIR"
|
||||
# PRESERVE user data by default. See uclaude_uninstall.sh history.
|
||||
case "${MODE:-safe}" in
|
||||
safe)
|
||||
# Remove only managed config files, keep everything else
|
||||
for f in settings.json config.toml config.yaml; do
|
||||
if [ -f "$user_home/.gemini/$f" ]; then
|
||||
cp -p "$user_home/.gemini/$f" "$user_home/.gemini/$f.uninstall.bak.$TIMESTAMP"
|
||||
rm -f "$user_home/.gemini/$f"
|
||||
echo " Removed $user_home/.gemini/$f (backup: $f.uninstall.bak.$TIMESTAMP)"
|
||||
fi
|
||||
done
|
||||
echo " PRESERVED user data in $user_home/.gemini"
|
||||
;;
|
||||
settings-only)
|
||||
for f in settings.json config.toml config.yaml; do
|
||||
if [ -f "$user_home/.gemini/$f" ]; then
|
||||
cp -p "$user_home/.gemini/$f" "$user_home/.gemini/$f.uninstall.bak.$TIMESTAMP"
|
||||
rm -f "$user_home/.gemini/$f"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
full)
|
||||
BAK="$user_home/.gemini.uninstall-backup.$TIMESTAMP.tar.gz"
|
||||
if tar -czf "$BAK" -C "$user_home" .gemini 2>/dev/null; then
|
||||
echo " Backup: $BAK"
|
||||
rm -rf "$user_home/.gemini"
|
||||
echo " Removed $user_home/.gemini (restore: tar -xzf $BAK -C $user_home)"
|
||||
else
|
||||
echo " ERROR: backup failed — refusing to delete $user_home/.gemini"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
@@ -5,6 +5,57 @@
|
||||
# Usage: sudo bash uqwen_uninstall.sh
|
||||
set -euo pipefail
|
||||
|
||||
# ---- Interactive mode select (post user-data-loss incident) ----
|
||||
# Modes:
|
||||
# safe (default) — remove binary + settings, KEEP user data
|
||||
# settings-only — clear settings only, keep binary + user data
|
||||
# full — wipe everything (with tar backup first)
|
||||
|
||||
MODE="${UQWEN_MODE:-}"
|
||||
ASSUME_YES="${UCLAUDE_YES:-0}"
|
||||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||
|
||||
inventory_user_dirs_qwen() {
|
||||
for user_home in /root /home/*; do
|
||||
local d="$user_home/.qwen"
|
||||
[ -d "$d" ] || continue
|
||||
local size_kb
|
||||
size_kb=$(du -sk "$d" 2>/dev/null | awk '{print $1}')
|
||||
echo " $d ($((size_kb / 1024)) MB)"
|
||||
done
|
||||
}
|
||||
|
||||
if [ -z "$MODE" ] && [ "$ASSUME_YES" != "1" ]; then
|
||||
echo
|
||||
echo "What to uninstall Qwen CLI?"
|
||||
echo " 1) safe — remove binary + settings, KEEP user data (recommended)"
|
||||
echo " 2) settings-only — only clear settings, keep binary + user data"
|
||||
echo " 3) full — wipe everything (tar backup created first)"
|
||||
echo " 4) cancel"
|
||||
echo
|
||||
echo "Your data right now:"
|
||||
inventory_user_dirs_qwen
|
||||
echo
|
||||
while true; do
|
||||
read -r -p "Choose [1/2/3/4] (default 1=safe): " choice
|
||||
case "${choice:-1}" in
|
||||
1|safe) MODE="safe"; break ;;
|
||||
2|settings-only) MODE="settings-only"; break ;;
|
||||
3|full)
|
||||
echo "WARNING: full mode DESTROYS all Qwen CLI user data."
|
||||
read -r -p "Type 'WIPE' to confirm: " confirm
|
||||
[ "$confirm" = "WIPE" ] && MODE="full" && break
|
||||
echo "Confirmation failed, pick again." ;;
|
||||
4|cancel|q|exit) echo "Cancelled."; exit 0 ;;
|
||||
*) echo "Invalid — pick 1/2/3/4." ;;
|
||||
esac
|
||||
done
|
||||
elif [ -z "$MODE" ]; then
|
||||
MODE="safe"
|
||||
fi
|
||||
|
||||
echo "Mode: $MODE"
|
||||
|
||||
GREEN="\033[92m"
|
||||
RED="\033[91m"
|
||||
CYAN="\033[96m"
|
||||
@@ -52,12 +103,40 @@ fi
|
||||
|
||||
# ---- Remove settings ----
|
||||
|
||||
for user_home in /root $SCAN_DIRS/*; do
|
||||
QWEN_DIR="$user_home/.qwen"
|
||||
for user_home in /root $SCAN_DIRS/*; do QWEN_DIR="$user_home/.qwen"
|
||||
if [ -d "$QWEN_DIR" ]; then
|
||||
info "Removing $QWEN_DIR..."
|
||||
rm -rf "$QWEN_DIR"
|
||||
log "Removed $QWEN_DIR"
|
||||
# PRESERVE user data by default. See uclaude_uninstall.sh history.
|
||||
case "${MODE:-safe}" in
|
||||
safe)
|
||||
# Remove only managed config files, keep everything else
|
||||
for f in settings.json config.toml config.yaml; do
|
||||
if [ -f "$user_home/.qwen/$f" ]; then
|
||||
cp -p "$user_home/.qwen/$f" "$user_home/.qwen/$f.uninstall.bak.$TIMESTAMP"
|
||||
rm -f "$user_home/.qwen/$f"
|
||||
echo " Removed $user_home/.qwen/$f (backup: $f.uninstall.bak.$TIMESTAMP)"
|
||||
fi
|
||||
done
|
||||
echo " PRESERVED user data in $user_home/.qwen"
|
||||
;;
|
||||
settings-only)
|
||||
for f in settings.json config.toml config.yaml; do
|
||||
if [ -f "$user_home/.qwen/$f" ]; then
|
||||
cp -p "$user_home/.qwen/$f" "$user_home/.qwen/$f.uninstall.bak.$TIMESTAMP"
|
||||
rm -f "$user_home/.qwen/$f"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
full)
|
||||
BAK="$user_home/.qwen.uninstall-backup.$TIMESTAMP.tar.gz"
|
||||
if tar -czf "$BAK" -C "$user_home" .qwen 2>/dev/null; then
|
||||
echo " Backup: $BAK"
|
||||
rm -rf "$user_home/.qwen"
|
||||
echo " Removed $user_home/.qwen (restore: tar -xzf $BAK -C $user_home)"
|
||||
else
|
||||
echo " ERROR: backup failed — refusing to delete $user_home/.qwen"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
Reference in New Issue
Block a user