Jesús Pérez 4b92aa764a
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
implements a production-ready bootstrap installer with comprehensive error handling, version-agnostic archive extraction, and clear user messaging. All improvements follow DRY principles using symlink-based architecture for single-source-of-truth maintenance
2025-12-11 22:04:54 +00:00

1248 lines
38 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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