feat(taskserv): implement real-time version checking with configurable HTTP client

- Add: GitHub API integration for live version checking in taskserv management
- Add: HTTP client configuration option (http.use_curl) in config.defaults.toml
- Add: Helper function fetch_latest_version with curl/http get support
- Fix: Settings path structure for prov_data_dirpath access pattern
- Remove: Legacy simulation code for version checking
- Update: Core configuration name from "provisioning-system" to "provisioning"
- Clean: Remove obsolete example configs and infrastructure files
This commit is contained in:
Jesús Pérez 2025-09-24 01:55:06 +01:00
parent 38a7470da0
commit 3c3ef47f7f
No known key found for this signature in database
GPG key ID: 9F243E355E0BC939
34 changed files with 5942 additions and 13 deletions

View file

@ -0,0 +1,143 @@
# Secure Nushell Configuration for Infrastructure Servers
# Auto-generated by provisioning system
# Security-first configuration
$env.config = {
show_banner: false
use_ansi_coloring: true
edit_mode: emacs
# Security settings
shell_integration: false
cd_with_abbreviations: false
filesize_metric: true
table_mode: rounded
# History settings (limited for security)
history: {
max_size: 1000
sync_on_enter: true
file_format: "plaintext"
isolation: true
}
# Completion settings
completions: {
case_sensitive: false
quick: true
partial: true
algorithm: "prefix"
external: {
enable: {% if taskserv.nushell_external_completions | default(false) %}true{% else %}false{% endif %}
max_results: 100
completer: null
}
}
# Performance limits
table: {
mode: rounded
index_mode: always
trim: {
methodology: wrapping
wrapping_try_keep_words: true
truncating_suffix: "..."
}
}
# Error handling
error_style: "fancy"
# Hooks for security and audit
hooks: {
pre_prompt: [{
condition: {|| true }
code: {||
# Audit logging
if ($env.NUSHELL_AUDIT_ENABLED? | default false) {
$"(date now | format date '%Y-%m-%d %H:%M:%S') - Session active" | save -a $env.NUSHELL_AUDIT_FILE
}
}
}]
pre_execution: [{
condition: {|| true }
code: {|| |cmd|
# Command validation and audit
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
}
# Security check for blocked commands
let blocked = ($env.NUSHELL_BLOCKED_COMMANDS? | default "" | split row ",")
let cmd_name = ($cmd | split row " " | first)
if $cmd_name in $blocked {
error make {msg: $"Command '($cmd_name)' is blocked for security reasons"}
}
}
}]
command_not_found: [{
condition: {|| true }
code: {|| |cmd_name|
$"Command '($cmd_name)' not found. Available commands are restricted for security."
}
}]
}
# Menus disabled for security
menus: []
# Keybindings (minimal for security)
keybindings: [
{
name: completion_menu
modifier: none
keycode: tab
mode: [emacs vi_normal vi_insert]
event: {
until: [
{ send: menu name: completion_menu }
{ send: menunext }
]
}
}
]
}
# Security aliases (read-only operations)
alias ll = ls -la
alias df = df -h
alias free = free -h
alias pstree = ps aux --forest
# Restricted environment setup
{% if taskserv.nushell_readonly | default(true) %}
# Read-only mode - disable write operations
def rm [] { error make {msg: "rm command disabled in read-only mode"} }
def mv [] { error make {msg: "mv command disabled in read-only mode"} }
def cp [] { error make {msg: "cp command disabled in read-only mode"} }
def chmod [] { error make {msg: "chmod command disabled in read-only mode"} }
def chown [] { error make {msg: "chown command disabled in read-only mode"} }
{% endif %}
# Load observability modules if enabled
{% if taskserv.nushell_metrics | default(true) %}
source $"($env.NUSHELL_HOME)/observability/collect.nu"
{% endif %}
# Session timeout warning
def session-check [] {
let start_time = (date now)
let timeout = ($env.NUSHELL_SESSION_TIMEOUT? | default 900 | into int)
if ((date now) - $start_time) > ($timeout * 1sec) {
print "⚠️ Session timeout approaching. Please complete your tasks."
}
}
# Initialize secure environment
print $"🛡️ Nushell secure mode active - execution mode: ($env.NUSHELL_EXECUTION_MODE? | default 'restricted')"
if ($env.NUSHELL_READONLY_MODE? | default true) {
print "📖 Read-only mode enabled"
}
print $"⏱️ Session timeout: ($env.NUSHELL_SESSION_TIMEOUT? | default 900) seconds"

View file

@ -0,0 +1,44 @@
# Nushell Runtime Environment Configuration
# Security: All paths are sandboxed and validated
# Core Nushell paths
NUSHELL_HOME={{taskserv.admin_user_home}}/nushell
NUSHELL_CONFIG_DIR={{taskserv.admin_user_home}}/.config/nushell
NUSHELL_DATA_DIR={{taskserv.admin_user_home}}/.local/share/nushell
NUSHELL_SCRIPTS_DIR={{taskserv.admin_user_home}}/nushell/scripts
NUSHELL_LIB_DIR={{taskserv.admin_user_home}}/nushell/lib
# Security settings
NUSHELL_EXECUTION_MODE={{taskserv.nushell_execution_mode | default("restricted")}}
NUSHELL_READONLY_MODE={{taskserv.nushell_readonly | default("true")}}
NUSHELL_NETWORK_ENABLED={{taskserv.nushell_network | default("false")}}
NUSHELL_MAX_MEMORY={{taskserv.nushell_max_memory | default("256MB")}}
NUSHELL_MAX_CPU_TIME={{taskserv.nushell_max_cpu_time | default("30s")}}
# Plugin configuration
NUSHELL_PLUGINS_ENABLED={{taskserv.nushell_plugins | default("false")}}
NUSHELL_PLUGIN_ALLOWLIST="{{taskserv.nushell_plugin_allowlist | default('nu_plugin_kcl,nu_plugin_tera,nu_plugin_polars')}}"
# Remote execution settings
NUSHELL_REMOTE_USER={{taskserv.admin_user}}
NUSHELL_REMOTE_TIMEOUT={{taskserv.nushell_remote_timeout | default("300")}}
NUSHELL_SESSION_TIMEOUT={{taskserv.nushell_session_timeout | default("900")}}
# Logging and audit
NUSHELL_LOG_LEVEL={{taskserv.nushell_log_level | default("info")}}
NUSHELL_AUDIT_ENABLED={{taskserv.nushell_audit | default("true")}}
NUSHELL_AUDIT_FILE={{taskserv.admin_user_home}}/nushell/audit.log
# KCL integration (optional)
KCL_ENABLED={{taskserv.kcl_enabled | default("false")}}
KCL_BINARY_PATH={{taskserv.kcl_binary_path | default("/usr/local/bin/kcl")}}
# Observability settings
NUSHELL_METRICS_ENABLED={{taskserv.nushell_metrics | default("true")}}
NUSHELL_TELEMETRY_ENDPOINT={{taskserv.nushell_telemetry_endpoint | default("")}}
NUSHELL_LOG_COLLECTION={{taskserv.nushell_log_collection | default("false")}}
# Environment restrictions
NUSHELL_ALLOWED_COMMANDS="{{taskserv.nushell_allowed_commands | default('ls,cat,grep,ps,df,free,uptime,systemctl,kubectl')}}"
NUSHELL_BLOCKED_COMMANDS="{{taskserv.nushell_blocked_commands | default('rm,mv,cp,chmod,chown,sudo,su')}}"
NUSHELL_ALLOWED_PATHS="{{taskserv.nushell_allowed_paths | default('/tmp,/var/log,/proc,/sys')}}"

View file

