437 lines
12 KiB
Bash
437 lines
12 KiB
Bash
![]() |
#!/bin/bash
|
||
|
# Nushell Infrastructure Runtime Installation Script
|
||
|
# Secure installation with version management and safety checks
|
||
|
|
||
|
set -euo pipefail
|
||
|
|
||
|
# Configuration
|
||
|
NUSHELL_VERSION="${NUSHELL_VERSION:-0.107.1}"
|
||
|
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
|
||
|
CONFIG_DIR="${CONFIG_DIR:-/etc/nushell}"
|
||
|
USER_HOME="${USER_HOME:-$HOME}"
|
||
|
ADMIN_USER="${ADMIN_USER:-$(whoami)}"
|
||
|
|
||
|
# Security settings
|
||
|
NUSHELL_READONLY="${NUSHELL_READONLY:-true}"
|
||
|
NUSHELL_PLUGINS="${NUSHELL_PLUGINS:-false}"
|
||
|
NUSHELL_NETWORK="${NUSHELL_NETWORK:-false}"
|
||
|
|
||
|
# Colors for output
|
||
|
RED='\033[0;31m'
|
||
|
GREEN='\033[0;32m'
|
||
|
YELLOW='\033[1;33m'
|
||
|
BLUE='\033[0;34m'
|
||
|
NC='\033[0m'
|
||
|
|
||
|
log() {
|
||
|
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||
|
}
|
||
|
|
||
|
error() {
|
||
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||
|
}
|
||
|
|
||
|
warn() {
|
||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||
|
}
|
||
|
|
||
|
success() {
|
||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||
|
}
|
||
|
|
||
|
# Check prerequisites
|
||
|
check_prerequisites() {
|
||
|
log "Checking prerequisites..."
|
||
|
|
||
|
# Check if running as root or with sudo privileges
|
||
|
if [[ $EUID -eq 0 ]]; then
|
||
|
warn "Running as root. Consider using a dedicated user for Nushell operations."
|
||
|
fi
|
||
|
|
||
|
# Check system architecture
|
||
|
local arch=$(uname -m)
|
||
|
case $arch in
|
||
|
x86_64|amd64)
|
||
|
NUSHELL_ARCH="x86_64"
|
||
|
;;
|
||
|
aarch64|arm64)
|
||
|
NUSHELL_ARCH="aarch64"
|
||
|
;;
|
||
|
*)
|
||
|
error "Unsupported architecture: $arch"
|
||
|
exit 1
|
||
|
;;
|
||
|
esac
|
||
|
|
||
|
# Check OS
|
||
|
local os=$(uname -s)
|
||
|
case $os in
|
||
|
Linux)
|
||
|
NUSHELL_OS="unknown-linux-gnu"
|
||
|
;;
|
||
|
Darwin)
|
||
|
NUSHELL_OS="apple-darwin"
|
||
|
;;
|
||
|
*)
|
||
|
error "Unsupported operating system: $os"
|
||
|
exit 1
|
||
|
;;
|
||
|
esac
|
||
|
|
||
|
# Check available disk space (minimum 100MB)
|
||
|
local available_space=$(df "$INSTALL_DIR" | awk 'NR==2 {print $4}')
|
||
|
if [[ $available_space -lt 102400 ]]; then
|
||
|
error "Insufficient disk space. Need at least 100MB in $INSTALL_DIR"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
success "Prerequisites check completed"
|
||
|
}
|
||
|
|
||
|
# Download and verify Nushell
|
||
|
download_nushell() {
|
||
|
log "Downloading Nushell v${NUSHELL_VERSION} for ${NUSHELL_ARCH}-${NUSHELL_OS}..."
|
||
|
|
||
|
local download_url="https://github.com/nushell/nushell/releases/download/${NUSHELL_VERSION}/nu-${NUSHELL_VERSION}-${NUSHELL_ARCH}-${NUSHELL_OS}.tar.gz"
|
||
|
local temp_dir=$(mktemp -d)
|
||
|
local archive_file="${temp_dir}/nushell.tar.gz"
|
||
|
|
||
|
# Download with retry logic
|
||
|
local max_retries=3
|
||
|
local retry_count=0
|
||
|
|
||
|
while [[ $retry_count -lt $max_retries ]]; do
|
||
|
if curl -L --fail --silent --show-error "$download_url" -o "$archive_file"; then
|
||
|
break
|
||
|
else
|
||
|
retry_count=$((retry_count + 1))
|
||
|
warn "Download attempt $retry_count failed. Retrying..."
|
||
|
sleep 2
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
if [[ $retry_count -eq $max_retries ]]; then
|
||
|
error "Failed to download Nushell after $max_retries attempts"
|
||
|
rm -rf "$temp_dir"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
# Verify download
|
||
|
if [[ ! -f "$archive_file" ]] || [[ ! -s "$archive_file" ]]; then
|
||
|
error "Downloaded file is empty or missing"
|
||
|
rm -rf "$temp_dir"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
log "Extracting Nushell..."
|
||
|
tar -xzf "$archive_file" -C "$temp_dir"
|
||
|
|
||
|
# Find the nu binary
|
||
|
local nu_binary=$(find "$temp_dir" -name "nu" -type f -executable | head -1)
|
||
|
if [[ -z "$nu_binary" ]]; then
|
||
|
error "Could not find nu binary in downloaded archive"
|
||
|
rm -rf "$temp_dir"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
# Install binary
|
||
|
sudo mkdir -p "$INSTALL_DIR"
|
||
|
sudo cp "$nu_binary" "$INSTALL_DIR/nu"
|
||
|
sudo chmod +x "$INSTALL_DIR/nu"
|
||
|
|
||
|
# Copy additional tools if they exist
|
||
|
for tool in nu_plugin_kcl nu_plugin_tera nu_plugin_polars; do
|
||
|
local tool_binary=$(find "$temp_dir" -name "$tool" -type f -executable | head -1)
|
||
|
if [[ -n "$tool_binary" ]] && [[ "$NUSHELL_PLUGINS" == "true" ]]; then
|
||
|
sudo cp "$tool_binary" "$INSTALL_DIR/$tool"
|
||
|
sudo chmod +x "$INSTALL_DIR/$tool"
|
||
|
log "Installed plugin: $tool"
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
# Cleanup
|
||
|
rm -rf "$temp_dir"
|
||
|
|
||
|
success "Nushell installation completed"
|
||
|
}
|
||
|
|
||
|
# Create secure configuration
|
||
|
create_configuration() {
|
||
|
log "Creating secure Nushell configuration..."
|
||
|
|
||
|
# Create system-wide config directory
|
||
|
sudo mkdir -p "$CONFIG_DIR"
|
||
|
sudo mkdir -p "$CONFIG_DIR/scripts"
|
||
|
sudo mkdir -p "$CONFIG_DIR/observability"
|
||
|
|
||
|
# Create user-specific directories
|
||
|
mkdir -p "$USER_HOME/.config/nushell"
|
||
|
mkdir -p "$USER_HOME/.local/share/nushell"
|
||
|
mkdir -p "$USER_HOME/nushell/scripts"
|
||
|
mkdir -p "$USER_HOME/nushell/observability"
|
||
|
mkdir -p "$USER_HOME/nushell/lib"
|
||
|
|
||
|
# Set secure permissions
|
||
|
chmod 750 "$USER_HOME/nushell"
|
||
|
chmod 700 "$USER_HOME/nushell/scripts"
|
||
|
|
||
|
# Create basic configuration files
|
||
|
cat > "$USER_HOME/.config/nushell/env.nu" << 'EOF'
|
||
|
# Nushell Infrastructure Environment
|
||
|
# Security-focused configuration for infrastructure servers
|
||
|
|
||
|
$env.NUSHELL_HOME = $"($env.HOME)/nushell"
|
||
|
$env.NUSHELL_CONFIG_DIR = $"($env.HOME)/.config/nushell"
|
||
|
$env.NUSHELL_EXECUTION_MODE = "restricted"
|
||
|
$env.NUSHELL_READONLY_MODE = true
|
||
|
$env.NUSHELL_AUDIT_ENABLED = true
|
||
|
$env.NUSHELL_AUDIT_FILE = $"($env.HOME)/nushell/audit.log"
|
||
|
|
||
|
# Security: Sanitize PATH
|
||
|
$env.PATH = ($env.PATH | split row (char esep) | where $it =~ "^/(usr/)?(local/)?bin$|^/(usr/)?sbin$" | str join (char esep))
|
||
|
|
||
|
# Session information
|
||
|
$env.NUSHELL_SESSION_ID = (random uuid)
|
||
|
$env.NUSHELL_SESSION_START = (date now | format date "%Y-%m-%d %H:%M:%S")
|
||
|
|
||
|
print "🔧 Nushell Infrastructure Runtime initialized"
|
||
|
print $"🛡️ Security mode: ($env.NUSHELL_EXECUTION_MODE) | Readonly: ($env.NUSHELL_READONLY_MODE)"
|
||
|
EOF
|
||
|
|
||
|
cat > "$USER_HOME/.config/nushell/config.nu" << 'EOF'
|
||
|
# Secure Nushell Configuration for Infrastructure Servers
|
||
|
|
||
|
$env.config = {
|
||
|
show_banner: false
|
||
|
use_ansi_coloring: true
|
||
|
edit_mode: emacs
|
||
|
shell_integration: false
|
||
|
cd_with_abbreviations: false
|
||
|
filesize_metric: true
|
||
|
table_mode: rounded
|
||
|
|
||
|
history: {
|
||
|
max_size: 1000
|
||
|
sync_on_enter: true
|
||
|
file_format: "plaintext"
|
||
|
isolation: true
|
||
|
}
|
||
|
|
||
|
completions: {
|
||
|
case_sensitive: false
|
||
|
quick: true
|
||
|
partial: true
|
||
|
algorithm: "prefix"
|
||
|
external: {
|
||
|
enable: false
|
||
|
max_results: 100
|
||
|
completer: null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
error_style: "fancy"
|
||
|
|
||
|
hooks: {
|
||
|
pre_execution: [{
|
||
|
condition: {|| true }
|
||
|
code: {|| |cmd|
|
||
|
if ($env.NUSHELL_AUDIT_ENABLED? | default false) {
|
||
|
$"(date now | format date '%Y-%m-%d %H:%M:%S') - Command: ($cmd)" | save -a $env.NUSHELL_AUDIT_FILE
|
||
|
}
|
||
|
|
||
|
let blocked = ["rm", "mv", "cp", "chmod", "chown", "sudo", "su"]
|
||
|
let cmd_name = ($cmd | split row " " | first)
|
||
|
if $cmd_name in $blocked and ($env.NUSHELL_READONLY_MODE? | default true) {
|
||
|
error make {msg: $"Command '($cmd_name)' is blocked in read-only mode"}
|
||
|
}
|
||
|
}
|
||
|
}]
|
||
|
}
|
||
|
|
||
|
menus: []
|
||
|
keybindings: []
|
||
|
}
|
||
|
|
||
|
# Security aliases
|
||
|
alias ll = ls -la
|
||
|
alias df = df -h
|
||
|
alias free = free -h
|
||
|
|
||
|
print "🛡️ Nushell secure mode active"
|
||
|
EOF
|
||
|
|
||
|
# Set proper ownership
|
||
|
chown -R "$ADMIN_USER:$ADMIN_USER" "$USER_HOME/.config/nushell"
|
||
|
chown -R "$ADMIN_USER:$ADMIN_USER" "$USER_HOME/nushell"
|
||
|
|
||
|
success "Configuration created successfully"
|
||
|
}
|
||
|
|
||
|
# Install plugins (if enabled)
|
||
|
install_plugins() {
|
||
|
if [[ "$NUSHELL_PLUGINS" != "true" ]]; then
|
||
|
log "Plugin installation skipped (disabled)"
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
log "Installing Nushell plugins..."
|
||
|
|
||
|
# KCL plugin (for configuration language support)
|
||
|
if command -v kcl &> /dev/null; then
|
||
|
log "KCL binary found, plugin support available"
|
||
|
else
|
||
|
warn "KCL binary not found. Install KCL for full configuration support."
|
||
|
fi
|
||
|
|
||
|
# Create plugin registration script
|
||
|
cat > "$USER_HOME/nushell/scripts/register-plugins.nu" << 'EOF'
|
||
|
# Plugin registration script
|
||
|
# Run this to register available plugins
|
||
|
|
||
|
print "🔌 Registering Nushell plugins..."
|
||
|
|
||
|
try {
|
||
|
if (which nu_plugin_kcl | is-not-empty) {
|
||
|
plugin add nu_plugin_kcl
|
||
|
print "✅ Registered nu_plugin_kcl"
|
||
|
}
|
||
|
} catch {
|
||
|
print "⚠️ Failed to register nu_plugin_kcl"
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
if (which nu_plugin_tera | is-not-empty) {
|
||
|
plugin add nu_plugin_tera
|
||
|
print "✅ Registered nu_plugin_tera"
|
||
|
}
|
||
|
} catch {
|
||
|
print "⚠️ Failed to register nu_plugin_tera"
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
if (which nu_plugin_polars | is-not-empty) {
|
||
|
plugin add nu_plugin_polars
|
||
|
print "✅ Registered nu_plugin_polars"
|
||
|
}
|
||
|
} catch {
|
||
|
print "⚠️ Failed to register nu_plugin_polars"
|
||
|
}
|
||
|
|
||
|
print "🔌 Plugin registration completed"
|
||
|
EOF
|
||
|
|
||
|
chmod +x "$USER_HOME/nushell/scripts/register-plugins.nu"
|
||
|
success "Plugin installation completed"
|
||
|
}
|
||
|
|
||
|
# Create systemd service (optional)
|
||
|
create_service() {
|
||
|
if [[ ! -d "/etc/systemd/system" ]]; then
|
||
|
log "Systemd not available, skipping service creation"
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
log "Creating Nushell monitoring service..."
|
||
|
|
||
|
sudo tee "/etc/systemd/system/nushell-monitoring.service" > /dev/null << EOF
|
||
|
[Unit]
|
||
|
Description=Nushell Infrastructure Monitoring
|
||
|
After=network.target
|
||
|
Wants=network.target
|
||
|
|
||
|
[Service]
|
||
|
Type=simple
|
||
|
User=$ADMIN_USER
|
||
|
Group=$ADMIN_USER
|
||
|
WorkingDirectory=$USER_HOME/nushell
|
||
|
Environment=NUSHELL_EXECUTION_MODE=restricted
|
||
|
Environment=NUSHELL_READONLY_MODE=true
|
||
|
Environment=NUSHELL_AUDIT_ENABLED=true
|
||
|
ExecStart=$INSTALL_DIR/nu --config $USER_HOME/.config/nushell/config.nu --env-config $USER_HOME/.config/nushell/env.nu
|
||
|
Restart=always
|
||
|
RestartSec=10
|
||
|
|
||
|
# Security settings
|
||
|
NoNewPrivileges=true
|
||
|
PrivateTmp=true
|
||
|
ProtectSystem=strict
|
||
|
ProtectHome=read-only
|
||
|
ReadOnlyPaths=/
|
||
|
|
||
|
[Install]
|
||
|
WantedBy=multi-user.target
|
||
|
EOF
|
||
|
|
||
|
sudo systemctl daemon-reload
|
||
|
success "Systemd service created (disabled by default)"
|
||
|
}
|
||
|
|
||
|
# Verify installation
|
||
|
verify_installation() {
|
||
|
log "Verifying Nushell installation..."
|
||
|
|
||
|
# Check binary
|
||
|
if ! command -v nu &> /dev/null; then
|
||
|
error "Nushell binary not found in PATH"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
# Check version
|
||
|
local installed_version=$(nu --version | awk '{print $2}')
|
||
|
if [[ "$installed_version" != "$NUSHELL_VERSION" ]]; then
|
||
|
warn "Version mismatch: expected $NUSHELL_VERSION, got $installed_version"
|
||
|
else
|
||
|
success "Nushell version $installed_version verified"
|
||
|
fi
|
||
|
|
||
|
# Test basic functionality
|
||
|
if echo 'print "test"' | nu --config /dev/null &> /dev/null; then
|
||
|
success "Basic functionality test passed"
|
||
|
else
|
||
|
error "Basic functionality test failed"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
# Check configuration files
|
||
|
if [[ -f "$USER_HOME/.config/nushell/config.nu" ]] && [[ -f "$USER_HOME/.config/nushell/env.nu" ]]; then
|
||
|
success "Configuration files created successfully"
|
||
|
else
|
||
|
error "Configuration files missing"
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
success "Installation verification completed"
|
||
|
}
|
||
|
|
||
|
# Main installation process
|
||
|
main() {
|
||
|
log "Starting Nushell Infrastructure Runtime installation..."
|
||
|
log "Version: $NUSHELL_VERSION"
|
||
|
log "Architecture: $NUSHELL_ARCH-$NUSHELL_OS"
|
||
|
log "Install directory: $INSTALL_DIR"
|
||
|
log "Security mode: readonly=$NUSHELL_READONLY, plugins=$NUSHELL_PLUGINS"
|
||
|
|
||
|
check_prerequisites
|
||
|
download_nushell
|
||
|
create_configuration
|
||
|
install_plugins
|
||
|
create_service
|
||
|
verify_installation
|
||
|
|
||
|
success "🎉 Nushell Infrastructure Runtime installation completed successfully!"
|
||
|
log ""
|
||
|
log "Next steps:"
|
||
|
log "1. Add $INSTALL_DIR to your PATH if not already present"
|
||
|
log "2. Run 'nu' to start Nushell with secure configuration"
|
||
|
log "3. Use 'nu $USER_HOME/nushell/scripts/register-plugins.nu' to register plugins (if enabled)"
|
||
|
log "4. Enable monitoring service: sudo systemctl enable nushell-monitoring.service (optional)"
|
||
|
log ""
|
||
|
log "Configuration files:"
|
||
|
log "- Config: $USER_HOME/.config/nushell/config.nu"
|
||
|
log "- Environment: $USER_HOME/.config/nushell/env.nu"
|
||
|
log "- Scripts: $USER_HOME/nushell/scripts/"
|
||
|
log "- Audit log: $USER_HOME/nushell/audit.log"
|
||
|
}
|
||
|
|
||
|
# Run main function
|
||
|
main "$@"
|