563 lines
16 KiB
Bash
563 lines
16 KiB
Bash
![]() |
#!/bin/bash
|
|||
|
|
|||
|
# Universal Nushell + Plugins Uninstaller
|
|||
|
# POSIX compliant shell script that cleanly removes Nushell and plugins installation
|
|||
|
#
|
|||
|
# This script:
|
|||
|
# - Detects installation locations (user ~/.local/bin or system /usr/local/bin)
|
|||
|
# - Removes Nushell binary and plugin binaries
|
|||
|
# - Cleans up configuration files (with backup option)
|
|||
|
# - Removes PATH entries from shell configuration files
|
|||
|
# - Unregisters plugins from Nushell
|
|||
|
# - Provides detailed removal report
|
|||
|
|
|||
|
set -e # Exit on error
|
|||
|
|
|||
|
# Configuration
|
|||
|
INSTALL_DIR_USER="$HOME/.local/bin"
|
|||
|
INSTALL_DIR_SYSTEM="/usr/local/bin"
|
|||
|
CONFIG_DIR="$HOME/.config/nushell"
|
|||
|
BACKUP_SUFFIX="uninstall-backup-$(date +%Y%m%d_%H%M%S)"
|
|||
|
|
|||
|
# Colors for output
|
|||
|
RED='\033[0;31m'
|
|||
|
GREEN='\033[0;32m'
|
|||
|
YELLOW='\033[1;33m'
|
|||
|
BLUE='\033[0;34m'
|
|||
|
PURPLE='\033[0;35m'
|
|||
|
CYAN='\033[0;36m'
|
|||
|
NC='\033[0m' # No Color
|
|||
|
|
|||
|
# Logging functions
|
|||
|
log_info() {
|
|||
|
printf "${BLUE}ℹ️ %s${NC}\\n" "$1"
|
|||
|
}
|
|||
|
|
|||
|
log_success() {
|
|||
|
printf "${GREEN}✅ %s${NC}\\n" "$1"
|
|||
|
}
|
|||
|
|
|||
|
log_warn() {
|
|||
|
printf "${YELLOW}⚠️ %s${NC}\\n" "$1"
|
|||
|
}
|
|||
|
|
|||
|
log_error() {
|
|||
|
printf "${RED}❌ %s${NC}\\n" "$1" >&2
|
|||
|
}
|
|||
|
|
|||
|
log_debug() {
|
|||
|
if [ "$DEBUG" = "1" ]; then
|
|||
|
printf "${PURPLE}🐛 DEBUG: %s${NC}\\n" "$1"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# Usage information
|
|||
|
usage() {
|
|||
|
cat << EOF
|
|||
|
Nushell Full Distribution Uninstaller
|
|||
|
|
|||
|
USAGE:
|
|||
|
$0 [OPTIONS]
|
|||
|
|
|||
|
OPTIONS:
|
|||
|
-h, --help Show this help message
|
|||
|
-y, --yes Non-interactive mode (assume yes to prompts)
|
|||
|
-k, --keep-config Keep configuration files (don't remove ~/.config/nushell)
|
|||
|
-b, --backup-config Backup configuration before removal
|
|||
|
--system Remove from system location (/usr/local/bin) - requires sudo
|
|||
|
--user Remove from user location (~/.local/bin) - default
|
|||
|
--dry-run Show what would be removed without actually removing
|
|||
|
--debug Enable debug output
|
|||
|
|
|||
|
EXAMPLES:
|
|||
|
$0 # Interactive removal from user location
|
|||
|
$0 -y # Non-interactive removal
|
|||
|
$0 --backup-config -y # Remove with config backup
|
|||
|
$0 --system # Remove system installation (needs sudo)
|
|||
|
$0 --dry-run # Show what would be removed
|
|||
|
|
|||
|
EOF
|
|||
|
}
|
|||
|
|
|||
|
# Parse command line arguments
|
|||
|
INTERACTIVE=1
|
|||
|
KEEP_CONFIG=0
|
|||
|
BACKUP_CONFIG=0
|
|||
|
SYSTEM_INSTALL=0
|
|||
|
DRY_RUN=0
|
|||
|
|
|||
|
while [ $# -gt 0 ]; do
|
|||
|
case $1 in
|
|||
|
-h|--help)
|
|||
|
usage
|
|||
|
exit 0
|
|||
|
;;
|
|||
|
-y|--yes)
|
|||
|
INTERACTIVE=0
|
|||
|
shift
|
|||
|
;;
|
|||
|
-k|--keep-config)
|
|||
|
KEEP_CONFIG=1
|
|||
|
shift
|
|||
|
;;
|
|||
|
-b|--backup-config)
|
|||
|
BACKUP_CONFIG=1
|
|||
|
shift
|
|||
|
;;
|
|||
|
--system)
|
|||
|
SYSTEM_INSTALL=1
|
|||
|
shift
|
|||
|
;;
|
|||
|
--user)
|
|||
|
SYSTEM_INSTALL=0
|
|||
|
shift
|
|||
|
;;
|
|||
|
--dry-run)
|
|||
|
DRY_RUN=1
|
|||
|
shift
|
|||
|
;;
|
|||
|
--debug)
|
|||
|
DEBUG=1
|
|||
|
shift
|
|||
|
;;
|
|||
|
*)
|
|||
|
log_error "Unknown option: $1"
|
|||
|
usage
|
|||
|
exit 1
|
|||
|
;;
|
|||
|
esac
|
|||
|
done
|
|||
|
|
|||
|
# Determine installation directory
|
|||
|
if [ "$SYSTEM_INSTALL" = "1" ]; then
|
|||
|
INSTALL_DIR="$INSTALL_DIR_SYSTEM"
|
|||
|
log_info "Targeting system installation: $INSTALL_DIR"
|
|||
|
|
|||
|
# Check if we need sudo
|
|||
|
if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" = "0" ]; then
|
|||
|
log_warn "System installation requires administrative privileges"
|
|||
|
if [ "$INTERACTIVE" = "1" ]; then
|
|||
|
printf "Continue with sudo? [y/N]: "
|
|||
|
read -r response
|
|||
|
case "$response" in
|
|||
|
[yY]|[yY][eE][sS])
|
|||
|
;;
|
|||
|
*)
|
|||
|
log_info "Uninstallation cancelled"
|
|||
|
exit 0
|
|||
|
;;
|
|||
|
esac
|
|||
|
fi
|
|||
|
|
|||
|
# Re-run with sudo if not already root
|
|||
|
if [ "$(id -u)" -ne 0 ]; then
|
|||
|
log_info "Re-running with sudo..."
|
|||
|
exec sudo "$0" "$@"
|
|||
|
fi
|
|||
|
fi
|
|||
|
else
|
|||
|
INSTALL_DIR="$INSTALL_DIR_USER"
|
|||
|
log_info "Targeting user installation: $INSTALL_DIR"
|
|||
|
fi
|
|||
|
|
|||
|
# Detection functions
|
|||
|
detect_nushell_installation() {
|
|||
|
local install_dir="$1"
|
|||
|
local found_items=""
|
|||
|
|
|||
|
log_debug "Detecting Nushell installation in $install_dir"
|
|||
|
|
|||
|
# Check for nu binary
|
|||
|
if [ -f "$install_dir/nu" ]; then
|
|||
|
found_items="$found_items nu"
|
|||
|
log_debug "Found nu binary: $install_dir/nu"
|
|||
|
fi
|
|||
|
|
|||
|
# Check for plugin binaries
|
|||
|
for plugin_binary in "$install_dir"/nu_plugin_*; do
|
|||
|
if [ -f "$plugin_binary" ]; then
|
|||
|
local plugin_name=$(basename "$plugin_binary")
|
|||
|
found_items="$found_items $plugin_name"
|
|||
|
log_debug "Found plugin binary: $plugin_binary"
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
echo "$found_items"
|
|||
|
}
|
|||
|
|
|||
|
detect_config_installation() {
|
|||
|
local config_dir="$1"
|
|||
|
local found_items=""
|
|||
|
|
|||
|
log_debug "Detecting configuration in $config_dir"
|
|||
|
|
|||
|
if [ -d "$config_dir" ]; then
|
|||
|
# Check for main config files
|
|||
|
for config_file in config.nu env.nu distribution_config.toml; do
|
|||
|
if [ -f "$config_dir/$config_file" ]; then
|
|||
|
found_items="$found_items $config_file"
|
|||
|
log_debug "Found config file: $config_dir/$config_file"
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
# Check for plugin registration
|
|||
|
if [ -f "$config_dir/plugin.nu" ] || [ -f "$config_dir/registry.dat" ]; then
|
|||
|
found_items="$found_items plugin-registry"
|
|||
|
log_debug "Found plugin registry files"
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
echo "$found_items"
|
|||
|
}
|
|||
|
|
|||
|
detect_shell_profile_entries() {
|
|||
|
local install_dir="$1"
|
|||
|
local found_profiles=""
|
|||
|
|
|||
|
log_debug "Detecting shell profile entries for $install_dir"
|
|||
|
|
|||
|
# Check common shell profile files
|
|||
|
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" "$HOME/.bash_profile"; do
|
|||
|
if [ -f "$profile" ]; then
|
|||
|
if grep -q "$install_dir" "$profile" 2>/dev/null; then
|
|||
|
found_profiles="$found_profiles $(basename "$profile")"
|
|||
|
log_debug "Found PATH entry in: $profile"
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
echo "$found_profiles"
|
|||
|
}
|
|||
|
|
|||
|
# Removal functions
|
|||
|
remove_binaries() {
|
|||
|
local install_dir="$1"
|
|||
|
local binaries="$2"
|
|||
|
local removed_count=0
|
|||
|
|
|||
|
log_info "Removing binaries from $install_dir..."
|
|||
|
|
|||
|
for binary in $binaries; do
|
|||
|
local binary_path="$install_dir/$binary"
|
|||
|
if [ -f "$binary_path" ]; then
|
|||
|
log_info "Removing binary: $binary"
|
|||
|
if [ "$DRY_RUN" = "0" ]; then
|
|||
|
rm -f "$binary_path"
|
|||
|
if [ $? -eq 0 ]; then
|
|||
|
log_success "Removed: $binary"
|
|||
|
removed_count=$((removed_count + 1))
|
|||
|
else
|
|||
|
log_error "Failed to remove: $binary"
|
|||
|
fi
|
|||
|
else
|
|||
|
log_info "[DRY RUN] Would remove: $binary_path"
|
|||
|
removed_count=$((removed_count + 1))
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
log_success "Removed $removed_count binaries"
|
|||
|
}
|
|||
|
|
|||
|
backup_configuration() {
|
|||
|
local config_dir="$1"
|
|||
|
local backup_dir="$config_dir.$BACKUP_SUFFIX"
|
|||
|
|
|||
|
if [ -d "$config_dir" ]; then
|
|||
|
log_info "Backing up configuration to: $backup_dir"
|
|||
|
if [ "$DRY_RUN" = "0" ]; then
|
|||
|
cp -r "$config_dir" "$backup_dir"
|
|||
|
if [ $? -eq 0 ]; then
|
|||
|
log_success "Configuration backed up successfully"
|
|||
|
else
|
|||
|
log_error "Failed to backup configuration"
|
|||
|
return 1
|
|||
|
fi
|
|||
|
else
|
|||
|
log_info "[DRY RUN] Would backup configuration to: $backup_dir"
|
|||
|
fi
|
|||
|
else
|
|||
|
log_info "No configuration directory to backup"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
remove_configuration() {
|
|||
|
local config_dir="$1"
|
|||
|
local config_files="$2"
|
|||
|
|
|||
|
if [ "$KEEP_CONFIG" = "1" ]; then
|
|||
|
log_info "Keeping configuration files as requested"
|
|||
|
return 0
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$BACKUP_CONFIG" = "1" ]; then
|
|||
|
backup_configuration "$config_dir"
|
|||
|
fi
|
|||
|
|
|||
|
if [ -d "$config_dir" ]; then
|
|||
|
log_info "Removing configuration directory: $config_dir"
|
|||
|
|
|||
|
# Ask for confirmation if interactive and not just removing empty dir
|
|||
|
if [ "$INTERACTIVE" = "1" ] && [ -n "$config_files" ]; then
|
|||
|
printf "Remove configuration directory $config_dir? [y/N]: "
|
|||
|
read -r response
|
|||
|
case "$response" in
|
|||
|
[yY]|[yY][eE][sS])
|
|||
|
;;
|
|||
|
*)
|
|||
|
log_info "Keeping configuration directory"
|
|||
|
return 0
|
|||
|
;;
|
|||
|
esac
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$DRY_RUN" = "0" ]; then
|
|||
|
rm -rf "$config_dir"
|
|||
|
if [ $? -eq 0 ]; then
|
|||
|
log_success "Configuration directory removed"
|
|||
|
else
|
|||
|
log_error "Failed to remove configuration directory"
|
|||
|
fi
|
|||
|
else
|
|||
|
log_info "[DRY RUN] Would remove configuration directory: $config_dir"
|
|||
|
fi
|
|||
|
else
|
|||
|
log_info "No configuration directory found"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
remove_shell_profile_entries() {
|
|||
|
local install_dir="$1"
|
|||
|
local profiles="$2"
|
|||
|
|
|||
|
if [ -z "$profiles" ]; then
|
|||
|
log_info "No shell profile entries found"
|
|||
|
return 0
|
|||
|
fi
|
|||
|
|
|||
|
log_info "Removing PATH entries from shell profiles..."
|
|||
|
|
|||
|
for profile_name in $profiles; do
|
|||
|
local profile_path="$HOME/.$profile_name"
|
|||
|
if [ -f "$profile_path" ]; then
|
|||
|
log_info "Cleaning PATH entries from: $profile_name"
|
|||
|
|
|||
|
if [ "$DRY_RUN" = "0" ]; then
|
|||
|
# Create backup
|
|||
|
cp "$profile_path" "$profile_path.$BACKUP_SUFFIX"
|
|||
|
|
|||
|
# Remove lines containing the install directory
|
|||
|
grep -v "$install_dir" "$profile_path.$BACKUP_SUFFIX" > "$profile_path"
|
|||
|
|
|||
|
if [ $? -eq 0 ]; then
|
|||
|
log_success "Cleaned PATH entries from: $profile_name"
|
|||
|
else
|
|||
|
log_error "Failed to clean PATH entries from: $profile_name"
|
|||
|
# Restore backup
|
|||
|
mv "$profile_path.$BACKUP_SUFFIX" "$profile_path"
|
|||
|
fi
|
|||
|
else
|
|||
|
log_info "[DRY RUN] Would remove PATH entries from: $profile_path"
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
# Unregister plugins from nushell (if nu is still available)
|
|||
|
unregister_plugins() {
|
|||
|
local install_dir="$1"
|
|||
|
|
|||
|
# Only try to unregister if nu is still available somewhere
|
|||
|
local nu_binary=""
|
|||
|
if command -v nu >/dev/null 2>&1; then
|
|||
|
nu_binary=$(command -v nu)
|
|||
|
elif [ -f "$install_dir/nu" ]; then
|
|||
|
nu_binary="$install_dir/nu"
|
|||
|
else
|
|||
|
log_info "Nushell not available for plugin unregistration"
|
|||
|
return 0
|
|||
|
fi
|
|||
|
|
|||
|
log_info "Attempting to unregister plugins..."
|
|||
|
|
|||
|
if [ "$DRY_RUN" = "0" ]; then
|
|||
|
# Try to get list of registered plugins
|
|||
|
local registered_plugins=""
|
|||
|
registered_plugins=$("$nu_binary" -c "plugin list | get name" 2>/dev/null) || {
|
|||
|
log_warn "Could not retrieve plugin list"
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
if [ -n "$registered_plugins" ]; then
|
|||
|
log_info "Unregistering plugins from nushell..."
|
|||
|
for plugin in $registered_plugins; do
|
|||
|
"$nu_binary" -c "plugin rm $plugin" 2>/dev/null || {
|
|||
|
log_warn "Could not unregister plugin: $plugin"
|
|||
|
}
|
|||
|
done
|
|||
|
log_success "Plugin unregistration completed"
|
|||
|
else
|
|||
|
log_info "No registered plugins found"
|
|||
|
fi
|
|||
|
else
|
|||
|
log_info "[DRY RUN] Would attempt to unregister plugins"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# Main uninstallation process
|
|||
|
main() {
|
|||
|
log_info "🗑️ Nushell Full Distribution Uninstaller"
|
|||
|
log_info "========================================"
|
|||
|
|
|||
|
if [ "$DRY_RUN" = "1" ]; then
|
|||
|
log_warn "DRY RUN MODE - No files will be modified"
|
|||
|
fi
|
|||
|
|
|||
|
# Detect current installation
|
|||
|
log_info ""
|
|||
|
log_info "🔍 Detecting current installation..."
|
|||
|
|
|||
|
local binaries=""
|
|||
|
local config_files=""
|
|||
|
local shell_profiles=""
|
|||
|
|
|||
|
# Check user installation
|
|||
|
local user_binaries=$(detect_nushell_installation "$INSTALL_DIR_USER")
|
|||
|
local user_config=$(detect_config_installation "$CONFIG_DIR")
|
|||
|
local user_profiles=$(detect_shell_profile_entries "$INSTALL_DIR_USER")
|
|||
|
|
|||
|
# Check system installation
|
|||
|
local system_binaries=$(detect_nushell_installation "$INSTALL_DIR_SYSTEM")
|
|||
|
local system_profiles=$(detect_shell_profile_entries "$INSTALL_DIR_SYSTEM")
|
|||
|
|
|||
|
# Determine what we're removing based on target
|
|||
|
if [ "$SYSTEM_INSTALL" = "1" ]; then
|
|||
|
binaries="$system_binaries"
|
|||
|
shell_profiles="$system_profiles"
|
|||
|
# Config is always user-level
|
|||
|
config_files="$user_config"
|
|||
|
else
|
|||
|
binaries="$user_binaries"
|
|||
|
shell_profiles="$user_profiles"
|
|||
|
config_files="$user_config"
|
|||
|
fi
|
|||
|
|
|||
|
# Show detection results
|
|||
|
log_info "Installation Status:"
|
|||
|
if [ -n "$user_binaries" ]; then
|
|||
|
log_info " 📁 User binaries ($INSTALL_DIR_USER): $user_binaries"
|
|||
|
else
|
|||
|
log_info " 📁 User binaries ($INSTALL_DIR_USER): none found"
|
|||
|
fi
|
|||
|
|
|||
|
if [ -n "$system_binaries" ]; then
|
|||
|
log_info " 📁 System binaries ($INSTALL_DIR_SYSTEM): $system_binaries"
|
|||
|
else
|
|||
|
log_info " 📁 System binaries ($INSTALL_DIR_SYSTEM): none found"
|
|||
|
fi
|
|||
|
|
|||
|
if [ -n "$config_files" ]; then
|
|||
|
log_info " ⚙️ Configuration ($CONFIG_DIR): $config_files"
|
|||
|
else
|
|||
|
log_info " ⚙️ Configuration ($CONFIG_DIR): none found"
|
|||
|
fi
|
|||
|
|
|||
|
if [ -n "$shell_profiles" ]; then
|
|||
|
log_info " 🐚 Shell profiles: $shell_profiles"
|
|||
|
else
|
|||
|
log_info " 🐚 Shell profiles: no PATH entries found"
|
|||
|
fi
|
|||
|
|
|||
|
# Check if anything was found
|
|||
|
if [ -z "$binaries" ] && [ -z "$config_files" ] && [ -z "$shell_profiles" ]; then
|
|||
|
log_warn "No Nushell installation detected"
|
|||
|
if [ "$INTERACTIVE" = "1" ]; then
|
|||
|
printf "Continue anyway? [y/N]: "
|
|||
|
read -r response
|
|||
|
case "$response" in
|
|||
|
[yY]|[yY][eE][sS])
|
|||
|
;;
|
|||
|
*)
|
|||
|
log_info "Uninstallation cancelled"
|
|||
|
exit 0
|
|||
|
;;
|
|||
|
esac
|
|||
|
else
|
|||
|
log_info "Nothing to remove"
|
|||
|
exit 0
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
# Confirmation prompt
|
|||
|
if [ "$INTERACTIVE" = "1" ]; then
|
|||
|
log_info ""
|
|||
|
log_warn "This will remove the detected Nushell installation components."
|
|||
|
if [ "$KEEP_CONFIG" = "1" ]; then
|
|||
|
log_info "Configuration files will be kept as requested."
|
|||
|
elif [ "$BACKUP_CONFIG" = "1" ]; then
|
|||
|
log_info "Configuration files will be backed up before removal."
|
|||
|
fi
|
|||
|
|
|||
|
printf "Proceed with uninstallation? [y/N]: "
|
|||
|
read -r response
|
|||
|
case "$response" in
|
|||
|
[yY]|[yY][eE][sS])
|
|||
|
;;
|
|||
|
*)
|
|||
|
log_info "Uninstallation cancelled"
|
|||
|
exit 0
|
|||
|
;;
|
|||
|
esac
|
|||
|
fi
|
|||
|
|
|||
|
# Perform uninstallation
|
|||
|
log_info ""
|
|||
|
log_info "🗑️ Starting uninstallation..."
|
|||
|
|
|||
|
# Unregister plugins before removing binaries
|
|||
|
if [ -n "$binaries" ]; then
|
|||
|
unregister_plugins "$INSTALL_DIR"
|
|||
|
fi
|
|||
|
|
|||
|
# Remove binaries
|
|||
|
if [ -n "$binaries" ]; then
|
|||
|
remove_binaries "$INSTALL_DIR" "$binaries"
|
|||
|
fi
|
|||
|
|
|||
|
# Remove configuration
|
|||
|
if [ -n "$config_files" ]; then
|
|||
|
remove_configuration "$CONFIG_DIR" "$config_files"
|
|||
|
fi
|
|||
|
|
|||
|
# Clean shell profiles
|
|||
|
if [ -n "$shell_profiles" ]; then
|
|||
|
remove_shell_profile_entries "$INSTALL_DIR" "$shell_profiles"
|
|||
|
fi
|
|||
|
|
|||
|
# Final summary
|
|||
|
log_info ""
|
|||
|
log_success "🎉 Uninstallation completed!"
|
|||
|
log_info "Summary:"
|
|||
|
log_info " 🗑️ Removed from: $INSTALL_DIR"
|
|||
|
if [ -n "$config_files" ] && [ "$KEEP_CONFIG" = "0" ]; then
|
|||
|
log_info " 🗑️ Configuration removed: $CONFIG_DIR"
|
|||
|
elif [ "$KEEP_CONFIG" = "1" ]; then
|
|||
|
log_info " 💾 Configuration kept: $CONFIG_DIR"
|
|||
|
fi
|
|||
|
if [ "$BACKUP_CONFIG" = "1" ]; then
|
|||
|
log_info " 💾 Configuration backed up with suffix: .$BACKUP_SUFFIX"
|
|||
|
fi
|
|||
|
|
|||
|
log_info ""
|
|||
|
log_info "🔄 To complete removal:"
|
|||
|
log_info "1. Restart your terminal or run: source ~/.bashrc (or equivalent)"
|
|||
|
log_info "2. Verify removal: command -v nu (should return nothing)"
|
|||
|
|
|||
|
if [ "$KEEP_CONFIG" = "1" ] || [ "$BACKUP_CONFIG" = "1" ]; then
|
|||
|
log_info ""
|
|||
|
log_info "📝 Note: Configuration files were preserved as requested"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# Run main function
|
|||
|
main "$@"
|