@ -0,0 +1,93 @@
# Nushell Environment Variables for Infrastructure Servers
# Security-focused environment setup
# Core environment paths
$env.NUSHELL_HOME = "{{taskserv.admin_user_home}}/nushell"
$env.NUSHELL_CONFIG_DIR = "{{taskserv.admin_user_home}}/.config/nushell"
$env.NUSHELL_DATA_DIR = "{{taskserv.admin_user_home}}/.local/share/nushell"
# Security environment variables
$env.NUSHELL_EXECUTION_MODE = "{{taskserv.nushell_execution_mode | default('restricted')}}"
$env.NUSHELL_READONLY_MODE = {% if taskserv.nushell_readonly | default(true) %}true{% else %}false{% endif %}
$env.NUSHELL_AUDIT_ENABLED = {% if taskserv.nushell_audit | default(true) %}true{% else %}false{% endif %}
$env.NUSHELL_AUDIT_FILE = "{{taskserv.admin_user_home}}/nushell/audit.log"
# Resource limits
$env.NUSHELL_MAX_MEMORY = "{{taskserv.nushell_max_memory | default('256MB')}}"
$env.NUSHELL_SESSION_TIMEOUT = {{taskserv.nushell_session_timeout | default(900)}}
# Command restrictions
$env.NUSHELL_ALLOWED_COMMANDS = "{{taskserv.nushell_allowed_commands | default('ls,cat,grep,ps,df,free,uptime,systemctl,kubectl')}}"
$env.NUSHELL_BLOCKED_COMMANDS = "{{taskserv.nushell_blocked_commands | default('rm,mv,cp,chmod,chown,sudo,su')}}"
$env.NUSHELL_ALLOWED_PATHS = "{{taskserv.nushell_allowed_paths | default('/tmp,/var/log,/proc,/sys')}}"
# Plugin configuration
$env.NUSHELL_PLUGINS_ENABLED = {% if taskserv.nushell_plugins | default(false) %}true{% else %}false{% endif %}
{% if taskserv.nushell_plugins | default(false) %}
$env.NUSHELL_PLUGIN_ALLOWLIST = "{{taskserv.nushell_plugin_allowlist | default('nu_plugin_kcl,nu_plugin_tera,nu_plugin_polars')}}"
{% endif %}
# KCL integration
$env.KCL_ENABLED = {% if taskserv.kcl_enabled | default(false) %}true{% else %}false{% endif %}
{% if taskserv.kcl_enabled | default(false) %}
$env.KCL_BINARY_PATH = "{{taskserv.kcl_binary_path | default('/usr/local/bin/kcl')}}"
{% endif %}
# Observability settings
$env.NUSHELL_METRICS_ENABLED = {% if taskserv.nushell_metrics | default(true) %}true{% else %}false{% endif %}
$env.NUSHELL_LOG_COLLECTION = {% if taskserv.nushell_log_collection | default(false) %}true{% else %}false{% endif %}
{% if taskserv.nushell_telemetry_endpoint | default("") != "" %}
$env.NUSHELL_TELEMETRY_ENDPOINT = "{{taskserv.nushell_telemetry_endpoint}}"
{% endif %}
# Provisioning integration
$env.PROVISIONING_NUSHELL_VERSION = "1.0.0"
$env.PROVISIONING_NUSHELL_MODE = "infrastructure"
# Security: Sanitize PATH to prevent privilege escalation
$env.PATH = ($env.PATH | split row (char esep) | where $it =~ "^/(usr/)?(local/)?bin$|^/(usr/)?sbin$" | str join (char esep))
# Add Nushell tools to PATH if they exist
if ("{{taskserv.admin_user_home}}/.local/bin" | path exists) {
$env.PATH = ($env.PATH | split row (char esep) | prepend "{{taskserv.admin_user_home}}/.local/bin" | str join (char esep))
}
# Default editor for security (read-only contexts)
{% if taskserv.nushell_readonly | default(true) %}
$env.EDITOR = "cat"
$env.VISUAL = "cat"
{% else %}
$env.EDITOR = "{{taskserv.editor | default('nano')}}"
$env.VISUAL = "{{taskserv.visual_editor | default('nano')}}"
{% endif %}
# Logging configuration
$env.NU_LOG_LEVEL = "{{taskserv.nushell_log_level | default('info')}}"
$env.NU_LOG_FORMAT = "json"
$env.NU_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# Network restrictions
{% if taskserv.nushell_network | default(false) %}
$env.NUSHELL_NETWORK_ENABLED = true
{% else %}
$env.NUSHELL_NETWORK_ENABLED = false
# Disable network access for security
$env.http_proxy = "127.0.0.1:9999"
$env.https_proxy = "127.0.0.1:9999"
{% endif %}
# Session information
$env.NUSHELL_SESSION_ID = (random uuid)
$env.NUSHELL_SESSION_START = (date now | format date "%Y-%m-%d %H:%M:%S")
$env.NUSHELL_SERVER_ROLE = "{{server.role | default('worker')}}"
$env.NUSHELL_SERVER_HOSTNAME = "{{server.hostname | default('unknown')}}"
# Startup message
if not ($env.NUSHELL_QUIET? | default false) {
print $"🔧 Nushell Infrastructure Runtime v($env.PROVISIONING_NUSHELL_VERSION)"
print $"🏷️ Server: ($env.NUSHELL_SERVER_HOSTNAME) | Role: ($env.NUSHELL_SERVER_ROLE)"
print $"🛡️ Security: ($env.NUSHELL_EXECUTION_MODE) mode | Readonly: ($env.NUSHELL_READONLY_MODE)"
if $env.NUSHELL_AUDIT_ENABLED {
print $"📝 Audit logging enabled: ($env.NUSHELL_AUDIT_FILE)"
}
}

View file

@ -0,0 +1,437 @@
#!/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 "$@"

View file

@ -0,0 +1,57 @@
#!/usr/bin/env nu
# Info: Prepare for nushell installation on infrastructure servers
# Author: Generated by Claude Code
# Release: 1.0.0
# Date: 2025-09-23
use lib_provisioning/cmd/env.nu *
use lib_provisioning/cmd/lib.nu *
use lib_provisioning/utils/ui.nu *
print $"(_ansi green_bold)Nushell Runtime(_ansi reset) with ($env.PROVISIONING_VARS)"
let defs = load_defs
# Ensure target environment path exists
let target_path = $env.PROVISIONING_WK_ENV_PATH
^mkdir -p $"($target_path)/.config/nushell"
^mkdir -p $"($target_path)/.local/bin"
^mkdir -p $"($target_path)/.local/share/nushell"
# Create secure directory for Nushell scripts
^mkdir -p $"($target_path)/nushell/scripts"
^mkdir -p $"($target_path)/nushell/observability"
^mkdir -p $"($target_path)/nushell/lib"
# Set secure permissions for Nushell directories
^chmod 750 $"($target_path)/nushell"
^chmod 700 $"($target_path)/nushell/scripts"
# Create plugin directory if plugins are enabled
if ($defs.taskserv.nushell_plugins? | default false) {
^mkdir -p $"($target_path)/.local/share/nushell/plugins"
log_debug "Created Nushell plugins directory"
}
# Copy SSH keys if specified for remote operations
let ssh_keys = ($defs.taskserv.ssh_keys? | default "" | str replace "~" $env.HOME | str trim)
if $ssh_keys != "" {
^mkdir -p $"($target_path)/.ssh"
for key in ($ssh_keys | split row " ") {
log_debug $"Setting up SSH key: ($key)"
if ($key | path exists) {
cp $key $"($target_path)/.ssh"
^chmod 600 $"($target_path)/.ssh/($key | path basename)"
}
if ($"($key).pub" | path exists) {
cp $"($key).pub" $"($target_path)/.ssh"
^chmod 644 $"($target_path)/.ssh/($key | path basename).pub"
}
}
}
# Ensure proper ownership for security
let admin_user = ($defs.taskserv.admin_user? | default "root")
^chown -R $"($admin_user):($admin_user)" $"($target_path)/nushell"
log_info "Nushell environment prepared successfully"

View file

