nushell-plugins/scripts/install_full_nushell.nu

503 lines
17 KiB
Plaintext
Raw Permalink Normal View History

feat: Add complete Nushell full distribution system ## 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
2025-09-24 18:52:07 +01:00
#!/usr/bin/env nu
# Install Full Nushell Distribution Script
# Advanced nu-based installer called after bootstrap gets nushell running
# Provides plugin selection, configuration, and integration with existing nu environment
use lib/common_lib.nu [
log_info, log_error, log_success, log_warn, log_debug,
get_current_platform, ensure_dir, copy_file
]
def main [
--install-dir (-i): string = "" # Installation directory override
--config-dir (-c): string = "" # Configuration directory override
--plugins (-p): list<string> = [] # Specific plugins to install
--all-plugins (-a): bool = false # Install all available plugins
--no-register: bool = false # Don't register plugins with nushell
--register-only: bool = false # Only register plugins, don't copy binaries
--verify: bool = false # Verify installation after completion
--test: bool = false # Run in test mode (dry run)
--config-backup: bool = true # Backup existing configuration
--interactive (-I): bool = false # Interactive mode for plugin selection
--update (-u): bool = false # Update existing installation
] {
log_info "🚀 Nushell Full Distribution Advanced Installer"
log_info "=============================================="
if $test {
log_warn "🧪 Running in test mode (dry run)"
}
# Validate environment
validate_installation_environment $test
# Get installation paths
let paths = get_installation_paths $install_dir $config_dir
log_info ""
log_info "📍 Installation Configuration:"
log_info $" Binary directory: ($paths.bin_dir)"
log_info $" Config directory: ($paths.config_dir)"
log_info $" Plugins directory: ($paths.plugins_dir)"
if $register_only {
log_info " Mode: Register existing binaries only"
} else if $no_register {
log_info " Mode: Copy binaries without registration"
} else {
log_info " Mode: Copy binaries and register plugins"
}
# Check if this is an update
let is_update = $update or (check_existing_installation $paths)
if $is_update {
log_info " Type: Update existing installation"
} else {
log_info " Type: New installation"
}
# Plugin selection
let selected_plugins = if $interactive {
select_plugins_interactive
} else if ($plugins | length) > 0 {
$plugins
} else if $all_plugins {
get_available_plugins | get name
} else {
get_recommended_plugins
}
log_info ""
log_info $"🔌 Selected plugins: (($selected_plugins | length))"
$selected_plugins | each {|plugin| log_info $" - ($plugin)"}
if not $test {
# Backup existing configuration if requested
if $config_backup and $is_update {
backup_existing_config $paths.config_dir
}
# Create installation directories
setup_installation_directories $paths
# Install binaries
if not $register_only {
install_nushell_binary $paths $test
install_plugin_binaries $selected_plugins $paths $test
}
# Install configuration
install_configuration $paths $is_update $test
# Register plugins
if not $no_register {
register_plugins_with_nushell $selected_plugins $paths $test
}
# Verify installation if requested
if $verify {
verify_full_installation $paths $selected_plugins
}
log_success "✅ Installation completed successfully!"
print_installation_summary $paths $selected_plugins $is_update
} else {
log_info "🧪 Test mode completed - no changes made"
}
}
# Validate installation environment
def validate_installation_environment [test: bool] {
log_debug "Validating installation environment..."
# Check if we're running in nushell
if ($env.NU_VERSION? | is-empty) {
log_error "This installer must be run with nushell"
exit 1
}
# Check if we have write permissions to likely installation directories
let test_dirs = [
"~/.local/bin",
"~/.config/nushell",
"/usr/local/bin"
]
for dir in $test_dirs {
let expanded_dir = ($dir | path expand)
if ($expanded_dir | path exists) and not $test {
try {
touch $"($expanded_dir)/.write_test"
rm $"($expanded_dir)/.write_test"
log_debug $"✅ Write permission confirmed: ($expanded_dir)"
break
} catch {
log_debug $"❌ No write permission: ($expanded_dir)"
}
}
}
log_success "Environment validation passed"
}
# Get installation paths
def get_installation_paths [install_dir_override: string, config_dir_override: string] {
let home_dir = ($env.HOME | path expand)
# Determine binary installation directory
let bin_dir = if ($install_dir_override | str length) > 0 {
($install_dir_override | path expand)
} else if (which nu | length) > 0 {
# Use directory where current nu is installed
(which nu | get 0.path | path dirname)
} else {
# Default to ~/.local/bin
$"($home_dir)/.local/bin"
}
# Determine configuration directory
let config_dir = if ($config_dir_override | str length) > 0 {
($config_dir_override | path expand)
} else if ($env.XDG_CONFIG_HOME? | is-not-empty) {
$"($env.XDG_CONFIG_HOME)/nushell"
} else {
$"($home_dir)/.config/nushell"
}
# Plugin directory (for registration)
let plugins_dir = $"($config_dir)/plugins"
{
bin_dir: $bin_dir,
config_dir: $config_dir,
plugins_dir: $plugins_dir,
home_dir: $home_dir
}
}
# Check if installation already exists
def check_existing_installation [paths: record] -> bool {
let nu_exists = ($"($paths.bin_dir)/nu" | path exists)
let config_exists = ($"($paths.config_dir)/config.nu" | path exists)
$nu_exists or $config_exists
}
# Get available plugins from current directory
def get_available_plugins [] {
let bin_files = if ("./bin" | path exists) {
ls ./bin | where name =~ "nu_plugin_" | get name | each {|path| ($path | path basename | str replace ".exe" "")}
} else {
[]
}
$bin_files | each {|name| {name: $name, path: $"./bin/($name)"}}
}
# Get recommended plugins for default installation
def get_recommended_plugins [] {
let available = (get_available_plugins | get name)
let recommended = [
"nu_plugin_clipboard",
"nu_plugin_hashes",
"nu_plugin_desktop_notifications",
"nu_plugin_highlight"
]
# Return intersection of recommended and available
$recommended | where $it in $available
}
# Interactive plugin selection
def select_plugins_interactive [] {
log_info "🔌 Plugin Selection"
log_info "=================="
let available_plugins = get_available_plugins
if ($available_plugins | length) == 0 {
log_warn "No plugins found in package"
return []
}
log_info "Available plugins:"
$available_plugins | enumerate | each {|item|
log_info $" (($item.index + 1)). ($item.item.name)"
}
log_info ""
log_info "Selection options:"
log_info " - Enter numbers separated by spaces (e.g., '1 3 4')"
log_info " - Enter 'all' for all plugins"
log_info " - Enter 'recommended' for recommended plugins"
log_info " - Press Enter for recommended plugins"
let selection = (input "Your selection: ")
match $selection {
"all" => {$available_plugins | get name},
"recommended" | "" => {get_recommended_plugins},
_ => {
let indices = ($selection | split row " " | each {|x| ($x | into int) - 1})
$indices | each {|i| $available_plugins | get $i | get name}
}
}
}
# Setup installation directories
def setup_installation_directories [paths: record] {
log_info "📁 Setting up installation directories..."
ensure_dir $paths.bin_dir
ensure_dir $paths.config_dir
ensure_dir $paths.plugins_dir
log_success "✅ Installation directories ready"
}
# Install nushell binary
def install_nushell_binary [paths: record, test: bool] {
log_info "🚀 Installing nushell binary..."
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
let nu_binary = $"./bin/nu($binary_extension)"
if not ($nu_binary | path exists) {
log_error $"Nushell binary not found: ($nu_binary)"
exit 1
}
let dest_path = $"($paths.bin_dir)/nu($binary_extension)"
if not $test {
copy_file $nu_binary $dest_path
# Make executable on Unix-like systems
if not ($current_platform | str starts-with "windows") {
chmod +x $dest_path
}
}
log_success $"✅ Nushell binary installed: ($dest_path)"
}
# Install plugin binaries
def install_plugin_binaries [plugins: list<string>, paths: record, test: bool] {
log_info $"🔌 Installing plugin binaries... (($plugins | length))"
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
for plugin in $plugins {
let plugin_binary = $"./bin/($plugin)($binary_extension)"
let dest_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
if ($plugin_binary | path exists) {
if not $test {
copy_file $plugin_binary $dest_path
# Make executable on Unix-like systems
if not ($current_platform | str starts-with "windows") {
chmod +x $dest_path
}
}
log_success $"✅ Installed plugin: ($plugin)"
} else {
log_warn $"⚠️ Plugin binary not found: ($plugin_binary)"
}
}
}
# Install configuration files
def install_configuration [paths: record, is_update: bool, test: bool] {
log_info "⚙️ Installing configuration files..."
let config_files = [
{src: "./config/default_config.nu", dest: "config.nu", required: true},
{src: "./config/default_env.nu", dest: "env.nu", required: true},
{src: "./config/distribution_config.toml", dest: "distribution_config.toml", required: false}
]
for file in $config_files {
let src_path = $file.src
let dest_path = $"($paths.config_dir)/($file.dest)"
if ($src_path | path exists) {
if ($dest_path | path exists) and not $is_update {
log_info $"⚠️ Configuration file exists, creating backup: ($file.dest)"
if not $test {
cp $dest_path $"($dest_path).backup.(date now | format date '%Y%m%d_%H%M%S')"
}
}
if not $test {
copy_file $src_path $dest_path
}
log_success $"✅ Installed config: ($file.dest)"
} else if $file.required {
log_error $"Required configuration file not found: ($src_path)"
exit 1
}
}
}
# Register plugins with nushell
def register_plugins_with_nushell [plugins: list<string>, paths: record, test: bool] {
log_info $"📝 Registering plugins with nushell... (($plugins | length))"
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
let registration_commands = []
for plugin in $plugins {
let plugin_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
if ($plugin_path | path exists) or $test {
let register_cmd = $"plugin add ($plugin_path)"
$registration_commands = ($registration_commands | append $register_cmd)
log_success $"✅ Will register: ($plugin)"
} else {
log_warn $"⚠️ Cannot register plugin (binary not found): ($plugin)"
}
}
if not $test and ($registration_commands | length) > 0 {
# Create a temporary script to register plugins
let register_script = $"($paths.config_dir)/register_plugins_temp.nu"
let script_content = ([
"# Temporary plugin registration script",
"# Generated by install_full_nushell.nu",
""
] | append $registration_commands | append [
"",
"print \"✅ All plugins registered successfully\"",
$"rm ($register_script)"
] | str join "\n")
$script_content | save $register_script
log_info "Running plugin registration..."
try {
nu $register_script
} catch {|err|
log_error $"Plugin registration failed: ($err.msg)"
log_info "You can register plugins manually later with:"
$registration_commands | each {|cmd| log_info $" nu -c '($cmd)'"}
}
}
}
# Backup existing configuration
def backup_existing_config [config_dir: string] {
log_info "💾 Backing up existing configuration..."
let backup_dir = $"($config_dir)_backup_(date now | format date '%Y%m%d_%H%M%S')"
if ($config_dir | path exists) {
cp -r $config_dir $backup_dir
log_success $"✅ Configuration backed up to: ($backup_dir)"
} else {
log_info "No existing configuration to backup"
}
}
# Verify installation
def verify_full_installation [paths: record, plugins: list<string>] {
log_info "🔍 Verifying installation..."
let verification_results = {}
# Verify nushell binary
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
let nu_path = $"($paths.bin_dir)/nu($binary_extension)"
let nu_check = if ($nu_path | path exists) {
try {
let version = (run-external $nu_path "--version")
{status: "ok", message: $"Nushell installed: ($version)"}
} catch {|err|
{status: "error", message: $"Nushell binary exists but not executable: ($err.msg)"}
}
} else {
{status: "error", message: "Nushell binary not found"}
}
$verification_results = ($verification_results | insert "nushell" $nu_check)
# Verify plugins
for plugin in $plugins {
let plugin_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
let plugin_check = if ($plugin_path | path exists) {
{status: "ok", message: $"Plugin binary installed: ($plugin)"}
} else {
{status: "error", message: $"Plugin binary not found: ($plugin)"}
}
$verification_results = ($verification_results | insert $plugin $plugin_check)
}
# Verify configuration
let config_files = ["config.nu", "env.nu"]
for config_file in $config_files {
let config_path = $"($paths.config_dir)/($config_file)"
let config_check = if ($config_path | path exists) {
{status: "ok", message: $"Configuration installed: ($config_file)"}
} else {
{status: "error", message: $"Configuration not found: ($config_file)"}
}
$verification_results = ($verification_results | insert $config_file $config_check)
}
# Display verification results
log_info "📊 Verification Results:"
log_info "======================"
let all_ok = true
for item in ($verification_results | items) {
let status_icon = if $item.value.status == "ok" { "✅" } else { "❌"; $all_ok = false }
log_info $" ($status_icon) ($item.key): ($item.value.message)"
}
if $all_ok {
log_success "🎉 All verification checks passed!"
} else {
log_warn "⚠️ Some verification checks failed"
}
}
# Print installation summary
def print_installation_summary [paths: record, plugins: list<string>, is_update: bool] {
let action = if $is_update { "Updated" } else { "Installed" }
log_info ""
log_info $"📊 Installation Summary"
log_info "======================"
log_info $"Action: ($action) Nushell Full Distribution"
log_info $"Binary directory: ($paths.bin_dir)"
log_info $"Config directory: ($paths.config_dir)"
log_info $"Plugins installed: (($plugins | length))"
if ($plugins | length) > 0 {
$plugins | each {|plugin| log_info $" - ($plugin)"}
}
log_info ""
log_info "🚀 Next Steps:"
log_info "============="
log_info $"1. Add ($paths.bin_dir) to your PATH if not already present"
log_info "2. Restart your terminal or run: source ~/.bashrc (or equivalent)"
log_info "3. Test installation: nu --version"
log_info "4. Start using nushell: nu"
# Check if PATH update is needed
let path_entries = ($env.PATH | split row ":")
if not ($paths.bin_dir in $path_entries) {
log_info ""
log_warn "⚠️ PATH Update Required"
log_info $"Add this line to your shell profile (~/.bashrc, ~/.zshrc, etc.):"
log_info $"export PATH=\"($paths.bin_dir):$PATH\""
}
}