#!/bin/bash # Universal Nushell + Plugins Bootstrap Installer # POSIX compliant shell script that installs Nushell and plugins without any prerequisites # # This script: # - Detects platform (Linux/macOS, x86_64/arm64) # - Downloads or builds Nushell + plugins # - Installs to user location (~/.local/bin) or system (/usr/local/bin) # - Updates PATH in shell configuration files # - Creates initial Nushell configuration # - Registers all plugins automatically # - Verifies installation set -e # Exit on error # Configuration REPO_URL="https://github.com/jesusperezlorenzo/nushell-plugins" BINARY_REPO_URL="$REPO_URL/releases/download" INSTALL_DIR_USER="$HOME/.local/bin" INSTALL_DIR_SYSTEM="/usr/local/bin" CONFIG_DIR="$HOME/.config/nushell" TEMP_DIR="/tmp/nushell-install-$$" # 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_header() { printf "\n${PURPLE}🚀 %s${NC}\n" "$1" printf "${PURPLE}%s${NC}\n" "$(printf '=%.0s' $(seq 1 ${#1}))" } # Usage information usage() { cat << 'EOF' Nushell + Plugins Bootstrap Installer USAGE: curl -L install-url/install.sh | sh # or ./install.sh [OPTIONS] OPTIONS: --system Install to system directory (/usr/local/bin) ⚠️ Requires sudo if not root: sudo ./install.sh --system --user Install to user directory (~/.local/bin) [default] No sudo required - recommended for most users --install-dir PATH Install to custom directory (PATH must be writable) Bypasses interactive prompts when supplied explicitly Example: --install-dir ~/.local/bin --source-path PATH Install from local archive path (no download needed) Default: ./bin_archives (if --source-path is omitted) Useful for offline installations or pre-built archives Example: --source-path ./distribution/darwin-arm64 --no-path Don't modify shell PATH configuration --no-config Don't create initial nushell configuration --no-plugins Install only nushell, skip plugins --build-from-source Build from source instead of downloading binaries --verify Verify installation after completion --uninstall Remove nushell and plugins --version VERSION Install specific version (default: latest) --help Show this help message EXAMPLES: # Default installation (user directory, with plugins, no sudo needed) curl -L install-url/install.sh | sh # Install to custom directory (no prompts, no sudo needed) ./install.sh --install-dir ~/.local/bin # Install from local archive (default: ./bin_archives) ./install.sh --source-path # Install from custom local archive path ./install.sh --source-path ./distribution/darwin-arm64 # System installation (requires sudo) sudo ./install.sh --system # Install without plugins ./install.sh --no-plugins # Build from source ./install.sh --build-from-source # Install specific version ./install.sh --version v0.107.1 TROUBLESHOOTING: • Permission denied to /usr/local/bin? → Use: --install-dir ~/.local/bin (no sudo needed) → Or: sudo ./install.sh --system (for system-wide installation) • Not sure which option to use? → Default: ./install.sh (installs to ~/.local/bin, no sudo) → Safe and recommended for most users EOF } # Platform detection detect_platform() { local os arch # Detect OS case "$(uname -s)" in Linux) os="linux" ;; Darwin) os="darwin" ;; CYGWIN*|MINGW*|MSYS*) os="windows" ;; *) log_error "Unsupported OS: $(uname -s)"; exit 1 ;; esac # Detect architecture case "$(uname -m)" in x86_64|amd64) arch="x86_64" ;; aarch64|arm64) arch="arm64" ;; armv7l) arch="armv7" ;; *) log_error "Unsupported architecture: $(uname -m)"; exit 1 ;; esac # Special handling for Darwin arm64 if [ "$os" = "darwin" ] && [ "$arch" = "arm64" ]; then arch="arm64" fi echo "${os}-${arch}" } # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Check dependencies for building from source check_build_dependencies() { local missing="" if ! command_exists "git"; then missing="$missing git" fi if ! command_exists "cargo"; then missing="$missing cargo" fi if ! command_exists "rustc"; then missing="$missing rust" fi if [ -n "$missing" ]; then log_error "Missing build dependencies:$missing" log_info "Please install these tools or use binary installation instead" return 1 fi return 0 } # Download file with progress download_file() { local url="$1" local output="$2" local desc="${3:-file}" log_info "Downloading $desc..." if command_exists "curl"; then if ! curl -L --fail --progress-bar "$url" -o "$output"; then log_error "Failed to download $desc from $url" return 1 fi elif command_exists "wget"; then if ! wget --progress=bar:force "$url" -O "$output"; then log_error "Failed to download $desc from $url" return 1 fi else log_error "Neither curl nor wget is available" return 1 fi log_success "Downloaded $desc" return 0 } # Extract archive extract_archive() { local archive="$1" local destination="$2" log_info "Extracting archive..." case "$archive" in *.tar.gz) if ! tar -xzf "$archive" -C "$destination"; then log_error "Failed to extract $archive" return 1 fi ;; *.zip) if command_exists "unzip"; then if ! unzip -q "$archive" -d "$destination"; then log_error "Failed to extract $archive" return 1 fi else log_error "unzip command not found" return 1 fi ;; *) log_error "Unsupported archive format: $archive" return 1 ;; esac log_success "Extracted archive" return 0 } # Get latest release version get_latest_version() { local version="" if command_exists "curl"; then version=$(curl -s "https://api.github.com/repos/jesusperezlorenzo/nushell-plugins/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"([^"]+)".*/\1/') elif command_exists "wget"; then version=$(wget -qO- "https://api.github.com/repos/jesusperezlorenzo/nushell-plugins/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"([^"]+)".*/\1/') fi if [ -z "$version" ]; then # Fallback to a reasonable default version="v0.107.1" log_warn "Could not detect latest version, using $version" fi echo "$version" } # Download and install binaries install_from_binaries() { local platform="$1" local version="$2" local install_dir="$3" local include_plugins="$4" log_header "Installing from Pre-built Binaries" # Create temporary directory mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" # Determine archive name and URL local archive_name="nushell-plugins-${platform}-${version}.tar.gz" local download_url="${BINARY_REPO_URL}/${version}/${archive_name}" # Download archive if ! download_file "$download_url" "$archive_name" "Nushell distribution"; then log_warn "Binary download failed, trying alternative..." # Try without version prefix archive_name="nushell-plugins-${platform}.tar.gz" download_url="${BINARY_REPO_URL}/latest/${archive_name}" if ! download_file "$download_url" "$archive_name" "Nushell distribution (latest)"; then log_error "Failed to download binaries" return 1 fi fi # Extract archive if ! extract_archive "$archive_name" "."; then return 1 fi # Find extracted directory local extract_dir="" for dir in */; do if [ -d "$dir" ]; then extract_dir="$dir" break fi done if [ -z "$extract_dir" ]; then log_error "No extracted directory found" return 1 fi log_info "Installing binaries to $install_dir..." # Create install directory mkdir -p "$install_dir" # Install nushell binary local nu_binary="nu" if [ "$platform" = "windows-x86_64" ]; then nu_binary="nu.exe" fi # Check for binary - try both root and bin/ subdirectory local nu_path="${extract_dir}${nu_binary}" if [ ! -f "$nu_path" ]; then # Try bin/ subdirectory nu_path="${extract_dir}bin/${nu_binary}" fi if [ -f "$nu_path" ]; then cp "$nu_path" "$install_dir/" chmod +x "${install_dir}/${nu_binary}" log_success "Installed nushell binary" else log_error "Nushell binary not found in archive (tried root and bin/ directory)" return 1 fi # Install plugins if requested if [ "$include_plugins" = "true" ]; then local plugin_count=0 # Try both root and bin/ subdirectory for plugins for plugin_file in "${extract_dir}"nu_plugin_* "${extract_dir}bin/"nu_plugin_*; do if [ -f "$plugin_file" ]; then local plugin_name=$(basename "$plugin_file") cp "$plugin_file" "$install_dir/" chmod +x "${install_dir}/${plugin_name}" plugin_count=$((plugin_count + 1)) fi done if [ $plugin_count -gt 0 ]; then log_success "Installed $plugin_count plugins" else log_warn "No plugins found in archive" fi fi # Copy configuration files if they exist if [ -d "${extract_dir}config/" ]; then mkdir -p "$CONFIG_DIR" cp -r "${extract_dir}config/"* "$CONFIG_DIR/" log_success "Installed configuration files" fi return 0 } # Build and install from source install_from_source() { local install_dir="$1" local include_plugins="$2" log_header "Building from Source" # Check dependencies if ! check_build_dependencies; then return 1 fi # Create temporary directory mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" # Clone repository log_info "Cloning repository..." if ! git clone --recursive "https://github.com/jesusperezlorenzo/nushell-plugins" nushell-plugins; then log_error "Failed to clone repository" return 1 fi cd nushell-plugins # Build nushell log_info "Building nushell..." if command_exists "just"; then if ! just build-nushell; then log_error "Failed to build nushell with just" return 1 fi else # Fallback to manual build cd nushell if ! cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"; then log_error "Failed to build nushell" return 1 fi cd .. fi # Build plugins if requested if [ "$include_plugins" = "true" ]; then log_info "Building plugins..." if command_exists "just"; then if ! just build; then log_warn "Failed to build some plugins" fi else # Build plugins manually for plugin_dir in nu_plugin_*; do if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then log_info "Building $plugin_dir..." cd "$plugin_dir" if cargo build --release; then log_success "Built $plugin_dir" else log_warn "Failed to build $plugin_dir" fi cd .. fi done fi fi # Install binaries log_info "Installing binaries to $install_dir..." mkdir -p "$install_dir" # Install nushell local nu_binary="nushell/target/release/nu" if [ -f "$nu_binary" ]; then cp "$nu_binary" "$install_dir/" chmod +x "${install_dir}/nu" log_success "Installed nushell binary" else log_error "Nushell binary not found" return 1 fi # Install plugins if [ "$include_plugins" = "true" ]; then local plugin_count=0 for plugin_dir in nu_plugin_*; do if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then local plugin_binary="${plugin_dir}/target/release/${plugin_dir}" if [ -f "$plugin_binary" ]; then cp "$plugin_binary" "$install_dir/" chmod +x "${install_dir}/${plugin_dir}" plugin_count=$((plugin_count + 1)) fi fi done if [ $plugin_count -gt 0 ]; then log_success "Installed $plugin_count plugins" else log_warn "No plugins were built successfully" fi fi return 0 } # Install from local source (directory or extracted archive) install_from_local() { local source_dir="$1" local install_dir="$2" local include_plugins="$3" log_header "Installing from Local Source" # Ensure install directory exists mkdir -p "$install_dir" # Look for nushell binary local nu_binary="" if [ -f "$source_dir/nu" ]; then nu_binary="$source_dir/nu" elif [ -f "$source_dir/bin/nu" ]; then nu_binary="$source_dir/bin/nu" fi if [ -z "$nu_binary" ]; then log_error "Nushell binary not found in: $source_dir" return 1 fi # Copy nushell binary log_info "Copying nushell binary..." cp "$nu_binary" "$install_dir/" chmod +x "${install_dir}/nu" log_success "Installed nushell binary" # Install plugins if requested if [ "$include_plugins" = "true" ]; then local plugin_count=0 # Look for plugin binaries in both root and bin subdirectory for plugin_file in "$source_dir"/nu_plugin_* "$source_dir"/bin/nu_plugin_*; do if [ -f "$plugin_file" ]; then local plugin_name=$(basename "$plugin_file") # Skip metadata files (.d files) if [[ "$plugin_name" != *.d ]]; then log_info "Copying plugin: $plugin_name" cp "$plugin_file" "$install_dir/" chmod +x "${install_dir}/${plugin_name}" plugin_count=$((plugin_count + 1)) fi fi done if [ $plugin_count -gt 0 ]; then log_success "Installed $plugin_count plugins" else log_info "No plugins found in local source" fi fi return 0 } # Register plugins with nushell register_plugins() { local install_dir="$1" local nu_binary="${install_dir}/nu" if [ ! -f "$nu_binary" ]; then log_error "Nushell binary not found: $nu_binary" return 1 fi log_header "Registering Plugins" # Find all plugin binaries local plugin_count=0 local failed_plugins=() local incompatible_plugins=() for plugin_file in "${install_dir}"/nu_plugin_*; do if [ -f "$plugin_file" ] && [ -x "$plugin_file" ]; then local plugin_name=$(basename "$plugin_file") log_info "Registering $plugin_name..." # Capture both stdout and stderr to detect incompatibility local output output=$("$nu_binary" -c "plugin add '$plugin_file'" 2>&1) local exit_code=$? if [ $exit_code -eq 0 ]; then log_success "Registered $plugin_name" plugin_count=$((plugin_count + 1)) else # Check if it's a version incompatibility error if echo "$output" | grep -q "is not compatible with version\|is compiled for nushell version"; then log_warn "Skipping $plugin_name: Version mismatch (built for different Nushell version)" incompatible_plugins+=("$plugin_name") else log_warn "Failed to register $plugin_name: $(echo "$output" | head -1)" failed_plugins+=("$plugin_name") fi fi fi done echo "" if [ $plugin_count -gt 0 ]; then log_success "Successfully registered $plugin_count plugins" else log_warn "No plugins were registered" fi # Report incompatible plugins if [ ${#incompatible_plugins[@]} -gt 0 ]; then log_info "" log_warn "Skipped ${#incompatible_plugins[@]} incompatible plugins (version mismatch):" for plugin in "${incompatible_plugins[@]}"; do log_info " - $plugin" done log_info "These plugins were built for a different Nushell version." log_info "They can be rebuilt locally if needed or updated in the distribution." fi # Report failed plugins if [ ${#failed_plugins[@]} -gt 0 ]; then log_info "" log_error "Failed to register ${#failed_plugins[@]} plugins:" for plugin in "${failed_plugins[@]}"; do log_info " - $plugin" done fi return 0 } # Update PATH in shell configuration update_shell_path() { local install_dir="$1" log_header "Updating Shell Configuration" # List of shell configuration files to update local shell_configs="" # Detect current shell and add its config file first case "$SHELL" in */bash) shell_configs="$HOME/.bashrc $HOME/.bash_profile" ;; */zsh) shell_configs="$HOME/.zshrc" ;; */fish) shell_configs="$HOME/.config/fish/config.fish" ;; */nu) shell_configs="$HOME/.config/nushell/env.nu" ;; esac # Add common configuration files shell_configs="$shell_configs $HOME/.profile" local updated=false local path_found=false for config_file in $shell_configs; do if [ -f "$config_file" ] || [ "$config_file" = "$HOME/.bashrc" ] || [ "$config_file" = "$HOME/.profile" ]; then # Check if already in PATH if grep -q "$install_dir" "$config_file" 2>/dev/null; then log_info "PATH already configured in $(basename "$config_file")" path_found=true continue fi # Create config file if it doesn't exist if [ ! -f "$config_file" ]; then touch "$config_file" fi # Add PATH update to config file case "$config_file" in *.fish) echo "fish_add_path $install_dir" >> "$config_file" ;; */env.nu) echo "\$env.PATH = (\$env.PATH | split row (char esep) | append \"$install_dir\" | uniq)" >> "$config_file" ;; *) echo "" >> "$config_file" echo "# Added by nushell installer" >> "$config_file" echo "export PATH=\"$install_dir:\$PATH\"" >> "$config_file" ;; esac log_success "Updated $(basename "$config_file")" updated=true path_found=true fi done if [ "$updated" = "true" ]; then log_success "Shell configuration updated" log_info "Please restart your terminal or run 'source ~/.bashrc' to apply changes" elif [ "$path_found" = "true" ]; then log_success "PATH is already configured in your shell configuration files" log_info "No changes were needed" else log_warn "Could not find or update shell configuration files" log_info "Please manually add the following to your shell configuration:" log_info "export PATH=\"$install_dir:\$PATH\"" fi # Update current session PATH export PATH="$install_dir:$PATH" log_success "Updated PATH for current session" } # Create initial nushell configuration create_nushell_config() { log_header "Creating Nushell Configuration" # Create config directory mkdir -p "$CONFIG_DIR" # Create basic config.nu if it doesn't exist local config_file="$CONFIG_DIR/config.nu" if [ ! -f "$config_file" ]; then cat > "$config_file" << 'EOF' # Nushell Configuration # Created by nushell-plugins installer # Set up basic configuration $env.config = { show_banner: false edit_mode: emacs shell_integration: true table: { mode: rounded index_mode: always show_empty: true padding: { left: 1, right: 1 } } completions: { case_sensitive: false quick: true partial: true algorithm: "prefix" } history: { max_size: 10000 sync_on_enter: true file_format: "plaintext" } filesize: { metric: false format: "auto" } } # Load custom commands and aliases # Add your custom configuration below EOF log_success "Created config.nu" else log_info "config.nu already exists, skipping" fi # Create basic env.nu if it doesn't exist local env_file="$CONFIG_DIR/env.nu" if [ ! -f "$env_file" ]; then cat > "$env_file" << 'EOF' # Nushell Environment Configuration # Created by nushell-plugins installer # Environment variables $env.EDITOR = "nano" $env.BROWSER = "firefox" # Nushell specific environment $env.NU_LIB_DIRS = [ ($nu.config-path | path dirname | path join "scripts") ] $env.NU_PLUGIN_DIRS = [ ($nu.config-path | path dirname | path join "plugins") ] # Add your custom environment variables below EOF log_success "Created env.nu" else log_info "env.nu already exists, skipping" fi # Create scripts directory local scripts_dir="$CONFIG_DIR/scripts" mkdir -p "$scripts_dir" # Create plugins directory local plugins_dir="$CONFIG_DIR/plugins" mkdir -p "$plugins_dir" } # Verify installation verify_installation() { local install_dir="$1" local nu_binary="${install_dir}/nu" log_header "Verifying Installation" # Check if nushell binary exists and is executable if [ ! -f "$nu_binary" ]; then log_error "Nushell binary not found: $nu_binary" return 1 fi if [ ! -x "$nu_binary" ]; then log_error "Nushell binary is not executable: $nu_binary" return 1 fi # Test nushell version log_info "Testing nushell binary..." local version_output if version_output=$("$nu_binary" --version 2>&1); then log_success "Nushell version: $version_output" else log_error "Failed to run nushell binary" log_error "Output: $version_output" return 1 fi # Test basic nushell command log_info "Testing basic nushell functionality..." if "$nu_binary" -c "echo 'Hello from Nushell'" >/dev/null 2>&1; then log_success "Basic nushell functionality works" else log_error "Basic nushell functionality failed" return 1 fi # List registered plugins log_info "Checking registered plugins..." local plugin_output if plugin_output=$("$nu_binary" -c "plugin list" 2>&1); then local plugin_count=$(echo "$plugin_output" | grep -c "nu_plugin_" || true) if [ "$plugin_count" -gt 0 ]; then log_success "Found $plugin_count registered plugins" else log_warn "No plugins are registered" fi else log_warn "Could not check plugin status" fi # Check PATH log_info "Checking PATH configuration..." if command -v nu >/dev/null 2>&1; then log_success "Nushell is available in PATH" else log_warn "Nushell is not in PATH. You may need to restart your terminal." fi log_success "Installation verification complete!" return 0 } # Uninstall function uninstall_nushell() { log_header "Uninstalling Nushell" local removed_files=0 # Remove from user directory if [ -d "$INSTALL_DIR_USER" ]; then for binary in nu nu_plugin_*; do local file_path="$INSTALL_DIR_USER/$binary" if [ -f "$file_path" ]; then rm -f "$file_path" log_success "Removed $binary from $INSTALL_DIR_USER" removed_files=$((removed_files + 1)) fi done fi # Remove from system directory (if accessible) if [ -w "$INSTALL_DIR_SYSTEM" ] 2>/dev/null; then for binary in nu nu_plugin_*; do local file_path="$INSTALL_DIR_SYSTEM/$binary" if [ -f "$file_path" ]; then rm -f "$file_path" log_success "Removed $binary from $INSTALL_DIR_SYSTEM" removed_files=$((removed_files + 1)) fi done fi # Option to remove configuration printf "Remove nushell configuration directory ($CONFIG_DIR)? [y/N]: " read -r response case "$response" in [yY]|[yY][eE][sS]) if [ -d "$CONFIG_DIR" ]; then rm -rf "$CONFIG_DIR" log_success "Removed configuration directory" fi ;; *) log_info "Configuration directory preserved" ;; esac if [ $removed_files -gt 0 ]; then log_success "Uninstallation complete ($removed_files files removed)" log_warn "You may need to manually remove PATH entries from your shell configuration" else log_warn "No nushell files found to remove" fi } # Main installation function main() { local install_mode="user" local custom_install_dir="" local source_path="" local modify_path="true" local create_config="true" local include_plugins="true" local build_from_source="false" local verify_install="false" local do_uninstall="false" local version="" # Parse command line arguments while [ $# -gt 0 ]; do case "$1" in --system) install_mode="system" shift ;; --user) install_mode="user" shift ;; --install-dir) custom_install_dir="$2" shift 2 ;; --source-path) # If path is provided, use it; otherwise default to ./bin_archives if [ -n "$2" ] && [ "${2#-}" = "$2" ]; then source_path="$2" shift 2 else source_path="./bin_archives" shift fi ;; --no-path) modify_path="false" shift ;; --no-config) create_config="false" shift ;; --no-plugins) include_plugins="false" shift ;; --build-from-source) build_from_source="true" shift ;; --verify) verify_install="true" shift ;; --uninstall) do_uninstall="true" shift ;; --version) version="$2" shift 2 ;; --help) usage exit 0 ;; *) log_error "Unknown option: $1" usage exit 1 ;; esac done # Handle uninstall if [ "$do_uninstall" = "true" ]; then uninstall_nushell exit 0 fi # Show header log_header "Nushell + Plugins Installer" log_info "Universal bootstrap installer for Nushell and plugins" log_info "" # Detect platform local platform platform=$(detect_platform) log_info "Detected platform: $platform" # Determine installation directory local install_dir # If custom install dir provided, use it directly (bypass all prompts) if [ -n "$custom_install_dir" ]; then install_dir="$custom_install_dir" log_info "Using custom installation directory (via --install-dir): $install_dir" elif [ "$install_mode" = "system" ]; then install_dir="$INSTALL_DIR_SYSTEM" if [ "$(id -u)" != "0" ] && [ ! -w "$(dirname "$install_dir")" ]; then log_warn "System installation requires root privileges or write access to $INSTALL_DIR_SYSTEM" log_info "" log_info "Available options:" # Check if existing nu can be found if command -v nu >/dev/null 2>&1; then local existing_path=$(command -v nu | sed 's|/nu$||') log_info " 1) Install to existing nu location: $existing_path" fi log_info " 2) Install to user directory: $INSTALL_DIR_USER" log_info " 3) Run with sudo (requires password)" log_info "" # Interactive prompt if [ -t 0 ]; then read -p "Choose option (1-3) or press Enter for option 2 [default: 2]: " choice choice=${choice:-2} case "$choice" in 1) if command -v nu >/dev/null 2>&1; then install_dir=$(command -v nu | sed 's|/nu$||') log_info "Using existing nu location: $install_dir" else log_error "No existing nu found" exit 1 fi ;; 2) install_dir="$INSTALL_DIR_USER" log_info "Using user installation directory: $install_dir" ;; 3) log_error "Please re-run with sudo: sudo $0 $@" exit 1 ;; *) log_error "Invalid option" exit 1 ;; esac else # Non-interactive: use user dir as default log_info "Non-interactive mode: using user directory" install_dir="$INSTALL_DIR_USER" fi fi else install_dir="$INSTALL_DIR_USER" fi log_info "Installing to: $install_dir" # Ensure install directory is writable mkdir -p "$install_dir" 2>/dev/null || { log_error "Cannot write to installation directory: $install_dir" log_info "Check permissions or choose a different directory" exit 1 } # Handle source-path (local installation) if [ -n "$source_path" ]; then log_info "Installing from local source: $source_path" # Check if source exists if [ ! -e "$source_path" ]; then log_error "Source path not found: $source_path" exit 1 fi # If it's a file (archive), extract it if [ -f "$source_path" ]; then log_info "Extracting from archive: $source_path" # Create temp directory for extraction local extract_dir="$TEMP_DIR/nushell-extract" mkdir -p "$extract_dir" # Determine file type and extract case "$source_path" in *.tar.gz|*.tgz) tar -xzf "$source_path" -C "$extract_dir" || { log_error "Failed to extract tar.gz archive" exit 1 } ;; *.zip) unzip -q "$source_path" -d "$extract_dir" || { log_error "Failed to extract zip archive" exit 1 } ;; *) log_error "Unsupported archive format: $source_path" exit 1 ;; esac # Find extracted directory and install from it # Use -not -path to exclude the extract_dir itself from results local extracted=$(find "$extract_dir" -maxdepth 1 -type d -name "nushell-*" -not -path "$extract_dir" | head -1) if [ -z "$extracted" ]; then # If no nushell-* subdirectory found, check if extract_dir contains bin/nu directly extracted="$extract_dir" fi # Determine where the binaries are located local source_for_install="" # Check for binaries in multiple locations (in order of preference) if [ -f "$extracted/bin/nu" ]; then # Archive with subdirectory: nushell-X.Y.Z/bin/nu source_for_install="$extracted/bin" log_info "Found binaries in $extracted/bin/" elif [ -f "$extracted/nu" ]; then # Flat archive structure: binaries at root source_for_install="$extracted" log_info "Found binaries in $extracted/" fi # If not found yet, search for any nushell-* subdirectory with bin/nu if [ -z "$source_for_install" ] && [ -d "$extract_dir" ]; then # Exclude extract_dir itself to only find subdirectories local nushell_dir=$(find "$extract_dir" -maxdepth 1 -type d -name "nushell-*" -not -path "$extract_dir" ! -empty 2>/dev/null | head -1) if [ -n "$nushell_dir" ] && [ -f "$nushell_dir/bin/nu" ]; then source_for_install="$nushell_dir/bin" log_info "Found binaries in $nushell_dir/bin/" fi fi # Last resort: recursive search for any nu binary if [ -z "$source_for_install" ]; then local nu_binary=$(find "$extract_dir" -type f -name "nu" -executable 2>/dev/null | head -1) if [ -n "$nu_binary" ]; then source_for_install=$(dirname "$nu_binary") log_info "Found nu binary at: $source_for_install/nu" fi fi # Validate that we found the binaries if [ -z "$source_for_install" ] || [ ! -f "$source_for_install/nu" ]; then log_error "No Nushell binary found in extracted archive" log_info "Searched locations:" log_info " - $extracted/bin/nu" log_info " - $extracted/nu" log_info " - Any nushell-* subdirectory with bin/nu" log_info " - Recursive search in $extract_dir" log_info "" log_info "Archive contents:" find "$extract_dir" -type f -name "nu" 2>/dev/null | head -10 exit 1 fi # Install from the correct source directory log_info "Installing from extracted archive at: $source_for_install" install_from_local "$source_for_install" "$install_dir" "$include_plugins" elif [ -d "$source_path" ]; then # It's a directory, install directly from it log_info "Installing from local directory: $source_path" install_from_local "$source_path" "$install_dir" "$include_plugins" fi # Skip the rest of installation local build_from_source="skip_download" fi # Get version if not specified and not using local source if [ -z "$version" ] && [ "$build_from_source" != "skip_download" ]; then version=$(get_latest_version) fi if [ "$build_from_source" != "skip_download" ]; then log_info "Version: $version" fi # Cleanup function cleanup() { if [ -d "$TEMP_DIR" ]; then rm -rf "$TEMP_DIR" fi } trap cleanup EXIT # Install based on method (skip if already did local source installation) if [ "$build_from_source" = "skip_download" ]; then log_info "Local source installation completed" elif [ "$build_from_source" = "true" ]; then if ! install_from_source "$install_dir" "$include_plugins"; then log_error "Source installation failed" exit 1 fi else if ! install_from_binaries "$platform" "$version" "$install_dir" "$include_plugins"; then log_error "Binary installation failed" exit 1 fi fi # Register plugins if [ "$include_plugins" = "true" ]; then register_plugins "$install_dir" fi # Update PATH if [ "$modify_path" = "true" ]; then update_shell_path "$install_dir" fi # Create configuration if [ "$create_config" = "true" ]; then create_nushell_config fi # Verify installation if [ "$verify_install" = "true" ]; then if ! verify_installation "$install_dir"; then log_error "Installation verification failed" exit 1 fi fi # Final success message log_header "Installation Complete!" log_success "Nushell has been successfully installed to $install_dir" if [ "$include_plugins" = "true" ]; then log_success "Plugins have been registered with Nushell" fi if [ "$modify_path" = "true" ]; then log_info "To use Nushell, restart your terminal or run:" log_info " source ~/.bashrc # or your shell's config file" fi log_info "" log_info "Try running: nu --version" log_info "Or start Nushell with: nu" if [ "$include_plugins" = "true" ]; then log_info "Check plugins with: nu -c 'plugin list'" fi log_info "" log_info "For more information, visit: https://nushell.sh" log_info "" log_success "Happy shell scripting! 🚀" } # Run main function with all arguments main "$@"