@ -0,0 +1,262 @@
# Remote Execution Library for Nushell Infrastructure
# Secure, audited remote script execution capabilities
# Execute a Nushell script remotely with security restrictions
export def nu-remote-exec [
script_path: string # Path to the Nushell script to execute
--readonly(-r) # Force read-only mode
--timeout(-t): int = 300 # Execution timeout in seconds
--audit(-a) # Enable audit logging
] -> record {
# Validate script path
if not ($script_path | path exists) {
return {
success: false
error: $"Script not found: ($script_path)"
output: ""
duration: 0
}
}
# Security checks
let allowed_paths = ($env.NUSHELL_ALLOWED_PATHS? | default "/tmp,/var/log,/proc,/sys" | split row ",")
let script_dir = ($script_path | path dirname)
if not ($allowed_paths | any {|path| $script_dir | str starts-with $path}) {
return {
success: false
error: $"Script path not in allowed directories: ($script_dir)"
output: ""
duration: 0
}
}
# Prepare execution environment
let start_time = (date now)
let session_id = (random uuid)
# Audit logging if enabled
if ($audit or ($env.NUSHELL_AUDIT_ENABLED? | default false)) {
let audit_entry = {
timestamp: ($start_time | format date "%Y-%m-%d %H:%M:%S")
session_id: $session_id
action: "remote-exec"
script: $script_path
readonly: $readonly
user: ($env.USER? | default "unknown")
hostname: ($env.HOSTNAME? | default "unknown")
}
$audit_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
}
# Build execution command with security flags
mut nu_args = ["--no-config-file"]
if $readonly or ($env.NUSHELL_READONLY_MODE? | default true) {
$nu_args = ($nu_args | append "--no-history")
}
# Set resource limits
let memory_limit = ($env.NUSHELL_MAX_MEMORY? | default "256MB")
let cpu_time = ($env.NUSHELL_MAX_CPU_TIME? | default "30s")
try {
# Execute with timeout and resource limits
let result = (timeout $"($timeout)s" nu ...$nu_args $script_path | complete)
let end_time = (date now)
let duration = (($end_time - $start_time) / 1ms | math round)
# Log completion
if ($audit or ($env.NUSHELL_AUDIT_ENABLED? | default false)) {
let completion_entry = {
timestamp: ($end_time | format date "%Y-%m-%d %H:%M:%S")
session_id: $session_id
action: "remote-exec-complete"
exit_code: $result.exit_code
duration_ms: $duration
output_lines: ($result.stdout | lines | length)
}
$completion_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
}
return {
success: ($result.exit_code == 0)
error: $result.stderr
output: $result.stdout
duration: $duration
exit_code: $result.exit_code
session_id: $session_id
}
} catch { |err|
let end_time = (date now)
let duration = (($end_time - $start_time) / 1ms | math round)
# Log error
if ($audit or ($env.NUSHELL_AUDIT_ENABLED? | default false)) {
let error_entry = {
timestamp: ($end_time | format date "%Y-%m-%d %H:%M:%S")
session_id: $session_id
action: "remote-exec-error"
error: ($err | get msg)
duration_ms: $duration
}
$error_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
}
return {
success: false
error: ($err | get msg)
output: ""
duration: $duration
exit_code: 1
session_id: $session_id
}
}
}
# Execute a command pipeline remotely with streaming output
export def nu-remote-stream [
command: string # Command to execute
--filter(-f): string # Optional filter expression
--format: string = "table" # Output format (table, json, yaml)
--lines(-l): int # Limit output lines
] -> any {
# Security validation
let blocked_commands = ($env.NUSHELL_BLOCKED_COMMANDS? | default "" | split row ",")
let cmd_parts = ($command | split row " ")
let cmd_name = ($cmd_parts | first)
if $cmd_name in $blocked_commands {
error make {msg: $"Command '($cmd_name)' is blocked for security reasons"}
}
# Build pipeline
mut pipeline = $command
if ($filter | is-not-empty) {
$pipeline = $"($pipeline) | ($filter)"
}
if ($lines | is-not-empty) {
$pipeline = $"($pipeline) | first ($lines)"
}
# Format output
match $format {
"json" => { $pipeline = $"($pipeline) | to json" }
"yaml" => { $pipeline = $"($pipeline) | to yaml" }
"csv" => { $pipeline = $"($pipeline) | to csv" }
_ => { $pipeline = $"($pipeline) | table" }
}
# Execute with audit
if ($env.NUSHELL_AUDIT_ENABLED? | default false) {
let audit_entry = {
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
action: "remote-stream"
command: $command
filter: ($filter | default "")
format: $format
}
$audit_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
}
# Execute the pipeline
nu -c $pipeline
}
# Validate script security before execution
export def nu-validate-script [
script_path: string
] -> record {
if not ($script_path | path exists) {
return {valid: false, reason: "Script file not found"}
}
let content = (open $script_path)
let blocked_patterns = [
"rm -rf"
"sudo"
"su -"
"chmod 777"
"wget http://"
"curl http://"
"nc -"
"telnet"
"/dev/tcp"
"eval"
"exec"
]
for pattern in $blocked_patterns {
if ($content | str contains $pattern) {
return {
valid: false
reason: $"Script contains blocked pattern: ($pattern)"
}
}
}
# Check for allowed paths only
let allowed_paths = ($env.NUSHELL_ALLOWED_PATHS? | default "/tmp,/var/log,/proc,/sys" | split row ",")
let path_accesses = ($content | find -r "/(etc|root|home|usr/bin)" | length)
if $path_accesses > 0 {
return {
valid: false
reason: "Script accesses restricted system paths"
}
}
return {valid: true, reason: "Script validation passed"}
}
# Health check for remote Nushell environment
export def nu-health-check [] -> record {
let start_time = (date now)
mut health = {
status: "healthy"
checks: {}
timestamp: ($start_time | format date "%Y-%m-%d %H:%M:%S")
}
# Check Nushell version
try {
let version = (version | get version)
$health.checks = ($health.checks | insert nushell_version {status: "ok", value: $version})
} catch {
$health.checks = ($health.checks | insert nushell_version {status: "error", value: "unknown"})
$health.status = "degraded"
}
# Check environment variables
let required_vars = ["NUSHELL_HOME", "NUSHELL_EXECUTION_MODE"]
for var in $required_vars {
if ($env | get -i $var | is-empty) {
$health.checks = ($health.checks | insert $"env_($var)" {status: "error", value: "missing"})
$health.status = "unhealthy"
} else {
$health.checks = ($health.checks | insert $"env_($var)" {status: "ok", value: ($env | get $var)})
}
}
# Check disk space
try {
let disk_usage = (df -h | where Filesystem =~ "/" | first | get "Use%")
$health.checks = ($health.checks | insert disk_usage {status: "ok", value: $disk_usage})
} catch {
$health.checks = ($health.checks | insert disk_usage {status: "error", value: "unknown"})
}
# Check memory usage
try {
let mem_info = (free -m | lines | get 1 | split row " " | where $it != "" | get 2)
$health.checks = ($health.checks | insert memory_mb {status: "ok", value: $mem_info})
} catch {
$health.checks = ($health.checks | insert memory_mb {status: "error", value: "unknown"})
}
return $health
}

486
taskservs/nushell/info.md Normal file
View file

