
Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
## Major Features Added - **Complete distribution infrastructure**: Build, package, and distribute Nushell binary alongside plugins - **Zero-prerequisite installation**: Bootstrap installers work on fresh systems without Rust/Cargo/Nushell - **Cross-platform support**: Linux, macOS, Windows (x86_64, ARM64) - **Self-contained packages**: Everything needed for complete Nushell environment ## New Components ### Build System - `scripts/build_nushell.nu` - Build nushell with all workspace plugins - `scripts/collect_full_binaries.nu` - Collect nu binary + all plugins - `justfiles/full_distro.just` - 40+ new recipes for distribution workflows ### Bootstrap Installers (Zero Prerequisites) - `installers/bootstrap/install.sh` - Universal POSIX installer (900+ lines) - `installers/bootstrap/install.ps1` - Windows PowerShell installer (800+ lines) - Complete platform detection, PATH setup, plugin registration ### Distribution System - `scripts/create_distribution_packages.nu` - Multi-platform package creator - `scripts/install_full_nushell.nu` - Advanced nu-based installer - `scripts/verify_installation.nu` - Installation verification suite - `scripts/lib/common_lib.nu` - Shared utilities and logging ### Configuration Management - `scripts/templates/default_config.nu` - Complete nushell configuration (500+ lines) - `scripts/templates/default_env.nu` - Cross-platform environment setup - `etc/distribution_config.toml` - Central distribution settings - `scripts/templates/uninstall.sh` & `uninstall.ps1` - Clean removal ## Key Workflows ```bash just build-full # Build nushell + all plugins just pack-full-all # Create packages for all platforms just verify-full # Verify installation just release-full-cross # Complete cross-platform release ``` ## Installation Experience - One-liner: `curl -sSf https://your-url/install.sh | sh` - Multiple modes: user, system, portable installation - Automatic plugin registration with verification - Professional uninstall capability ## Benefits - ✅ Solves bootstrap problem - no prerequisites needed - ✅ Production-ready distribution system - ✅ Complete cross-platform support - ✅ Professional installation experience - ✅ Integrates seamlessly with existing plugin workflows - ✅ Enterprise-grade verification and logging
563 lines
16 KiB
Bash
Executable File
563 lines
16 KiB
Bash
Executable File
#!/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 "$@" |