#!/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/your-org/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) --user Install to user directory (~/.local/bin) [default] --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) curl -L install-url/install.sh | sh # System installation curl -L install-url/install.sh | sh -s -- --system # Install without plugins curl -L install-url/install.sh | sh -s -- --no-plugins # Build from source curl -L install-url/install.sh | sh -s -- --build-from-source # Install specific version curl -L install-url/install.sh | sh -s -- --version v0.107.1 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/your-org/nushell-plugins/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"([^"]+)".*/\1/') elif command_exists "wget"; then version=$(wget -qO- "https://api.github.com/repos/your-org/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 if [ -f "${extract_dir}${nu_binary}" ]; then cp "${extract_dir}${nu_binary}" "$install_dir/" chmod +x "${install_dir}/${nu_binary}" log_success "Installed nushell binary" else log_error "Nushell binary not found in archive" return 1 fi # Install plugins if requested if [ "$include_plugins" = "true" ]; then local plugin_count=0 for plugin_file in "${extract_dir}"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 "$REPO_URL" 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 done 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 } # 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 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..." if "$nu_binary" -c "plugin add '$plugin_file'"; then log_success "Registered $plugin_name" plugin_count=$((plugin_count + 1)) else log_warn "Failed to register $plugin_name" fi fi done if [ $plugin_count -gt 0 ]; then log_success "Successfully registered $plugin_count plugins" else log_warn "No plugins were registered" 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 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 updated in $(basename "$config_file")" 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 fi done if [ "$updated" = "true" ]; then log_info "Please restart your terminal or run 'source ~/.bashrc' to update PATH" else log_warn "No shell configuration files were updated" log_info "Please manually add $install_dir to your 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 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 ;; --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 [ "$install_mode" = "system" ]; then install_dir="$INSTALL_DIR_SYSTEM" if [ "$(id -u)" != "0" ] && [ ! -w "$(dirname "$install_dir")" ]; then log_error "System installation requires root privileges" log_info "Run with sudo or use --user for user installation" exit 1 fi else install_dir="$INSTALL_DIR_USER" fi log_info "Installing to: $install_dir" # Get version if not specified if [ -z "$version" ]; then version=$(get_latest_version) fi log_info "Version: $version" # Cleanup function cleanup() { if [ -d "$TEMP_DIR" ]; then rm -rf "$TEMP_DIR" fi } trap cleanup EXIT # Install based on method if [ "$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 "$@"