@ -0,0 +1,486 @@
# Nushell Infrastructure Runtime Task Service
> **Security-First Shell Runtime for Cloud Native Infrastructure**
A secure, auditable Nushell runtime designed specifically for infrastructure servers, providing powerful scripting capabilities while maintaining strict security controls and operational safety.
## 🎯 Purpose
The Nushell task service provides a modern, secure shell environment for:
- **Remote Script Execution**: Safe execution of automation scripts on infrastructure servers
- **Observability**: Structured data collection for metrics, logs, and telemetry
- **Infrastructure Management**: Configuration-driven server management and orchestration
- **Security Auditing**: Complete audit trail of all shell operations and command execution
## 🏗️ Architecture
### Core Components
```
taskservs/nushell/
├── default/
│ ├── prepare # Environment preparation script
│ ├── install-nushell.sh # Nushell binary installation
│ ├── env-nushell.j2 # Environment configuration template
│ ├── config.nu.j2 # Secure Nushell configuration
│ ├── env.nu.j2 # Environment variables template
│ └── remote-exec.nu.j2 # Remote execution library
└── observability/
├── collect.nu # System metrics collection
├── process.nu # Log processing and analysis
└── telemetry.nu # Telemetry and monitoring
```
### Security Model
- **Read-Only Mode**: Default execution prevents destructive operations
- **Command Filtering**: Allowlist/blocklist for command execution
- **Path Restrictions**: Limited file system access to safe directories
- **Session Timeouts**: Automatic session termination for security
- **Audit Logging**: Complete command and operation logging
- **Resource Limits**: CPU, memory, and network usage controls
## 📋 Installation Options
### 1. Standalone Installation
Install Nushell as a dedicated task service:
```bash
./core/nulib/provisioning taskserv create nushell --infra <infrastructure-name>
```
### 2. Integrated with OS Base Layer
Enable during OS installation for automatic deployment:
```toml
# In your infrastructure configuration
[taskserv]
install_nushell = true
nushell_readonly = true
nushell_plugins = false
nushell_execution_mode = "restricted"
```
### 3. Conditional Installation by Server Role
Configure role-based installation:
```toml
# Control nodes: Always install
# Worker nodes: Optional based on needs
[server.control]
install_nushell = true
[server.worker]
install_nushell = false # or true if needed
```
## ⚙️ Configuration
### Security Settings
| Setting | Default | Description |
|---------|---------|-------------|
| `nushell_readonly` | `true` | Enable read-only mode (blocks write operations) |
| `nushell_execution_mode` | `"restricted"` | Execution mode: `restricted`, `normal`, `privileged` |
| `nushell_plugins` | `false` | Enable Nushell plugins (kcl, tera, polars) |
| `nushell_network` | `false` | Allow network operations |
| `nushell_audit` | `true` | Enable audit logging |
| `nushell_session_timeout` | `900` | Session timeout in seconds (15 minutes) |
### Resource Limits
| Setting | Default | Description |
|---------|---------|-------------|
| `nushell_max_memory` | `"256MB"` | Maximum memory usage |
| `nushell_max_cpu_time` | `"30s"` | Maximum CPU time per command |
| `nushell_remote_timeout` | `300` | Remote execution timeout (seconds) |
### Command Restrictions
```toml
[taskserv]
nushell_allowed_commands = "ls,cat,grep,ps,df,free,uptime,systemctl,kubectl"
nushell_blocked_commands = "rm,mv,cp,chmod,chown,sudo,su"
nushell_allowed_paths = "/tmp,/var/log,/proc,/sys"
```
## 🚀 Usage Examples
### Basic System Information
```nu
# Check system health
use /home/admin/nushell/observability/collect.nu *
status-check
# Collect system metrics
collect-system-metrics | to json
# Monitor for 5 minutes with 30s intervals
health-monitor --duration 300 --interval 30
```
### Log Analysis
```nu
# Parse and analyze logs
use /home/admin/nushell/observability/process.nu *
# Parse system logs
cat /var/log/syslog | lines | parse-logs --format syslog
# Detect anomalies in recent logs
collect-logs --since "1h" --level "error" | detect-anomalies --threshold 2.0
# Generate comprehensive log summary
collect-logs --since "24h" | generate-summary --include-patterns --include-anomalies
```
### Remote Execution
```nu
# Execute script with security validation
use /home/admin/nushell/lib/remote-exec.nu *
# Validate script before execution
nu-validate-script "/tmp/monitoring-script.nu"
# Execute with audit logging
nu-remote-exec "/tmp/monitoring-script.nu" --readonly --audit --timeout 120
# Stream live data with filtering
nu-remote-stream "ps aux" --filter "where cpu > 10" --format json --lines 20
```
### Telemetry Integration
```nu
# Initialize telemetry
use /home/admin/nushell/observability/telemetry.nu *
# Configure telemetry endpoint
init-telemetry --endpoint "https://monitoring.example.com/api/metrics" --enable-health
# Start health monitoring with alerts
health-monitoring --check-interval 60 --alert-endpoint "https://alerts.example.com/webhook"
# Send custom metrics
let custom_metrics = {app_name: "web-server", response_time: 250, status: "healthy"}
send-telemetry $custom_metrics --format "prometheus"
```
## 🔧 Integration Patterns
### SSH Remote Execution
Execute Nushell scripts remotely via SSH:
```bash
# Execute health check on remote server
ssh server-01 'nu /home/admin/nushell/observability/collect.nu -c "status-check | to json"'
# Stream metrics collection
ssh server-01 'nu -c "use /home/admin/nushell/observability/collect.nu *; health-monitor --duration 60 --interval 10"'
```
### Container Integration
Use in containerized environments:
```dockerfile
# Add to your infrastructure containers
RUN curl -L https://github.com/nushell/nushell/releases/download/0.107.1/nu-0.107.1-x86_64-unknown-linux-gnu.tar.gz | tar xz
COPY nushell-config/ /home/app/.config/nushell/
ENV NUSHELL_EXECUTION_MODE=restricted
ENV NUSHELL_READONLY_MODE=true
```
### Kubernetes Jobs
Deploy as Kubernetes jobs for infrastructure tasks:
```yaml
apiVersion: batch/v1
kind: Job
metadata:
name: nushell-health-check
spec:
template:
spec:
containers:
- name: nushell
image: infrastructure/nushell-runtime:latest
command: ["nu"]
args: ["/scripts/health-check.nu"]
env:
- name: NUSHELL_EXECUTION_MODE
value: "restricted"
- name: NUSHELL_AUDIT_ENABLED
value: "true"
```
## 🛡️ Security Features
### Command Validation
- **Pre-execution validation**: All commands validated before execution
- **Pattern matching**: Blocked dangerous command patterns
- **Path validation**: File access restricted to safe directories
- **Resource monitoring**: CPU, memory, and network usage tracked
### Audit Trail
Complete audit logging includes:
- Command execution timestamps
- User context and session information
- Input/output data (configurable)
- Error conditions and failures
- Resource usage metrics
### Session Management
- **Automatic timeouts**: Sessions expire after inactivity
- **Resource limits**: Memory and CPU usage constraints
- **Network isolation**: Optional network access controls
- **Privilege separation**: Non-privileged execution by default
## 📊 Monitoring and Observability
### Health Checks
```nu
# System health overview
status-check
# Detailed health monitoring
health-monitor --interval 30 --duration 600 --output "/tmp/health-data.json"
# Remote health validation
nu-health-check
```
### Metrics Collection
Automated collection of:
- **System metrics**: CPU, memory, disk, network
- **Process metrics**: Running processes, resource usage
- **Container metrics**: Docker/Podman container status
- **Kubernetes metrics**: Pod status, resource utilization
### Log Processing
Advanced log analysis capabilities:
- **Multi-format parsing**: JSON, syslog, Apache, Nginx
- **Pattern extraction**: Error patterns, IP addresses, URLs
- **Anomaly detection**: Statistical analysis of log patterns
- **Time-series aggregation**: Bucketed metrics over time
## 🔄 Deployment Strategies
### Development/Testing
```toml
[taskserv]
install_nushell = true
nushell_readonly = false
nushell_plugins = true
nushell_execution_mode = "normal"
nushell_network = true
```
### Production
```toml
[taskserv]
install_nushell = true
nushell_readonly = true
nushell_plugins = false
nushell_execution_mode = "restricted"
nushell_network = false
nushell_audit = true
nushell_session_timeout = 600
```
### High-Security Environments
```toml
[taskserv]
install_nushell = true
nushell_readonly = true
nushell_plugins = false
nushell_execution_mode = "restricted"
nushell_network = false
nushell_audit = true
nushell_session_timeout = 300
nushell_allowed_commands = "ls,cat,ps,df,free"
nushell_blocked_commands = "rm,mv,cp,chmod,chown,sudo,su,wget,curl"
```
## 🚨 Troubleshooting
### Common Issues
**1. Permission Denied Errors**
```nu
# Check file permissions
ls -la /home/admin/nushell/
# Verify ownership
ls -la ~/.config/nushell/
```
**2. Command Blocked in Read-Only Mode**
```nu
# Check execution mode
echo $env.NUSHELL_EXECUTION_MODE
# Review blocked commands
echo $env.NUSHELL_BLOCKED_COMMANDS
```
**3. Session Timeout Issues**
```nu
# Check session timeout setting
echo $env.NUSHELL_SESSION_TIMEOUT
# Review audit log for session activity
cat ~/nushell/audit.log | tail -20
```
**4. Plugin Loading Failures**
```nu
# Register plugins manually
nu ~/nushell/scripts/register-plugins.nu
# Check plugin availability
plugin list
```
### Debug Mode
Enable debug logging for troubleshooting:
```bash
# Set debug environment
export NUSHELL_LOG_LEVEL=debug
export NU_LOG_LEVEL=debug
# Run with verbose output
nu --log-level debug /path/to/script.nu
```
### Performance Issues
Monitor resource usage:
```nu
# Check memory usage
free -h
# Monitor CPU usage
top -p $(pgrep nu)
# Review resource limits
echo $env.NUSHELL_MAX_MEMORY
echo $env.NUSHELL_MAX_CPU_TIME
```
## 📈 Performance Considerations
### Resource Usage
- **Memory footprint**: ~50-100MB base usage
- **CPU overhead**: Minimal during idle, scales with workload
- **Disk usage**: ~20MB for binary + configs
- **Network impact**: Controlled by security settings
### Optimization Tips
1. **Use streaming operations** for large datasets
2. **Enable read-only mode** for security and performance
3. **Limit session timeouts** to prevent resource leaks
4. **Use structured data formats** (JSON, YAML) for efficiency
5. **Batch telemetry data** to reduce network overhead
## 🔗 Integration with Other Services
### KCL Configuration Language
Optional integration with KCL for configuration validation:
```nu
# Install KCL plugin (if enabled)
plugin add nu_plugin_kcl
# Validate KCL configurations
kcl run infrastructure.k | to json | from json
```
### Monitoring Systems
Compatible with popular monitoring solutions:
- **Prometheus**: Native metrics export format
- **InfluxDB**: Line protocol support
- **Grafana**: JSON data source integration
- **ELK Stack**: Structured log output
### CI/CD Pipelines
Use in automation workflows:
```yaml
# GitHub Actions example
- name: Infrastructure Health Check
run: |
ssh ${{ secrets.SERVER_HOST }} 'nu -c "
use /home/admin/nushell/observability/collect.nu *;
status-check | to json
"'
```
## 📚 Best Practices
### Security
1. **Always use read-only mode** in production
2. **Implement session timeouts** appropriate for your environment
3. **Enable audit logging** for compliance and debugging
4. **Restrict network access** unless specifically required
5. **Regularly review command allowlists** and access patterns
### Performance
1. **Use streaming operations** for processing large datasets
2. **Implement data pagination** for long-running queries
3. **Cache frequently accessed data** using structured formats
4. **Monitor resource usage** and adjust limits as needed
### Operational
1. **Document custom scripts** and their intended usage
2. **Test scripts in development** before production deployment
3. **Implement proper error handling** in automation scripts
4. **Use version control** for custom observability scripts
5. **Regular security audits** of command patterns and access logs
## 🆕 Version History
- **v1.0.0** (2025-09-23): Initial release with core security features
- Secure configuration templates
- Remote execution library
- Observability and telemetry integration
- OS base layer integration
- Comprehensive audit logging
## 🤝 Contributing
This task service is part of the cloud-native provisioning system. For improvements or issues:
1. Follow the project's architecture principles (PAP)
2. Ensure all changes maintain security-first approach
3. Test configurations in development environments
4. Document security implications of changes
5. Include appropriate test coverage for new features
## 📄 License
Part of the cloud-native provisioning system. See project license for details.

View file

