#!/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 "$@"