@ -0,0 +1,347 @@
# Observability Collection Scripts for Nushell Infrastructure
# Secure collection of system metrics, logs, and telemetry data
# Collect comprehensive system metrics
export def collect-system-metrics []: nothing -> record {
let timestamp = (date now)
let base_metrics = {
timestamp: ($timestamp | format date "%Y-%m-%d %H:%M:%S")
hostname: ($env.HOSTNAME? | default "unknown")
collection_version: "1.0.0"
}
# CPU metrics
let cpu_metrics = try {
let cpu_info = (cat /proc/cpuinfo | lines | where $it =~ "processor|model name|cpu MHz" | parse "{key}: {value}")
let cpu_count = ($cpu_info | where key == "processor" | length)
let cpu_model = ($cpu_info | where key =~ "model name" | first | get value)
# Load average
let loadavg = (cat /proc/loadavg | split row " ")
{
cores: $cpu_count
model: $cpu_model
load_1m: ($loadavg | get 0 | into float)
load_5m: ($loadavg | get 1 | into float)
load_15m: ($loadavg | get 2 | into float)
}
} catch {
{error: "Failed to collect CPU metrics"}
}
# Memory metrics
try {
let meminfo = (cat /proc/meminfo | lines | parse "{key}: {value} kB")
let total_mem = ($meminfo | where key == "MemTotal" | first | get value | into int)
let free_mem = ($meminfo | where key == "MemFree" | first | get value | into int)
let available_mem = ($meminfo | where key == "MemAvailable" | first | get value | into int)
let buffers = ($meminfo | where key == "Buffers" | first | get value | into int)
let cached = ($meminfo | where key == "Cached" | first | get value | into int)
$metrics = ($metrics | insert memory {
total_kb: $total_mem
free_kb: $free_mem
available_kb: $available_mem
buffers_kb: $buffers
cached_kb: $cached
used_kb: ($total_mem - $free_mem)
usage_percent: (($total_mem - $free_mem) / $total_mem * 100 | math round --precision 2)
})
} catch {
$metrics = ($metrics | insert memory {error: "Failed to collect memory metrics"})
}
# Disk metrics
try {
let disk_usage = (df -k | lines | skip 1 | parse "{filesystem} {total} {used} {available} {percent} {mount}")
$metrics = ($metrics | insert disk ($disk_usage | select filesystem total used available percent mount))
} catch {
$metrics = ($metrics | insert disk {error: "Failed to collect disk metrics"})
}
# Network metrics (basic)
try {
let network_stats = (cat /proc/net/dev | lines | skip 2 | parse "{interface}: {rx_bytes} {rx_packets} {rx_errs} {rx_drop} {rx_fifo} {rx_frame} {rx_compressed} {rx_multicast} {tx_bytes} {tx_packets} {tx_errs} {tx_drop} {tx_fifo} {tx_colls} {tx_carrier} {tx_compressed}")
$metrics = ($metrics | insert network ($network_stats | select interface rx_bytes tx_bytes rx_packets tx_packets))
} catch {
$metrics = ($metrics | insert network {error: "Failed to collect network metrics"})
}
# Process count
try {
let process_count = (ls /proc | where name =~ "^[0-9]+$" | length)
$metrics = ($metrics | insert processes {
total: $process_count
})
} catch {
$metrics = ($metrics | insert processes {error: "Failed to collect process metrics"})
}
return $metrics
}
# Collect container metrics (if running in containerized environment)
export def collect-container-metrics []: nothing -> record {
let timestamp = (date now)
mut metrics = {
timestamp: ($timestamp | format date "%Y-%m-%d %H:%M:%S")
container_runtime: "unknown"
}
# Check for Docker
try {
if (which docker | is-not-empty) {
let containers = (docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | lines | skip 1)
$metrics = ($metrics | insert docker {
available: true
containers: ($containers | length)
running: ($containers | where $it =~ "Up" | length)
})
$metrics = ($metrics | insert container_runtime "docker")
}
} catch {}
# Check for Podman
try {
if (which podman | is-not-empty) {
let containers = (podman ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | lines | skip 1)
$metrics = ($metrics | insert podman {
available: true
containers: ($containers | length)
running: ($containers | where $it =~ "Up" | length)
})
if ($metrics.container_runtime == "unknown") {
$metrics = ($metrics | insert container_runtime "podman")
}
}
} catch {}
# Check for Kubernetes
try {
if (which kubectl | is-not-empty) {
let pods = (kubectl get pods --all-namespaces --no-headers | lines)
$metrics = ($metrics | insert kubernetes {
available: true
pods_total: ($pods | length)
pods_running: ($pods | where $it =~ "Running" | length)
pods_pending: ($pods | where $it =~ "Pending" | length)
pods_failed: ($pods | where $it =~ "Failed" | length)
})
}
} catch {}
return $metrics
}
# Collect application logs with filtering
export def collect-logs [
--service(-s): string # Specific service to collect logs from
--since: string = "1h" # Time range (1h, 30m, etc.)
--level: string = "error" # Log level filter
--lines(-l): int = 100 # Maximum lines to collect
]: nothing -> list<record> {
mut logs = []
# Systemd journal logs
try {
mut journalctl_cmd = ["journalctl", "--output=json", "--no-pager", $"--since=($since)"]
if ($service | is-not-empty) {
$journalctl_cmd = ($journalctl_cmd | append ["-u", $service])
}
if (($level | is-not-empty) and ($level != "all")) {
$journalctl_cmd = ($journalctl_cmd | append ["-p", $level])
}
if ($lines | is-not-empty) {
$journalctl_cmd = ($journalctl_cmd | append ["-n", ($lines | into string)])
}
let journal_logs = (^$journalctl_cmd.0 ...$journalctl_cmd.1 | lines | where $it != "" | each { |line| $line | from json })
$logs = ($logs | append $journal_logs)
} catch {}
# Container logs (Docker)
try {
if (which docker | is-not-empty and ($service | is-not-empty)) {
let container_logs = (docker logs --since $since --tail $lines $service 2>/dev/null | lines | enumerate | each { |item|
{
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
source: "docker"
container: $service
message: $item.item
line_number: $item.index
}
})
$logs = ($logs | append $container_logs)
}
} catch {}
# File-based logs (common locations)
let log_files = [
"/var/log/syslog"
"/var/log/messages"
"/var/log/kern.log"
"/var/log/auth.log"
]
for log_file in $log_files {
try {
if ($log_file | path exists) {
let file_logs = (tail -n $lines $log_file | lines | enumerate | each { |item|
{
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
source: "file"
file: $log_file
message: $item.item
line_number: $item.index
}
})
$logs = ($logs | append $file_logs)
}
} catch {}
}
return ($logs | first $lines)
}
# Process and analyze log patterns
export def analyze-logs [logs: list<record>]: nothing -> record {
let total_logs = ($logs | length)
if $total_logs == 0 {
return {
total: 0
analysis: "No logs to analyze"
}
}
# Error pattern analysis
let error_patterns = ["error", "failed", "exception", "critical", "fatal"]
mut error_counts = {}
for pattern in $error_patterns {
let count = ($logs | where message =~ $"(?i)($pattern)" | length)
$error_counts = ($error_counts | insert $pattern $count)
}
# Source distribution
let source_dist = ($logs | group-by source | transpose key value | each { |item|
{source: $item.key, count: ($item.value | length)}
})
# Time-based analysis (last hour)
let recent_logs = ($logs | where timestamp > ((date now) - 1hr))
return {
total: $total_logs
recent_count: ($recent_logs | length)
error_patterns: $error_counts
source_distribution: $source_dist
analysis_timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
}
}
# Export metrics in various formats
export def export-metrics [
metrics: record
--format(-f): string = "json" # json, yaml, csv
--output(-o): string # Output file path
]: nothing -> any {
let formatted_data = match $format {
"yaml" => ($metrics | to yaml)
"csv" => {
# Flatten metrics for CSV export
let flattened = ($metrics | flatten | transpose key value)
$flattened | to csv
}
_ => ($metrics | to json)
}
if ($output | is-not-empty) {
$formatted_data | save $output
print $"Metrics exported to ($output)"
} else {
$formatted_data
}
}
# Health monitoring function
export def health-monitor [
--interval(-i): int = 60 # Collection interval in seconds
--duration(-d): int = 300 # Total monitoring duration in seconds
--output(-o): string # Output file for continuous monitoring
]: nothing -> nothing {
let start_time = (date now)
let end_time = ($start_time + ($duration * 1sec))
print $"🔍 Starting health monitoring for ($duration) seconds with ($interval)s intervals"
print $"📊 Collecting system and container metrics"
while (date now) < $end_time {
let current_time = (date now)
let system_metrics = (collect-system-metrics)
let container_metrics = (collect-container-metrics)
let combined_metrics = {
collection_time: ($current_time | format date "%Y-%m-%d %H:%M:%S")
system: $system_metrics
containers: $container_metrics
}
if ($output | is-not-empty) {
$combined_metrics | to json | save -a $output
} else {
print $"⏰ ($current_time | format date "%H:%M:%S") - CPU: ($system_metrics.cpu.load_1m?)% | Memory: ($system_metrics.memory.usage_percent?)%"
}
sleep ($interval * 1sec)
}
print "✅ Health monitoring completed"
}
# Quick system status check
export def status-check []: nothing -> record {
let system = (collect-system-metrics)
let containers = (collect-container-metrics)
# Determine overall health
mut health_status = "healthy"
mut alerts = []
# CPU load check
if (($system.cpu.load_1m? | default 0) > 4.0) {
$health_status = "warning"
$alerts = ($alerts | append "High CPU load")
}
# Memory usage check
if (($system.memory.usage_percent? | default 0) > 90) {
$health_status = "critical"
$alerts = ($alerts | append "High memory usage")
}
# Disk usage check
try {
let high_disk = ($system.disk | where {|x| ($x.percent | str replace "%" "" | into float) > 90})
if ($high_disk | length) > 0 {
$health_status = "warning"
$alerts = ($alerts | append "High disk usage")
}
} catch {}
return {
status: $health_status
alerts: $alerts
metrics: {
system: $system
containers: $containers
}
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
}
}

View file

@ -0,0 +1,419 @@
# Log Processing and Analysis Scripts for Nushell Infrastructure
# Advanced log parsing, filtering, and transformation capabilities
# Parse structured logs from various formats
export def parse-logs [
--format(-f): string = "auto" # json, syslog, apache, nginx, auto
--filter: string # Filter expression
--transform: string # Transform expression
] -> list<record> {
let input_data = $in
# Auto-detect format if not specified
let detected_format = if $format == "auto" {
if ($input_data | first | str starts-with "{") {
"json"
} else if ($input_data | first | str contains "T") {
"syslog"
} else {
"text"
}
} else {
$format
}
# Parse based on format
mut parsed_logs = match $detected_format {
"json" => {
$input_data | lines | where $it != "" | each { |line|
try {
$line | from json
} catch {
{raw: $line, parse_error: true}
}
}
}
"syslog" => {
$input_data | lines | each { |line|
# RFC3164 syslog format: <priority>timestamp hostname tag: message
let syslog_pattern = '<(?P<priority>\d+)>(?P<timestamp>\w+\s+\d+\s+\d+:\d+:\d+)\s+(?P<hostname>\S+)\s+(?P<tag>\S+):\s*(?P<message>.*)'
try {
let matches = ($line | parse -r $syslog_pattern)
if ($matches | length) > 0 {
$matches | first
} else {
{raw: $line, format: "syslog"}
}
} catch {
{raw: $line, parse_error: true}
}
}
}
"apache" => {
$input_data | lines | each { |line|
# Apache Combined Log Format
let apache_pattern = '(?P<ip>\S+)\s+\S+\s+\S+\s+\[(?P<timestamp>[^\]]+)\]\s+"(?P<method>\S+)\s+(?P<url>\S+)\s+(?P<protocol>[^"]+)"\s+(?P<status>\d+)\s+(?P<size>\d+|-)\s+"(?P<referer>[^"]*)"\s+"(?P<user_agent>[^"]*)"'
try {
let matches = ($line | parse -r $apache_pattern)
if ($matches | length) > 0 {
$matches | first
} else {
{raw: $line, format: "apache"}
}
} catch {
{raw: $line, parse_error: true}
}
}
}
"nginx" => {
$input_data | lines | each { |line|
# Nginx default log format
let nginx_pattern = '(?P<ip>\S+)\s+-\s+-\s+\[(?P<timestamp>[^\]]+)\]\s+"(?P<method>\S+)\s+(?P<url>\S+)\s+(?P<protocol>[^"]+)"\s+(?P<status>\d+)\s+(?P<size>\d+)\s+"(?P<referer>[^"]*)"\s+"(?P<user_agent>[^"]*)"'
try {
let matches = ($line | parse -r $nginx_pattern)
if ($matches | length) > 0 {
$matches | first
} else {
{raw: $line, format: "nginx"}
}
} catch {
{raw: $line, parse_error: true}
}
}
}
_ => {
$input_data | lines | enumerate | each { |item|
{
line_number: $item.index
message: $item.item
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
}
}
}
}
# Apply filter if specified
if ($filter | is-not-empty) {
$parsed_logs = ($parsed_logs | filter { |log|
try {
nu -c $"($log) | ($filter)"
} catch {
false
}
})
}
# Apply transformation if specified
if ($transform | is-not-empty) {
$parsed_logs = ($parsed_logs | each { |log|
try {
nu -c $"($log) | ($transform)"
} catch {
$log
}
})
}
return $parsed_logs
}
# Aggregate logs by time windows
export def aggregate-by-time [
logs: list<record>
--window(-w): string = "1h" # Time window: 1m, 5m, 1h, 1d
--field(-f): string = "timestamp" # Timestamp field name
--metric(-m): string = "count" # Aggregation metric: count, sum, avg, max, min
--group(-g): string # Group by field
] -> list<record> {
# Parse time window
let window_duration = match $window {
"1m" => 60
"5m" => 300
"1h" => 3600
"1d" => 86400
_ => 3600 # Default to 1 hour
}
# Convert timestamps to epoch and create time buckets
mut processed_logs = ($logs | each { |log|
let timestamp_value = ($log | get -i $field | default (date now))
let epoch = ($timestamp_value | date to-timezone UTC | format date "%s" | into int)
let bucket = (($epoch / $window_duration) * $window_duration)
$log | insert time_bucket $bucket | insert epoch $epoch
})
# Group by time bucket and optional field
let grouped = if ($group | is-not-empty) {
$processed_logs | group-by time_bucket $group
} else {
$processed_logs | group-by time_bucket
}
# Aggregate based on metric
$grouped | transpose bucket logs | each { |bucket_data|
let bucket_timestamp = ($bucket_data.bucket | into int | into datetime | format date "%Y-%m-%d %H:%M:%S")
let logs_in_bucket = $bucket_data.logs
match $metric {
"count" => {
{
timestamp: $bucket_timestamp
window: $window
count: ($logs_in_bucket | length)
}
}
"sum" => {
# Requires a numeric field to sum
{
timestamp: $bucket_timestamp
window: $window
sum: ($logs_in_bucket | get value | math sum)
}
}
"avg" => {
{
timestamp: $bucket_timestamp
window: $window
average: ($logs_in_bucket | get value | math avg)
}
}
_ => {
{
timestamp: $bucket_timestamp
window: $window
count: ($logs_in_bucket | length)
logs: $logs_in_bucket
}
}
}
} | sort-by timestamp
}
# Detect anomalies in log patterns
export def detect-anomalies [
logs: list<record>
--field(-f): string = "message" # Field to analyze
--threshold(-t): float = 2.0 # Standard deviation threshold
--window(-w): string = "1h" # Time window for baseline
] -> list<record> {
# Calculate baseline statistics
let baseline_window = match $window {
"1m" => 60
"5m" => 300
"1h" => 3600
"1d" => 86400
_ => 3600
}
let now = (date now)
let baseline_start = ($now - ($baseline_window * 1sec))
# Filter logs for baseline period
let baseline_logs = ($logs | where {|log|
let log_time = ($log | get -i timestamp | default $now)
$log_time >= $baseline_start and $log_time <= $now
})
if ($baseline_logs | length) == 0 {
return []
}
# Count occurrences by time buckets
let time_series = ($baseline_logs | aggregate-by-time --window "5m" --field timestamp --metric count)
# Calculate statistics
let counts = ($time_series | get count)
let mean = ($counts | math avg)
let std_dev = ($counts | math stddev)
# Find anomalies (values beyond threshold standard deviations)
let anomaly_threshold_high = ($mean + ($threshold * $std_dev))
let anomaly_threshold_low = ($mean - ($threshold * $std_dev))
let anomalies = ($time_series | where {|bucket|
$bucket.count > $anomaly_threshold_high or $bucket.count < $anomaly_threshold_low
})
return ($anomalies | each { |anomaly|
$anomaly | insert {
anomaly_type: (if $anomaly.count > $anomaly_threshold_high { "spike" } else { "drop" })
severity: (if (($anomaly.count - $mean) | math abs) > (3 * $std_dev) { "high" } else { "medium" })
baseline_mean: $mean
baseline_stddev: $std_dev
}
})
}
# Extract patterns and insights from logs
export def extract-patterns [
logs: list<record>
--field(-f): string = "message" # Field to analyze
--pattern-type(-t): string = "error" # error, ip, url, email, custom
--custom-regex(-r): string # Custom regex pattern
--min-frequency(-m): int = 2 # Minimum pattern frequency
] -> list<record> {
let field_values = ($logs | get $field | where $it != null)
let patterns = match $pattern_type {
"error" => {
# Common error patterns
let error_regexes = [
'error:?\s*(.+)',
'exception:?\s*(.+)',
'failed:?\s*(.+)',
'timeout:?\s*(.+)',
'connection\s*refused:?\s*(.+)'
]
mut all_matches = []
for regex in $error_regexes {
let matches = ($field_values | each { |value|
try {
$value | parse -r $regex | each { |match| $match."capture0" }
} catch {
[]
}
} | flatten)
$all_matches = ($all_matches | append $matches)
}
$all_matches
}
"ip" => {
# IP address pattern
let ip_regex = '\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b'
$field_values | each { |value|
try {
$value | parse -r $ip_regex
} catch {
[]
}
} | flatten
}
"url" => {
# URL pattern
let url_regex = 'https?://[^\s<>"]+'
$field_values | each { |value|
try {
$value | parse -r $url_regex
} catch {
[]
}
} | flatten
}
"email" => {
# Email pattern
let email_regex = '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
$field_values | each { |value|
try {
$value | parse -r $email_regex
} catch {
[]
}
} | flatten
}
"custom" => {
if ($custom_regex | is-not-empty) {
$field_values | each { |value|
try {
$value | parse -r $custom_regex
} catch {
[]
}
} | flatten
} else {
[]
}
}
_ => []
}
# Count pattern frequencies
let pattern_counts = ($patterns | group-by {|x| $x} | transpose pattern occurrences | each { |item|
{
pattern: $item.pattern
frequency: ($item.occurrences | length)
examples: ($item.occurrences | first 3)
}
} | where frequency >= $min_frequency | sort-by frequency -r)
return $pattern_counts
}
# Generate log summary report
export def generate-summary [
logs: list<record>
--timeframe(-t): string = "24h" # Timeframe for analysis
--include-patterns(-p) # Include pattern analysis
--include-anomalies(-a) # Include anomaly detection
] -> record {
let total_logs = ($logs | length)
let start_time = (date now | format date "%Y-%m-%d %H:%M:%S")
if $total_logs == 0 {
return {
summary: "No logs to analyze"
timestamp: $start_time
total_logs: 0
}
}
# Basic statistics
let time_range = ($logs | get -i timestamp | default [] | each { |ts| $ts | date to-timezone UTC })
let earliest = ($time_range | math min)
let latest = ($time_range | math max)
# Log level distribution
let level_distribution = ($logs | get -i level | default [] | group-by {|x| $x} | transpose level count | each { |item|
{level: $item.level, count: ($item.count | length)}
} | sort-by count -r)
# Source distribution
let source_distribution = ($logs | get -i source | default [] | group-by {|x| $x} | transpose source count | each { |item|
{source: $item.source, count: ($item.count | length)}
} | sort-by count -r)
mut summary_report = {
analysis_timestamp: $start_time
timeframe: $timeframe
total_logs: $total_logs
time_range: {
earliest: ($earliest | format date "%Y-%m-%d %H:%M:%S")
latest: ($latest | format date "%Y-%m-%d %H:%M:%S")
duration_hours: ((($latest | date to-timezone UTC) - ($earliest | date to-timezone UTC)) / 1hr | math round --precision 2)
}
distribution: {
by_level: $level_distribution
by_source: $source_distribution
}
statistics: {
logs_per_hour: (($total_logs / ((($latest | date to-timezone UTC) - ($earliest | date to-timezone UTC)) / 1hr)) | math round --precision 2)
unique_sources: ($source_distribution | length)
error_rate: (($logs | where {|log| ($log | get -i level | default "") =~ "error|critical|fatal"} | length) / $total_logs * 100 | math round --precision 2)
}
}
# Add pattern analysis if requested
if $include_patterns {
let error_patterns = (extract-patterns $logs --pattern-type error --min-frequency 2)
let ip_patterns = (extract-patterns $logs --pattern-type ip --min-frequency 3)
$summary_report = ($summary_report | insert patterns {
errors: $error_patterns
ip_addresses: ($ip_patterns | first 10)
})
}
# Add anomaly detection if requested
if $include_anomalies {
let anomalies = (detect-anomalies $logs --threshold 2.0 --window "1h")
$summary_report = ($summary_report | insert anomalies {
detected: ($anomalies | length)
high_severity: ($anomalies | where severity == "high" | length)
details: ($anomalies | first 5)
})
}
return $summary_report
}

View file

@ -0,0 +1,50 @@
# Simple test script for Nushell infrastructure
# Validates basic functionality without complex dependencies
export def test-basic-functionality []: nothing -> record {
{
nushell_version: (version | get version)
current_time: (date now | format date "%Y-%m-%d %H:%M:%S")
hostname: ($env.HOSTNAME? | default "unknown")
user: ($env.USER? | default "unknown")
working_directory: $env.PWD
test_status: "passed"
}
}
export def test-security-environment []: nothing -> record {
{
readonly_mode: ($env.NUSHELL_READONLY_MODE? | default "unknown")
execution_mode: ($env.NUSHELL_EXECUTION_MODE? | default "unknown")
audit_enabled: ($env.NUSHELL_AUDIT_ENABLED? | default "unknown")
session_timeout: ($env.NUSHELL_SESSION_TIMEOUT? | default "unknown")
test_status: "passed"
}
}
export def test-file-operations []: nothing -> record {
let test_results = {
can_read_proc: (try { ls /proc | length } catch { 0 })
can_read_tmp: (try { ls /tmp | length } catch { 0 })
current_processes: (try { ps | length } catch { 0 })
disk_usage: (try { df | length } catch { 0 })
test_status: "completed"
}
$test_results
}
# Main test function
export def run-all-tests []: nothing -> record {
let basic_test = (test-basic-functionality)
let security_test = (test-security-environment)
let file_test = (test-file-operations)
{
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
basic_functionality: $basic_test
security_environment: $security_test
file_operations: $file_test
overall_status: "tests_completed"
}
}

View file

@ -0,0 +1,398 @@
# Telemetry and Monitoring Integration for Nushell Infrastructure
# Secure telemetry collection and forwarding capabilities
# Send telemetry data to configured endpoints
export def send-telemetry [
data: record
--endpoint(-e): string # Override default endpoint
--format(-f): string = "json" # json, prometheus, influx
--timeout(-t): int = 30 # Request timeout in seconds
--retry(-r): int = 3 # Number of retries
] -> record {
let telemetry_endpoint = ($endpoint | default ($env.NUSHELL_TELEMETRY_ENDPOINT? | default ""))
if ($telemetry_endpoint | is-empty) {
return {
success: false
error: "No telemetry endpoint configured"
data_sent: false
}
}
# Prepare data based on format
let formatted_data = match $format {
"prometheus" => {
# Convert to Prometheus exposition format
convert-to-prometheus $data
}
"influx" => {
# Convert to InfluxDB line protocol
convert-to-influx $data
}
_ => {
# Default JSON format
$data | to json
}
}
# Add metadata
let telemetry_payload = {
timestamp: (date now | format date "%Y-%m-%dT%H:%M:%S.%fZ")
hostname: ($env.HOSTNAME? | default "unknown")
agent: "nushell-provisioning"
version: "1.0.0"
data: $data
}
# Send data with retries
mut attempt = 1
while $attempt <= $retry {
try {
let response = (http post $telemetry_endpoint ($telemetry_payload | to json) --timeout ($timeout * 1000) --headers {"Content-Type": "application/json"})
return {
success: true
endpoint: $telemetry_endpoint
response_status: 200
attempt: $attempt
data_sent: true
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
}
} catch { |err|
if $attempt == $retry {
return {
success: false
error: ($err | get msg)
endpoint: $telemetry_endpoint
attempts: $attempt
data_sent: false
}
}
# Wait before retry (exponential backoff)
let wait_time = ($attempt * $attempt * 2)
sleep ($wait_time * 1sec)
}
$attempt = ($attempt + 1)
}
return {
success: false
error: "Max retries exceeded"
attempts: $retry
data_sent: false
}
}
# Convert metrics to Prometheus exposition format
def convert-to-prometheus [data: record] -> string {
mut prometheus_output = ""
# Process system metrics if available
if ($data | get -i system | is-not-empty) {
let sys = ($data | get system)
# CPU metrics
if ($sys | get -i cpu | is-not-empty) {
let cpu = ($sys | get cpu)
$prometheus_output = $prometheus_output + $"# HELP system_load_1m System load average over 1 minute\n"
$prometheus_output = $prometheus_output + $"# TYPE system_load_1m gauge\n"
$prometheus_output = $prometheus_output + $"system_load_1m{hostname=\"($env.HOSTNAME? | default 'unknown')\"} ($cpu.load_1m? | default 0)\n"
$prometheus_output = $prometheus_output + $"# HELP system_load_5m System load average over 5 minutes\n"
$prometheus_output = $prometheus_output + $"# TYPE system_load_5m gauge\n"
$prometheus_output = $prometheus_output + $"system_load_5m{hostname=\"($env.HOSTNAME? | default 'unknown')\"} ($cpu.load_5m? | default 0)\n"
}
# Memory metrics
if ($sys | get -i memory | is-not-empty) {
let mem = ($sys | get memory)
$prometheus_output = $prometheus_output + $"# HELP system_memory_usage_percent Memory usage percentage\n"
$prometheus_output = $prometheus_output + $"# TYPE system_memory_usage_percent gauge\n"
$prometheus_output = $prometheus_output + $"system_memory_usage_percent{hostname=\"($env.HOSTNAME? | default 'unknown')\"} ($mem.usage_percent? | default 0)\n"
$prometheus_output = $prometheus_output + $"# HELP system_memory_total_bytes Total memory in bytes\n"
$prometheus_output = $prometheus_output + $"# TYPE system_memory_total_bytes gauge\n"
$prometheus_output = $prometheus_output + $"system_memory_total_bytes{hostname=\"($env.HOSTNAME? | default 'unknown')\"} (($mem.total_kb? | default 0) * 1024)\n"
}
}
return $prometheus_output
}
# Convert metrics to InfluxDB line protocol
def convert-to-influx [data: record] -> string {
mut influx_lines = []
let timestamp = (date now | format date "%s%N")
let hostname = ($env.HOSTNAME? | default "unknown")
# Process system metrics
if ($data | get -i system | is-not-empty) {
let sys = ($data | get system)
# CPU metrics
if ($sys | get -i cpu | is-not-empty) {
let cpu = ($sys | get cpu)
$influx_lines = ($influx_lines | append $"system_cpu,hostname=($hostname) load_1m=($cpu.load_1m? | default 0),load_5m=($cpu.load_5m? | default 0),load_15m=($cpu.load_15m? | default 0) ($timestamp)")
}
# Memory metrics
if ($sys | get -i memory | is-not-empty) {
let mem = ($sys | get memory)
$influx_lines = ($influx_lines | append $"system_memory,hostname=($hostname) usage_percent=($mem.usage_percent? | default 0),total_kb=($mem.total_kb? | default 0),used_kb=($mem.used_kb? | default 0) ($timestamp)")
}
# Process metrics
if ($sys | get -i processes | is-not-empty) {
let proc = ($sys | get processes)
$influx_lines = ($influx_lines | append $"system_processes,hostname=($hostname) total=($proc.total? | default 0) ($timestamp)")
}
}
return ($influx_lines | str join "\n")
}
# Create and manage telemetry batches
export def batch-telemetry [
--max-batch-size(-s): int = 100 # Maximum items per batch
--max-wait-time(-w): int = 30 # Maximum wait time in seconds
--output-file(-o): string # File to store batched data
] -> nothing {
mut batch = []
mut batch_start_time = (date now)
print $"📊 Starting telemetry batching (max size: ($max_batch_size), max wait: ($max_wait_time)s)"
# Monitor for telemetry data
while true {
# Check if we have data to batch (this would typically come from external sources)
# For demonstration, we'll create sample data
let current_time = (date now)
# Collect current metrics
try {
use ../observability/collect.nu *
let metrics = (collect-system-metrics)
# Add to batch
$batch = ($batch | append {
timestamp: ($current_time | format date "%Y-%m-%dT%H:%M:%S.%fZ")
type: "system_metrics"
data: $metrics
})
# Check batch conditions
let batch_size = ($batch | length)
let elapsed_time = (($current_time - $batch_start_time) / 1sec)
if $batch_size >= $max_batch_size or $elapsed_time >= $max_wait_time {
# Send batch
let batch_result = (send-batch $batch --output-file $output_file)
if $batch_result.success {
print $"✅ Batch sent successfully: ($batch_size) items"
} else {
print $"❌ Batch send failed: ($batch_result.error)"
}
# Reset batch
$batch = []
$batch_start_time = (date now)
}
} catch { |err|
print $"⚠️ Error collecting metrics: ($err | get msg)"
}
# Wait before next collection
sleep 10sec
}
}
# Send a batch of telemetry data
def send-batch [
batch: list<record>
--output-file(-o): string
] -> record {
if ($batch | length) == 0 {
return {success: true, message: "Empty batch, nothing to send"}
}
let batch_payload = {
batch_id: (random uuid)
batch_size: ($batch | length)
batch_timestamp: (date now | format date "%Y-%m-%dT%H:%M:%S.%fZ")
hostname: ($env.HOSTNAME? | default "unknown")
agent: "nushell-telemetry"
items: $batch
}
# Save to file if specified
if ($output_file | is-not-empty) {
try {
$batch_payload | to json | save -a $output_file
return {
success: true
message: $"Batch saved to file: ($output_file)"
batch_size: ($batch | length)
}
} catch { |err|
return {
success: false
error: $"Failed to save batch: ($err | get msg)"
}
}
}
# Send to telemetry endpoint
let endpoint = ($env.NUSHELL_TELEMETRY_ENDPOINT? | default "")
if ($endpoint | is-not-empty) {
return (send-telemetry $batch_payload --endpoint $endpoint)
} else {
return {
success: false
error: "No telemetry endpoint configured"
}
}
}
# Monitor system health and send alerts
export def health-monitoring [
--alert-threshold(-t): record = {cpu: 80, memory: 90, disk: 95} # Alert thresholds
--check-interval(-i): int = 60 # Check interval in seconds
--alert-endpoint(-e): string # Alert webhook endpoint
] -> nothing {
print $"🔍 Starting health monitoring with thresholds: ($alert_threshold)"
while true {
try {
use ../observability/collect.nu *
let status = (status-check)
# Check for threshold violations
mut alerts = []
# CPU check
if ($status.metrics.system.cpu.load_1m? | default 0) > ($alert_threshold.cpu / 10.0) {
$alerts = ($alerts | append {
type: "cpu_high"
severity: "warning"
message: $"High CPU load: ($status.metrics.system.cpu.load_1m)"
threshold: $alert_threshold.cpu
current_value: $status.metrics.system.cpu.load_1m
})
}
# Memory check
if ($status.metrics.system.memory.usage_percent? | default 0) > $alert_threshold.memory {
$alerts = ($alerts | append {
type: "memory_high"
severity: "critical"
message: $"High memory usage: ($status.metrics.system.memory.usage_percent)%"
threshold: $alert_threshold.memory
current_value: $status.metrics.system.memory.usage_percent
})
}
# Disk check
try {
let high_disk_usage = ($status.metrics.system.disk | where {|disk|
($disk.percent | str replace "%" "" | into float) > $alert_threshold.disk
})
if ($high_disk_usage | length) > 0 {
for disk in $high_disk_usage {
$alerts = ($alerts | append {
type: "disk_high"
severity: "critical"
message: $"High disk usage on ($disk.mount): ($disk.percent)"
threshold: $alert_threshold.disk
current_value: ($disk.percent | str replace "%" "" | into float)
filesystem: $disk.filesystem
mount: $disk.mount
})
}
}
} catch {}
# Send alerts if any
if ($alerts | length) > 0 {
let alert_payload = {
timestamp: (date now | format date "%Y-%m-%dT%H:%M:%S.%fZ")
hostname: ($env.HOSTNAME? | default "unknown")
alert_count: ($alerts | length)
alerts: $alerts
system_status: $status
}
# Send to telemetry endpoint
let result = (send-telemetry $alert_payload --endpoint ($alert_endpoint | default ($env.NUSHELL_TELEMETRY_ENDPOINT? | default "")))
if $result.success {
print $"🚨 Sent ($alerts | length) alerts to monitoring system"
} else {
print $"❌ Failed to send alerts: ($result.error)"
}
# Also log alerts locally
$alerts | each { |alert|
print $"⚠️ ALERT: ($alert.type) - ($alert.message)"
}
}
# Send regular health status
let health_payload = {
type: "health_check"
timestamp: (date now | format date "%Y-%m-%dT%H:%M:%S.%fZ")
status: $status
}
send-telemetry $health_payload | ignore
} catch { |err|
print $"❌ Health monitoring error: ($err | get msg)"
}
sleep ($check_interval * 1sec)
}
}
# Initialize telemetry configuration
export def init-telemetry [
--endpoint(-e): string # Telemetry endpoint URL
--format(-f): string = "json" # Default format
--enable-health(-h) # Enable health monitoring
--config-file(-c): string # Save configuration to file
] -> record {
let config = {
endpoint: ($endpoint | default "")
format: $format
health_monitoring: ($enable_health | default false)
created: (date now | format date "%Y-%m-%d %H:%M:%S")
version: "1.0.0"
}
# Set environment variables
$env.NUSHELL_TELEMETRY_ENDPOINT = ($endpoint | default "")
$env.NUSHELL_TELEMETRY_FORMAT = $format
$env.NUSHELL_TELEMETRY_ENABLED = "true"
# Save configuration if file specified
if ($config_file | is-not-empty) {
try {
$config | to json | save $config_file
print $"📝 Telemetry configuration saved to ($config_file)"
} catch { |err|
print $"⚠️ Failed to save configuration: ($err | get msg)"
}
}
print $"🔧 Telemetry initialized:"
print $" Endpoint: ($config.endpoint)"
print $" Format: ($config.format)"
print $" Health monitoring: ($config.health_monitoring)"
return $config
}