# Interactive Setup Wizard Module # Provides step-by-step interactive guidance for system setup # Follows Nushell guidelines: explicit types, single purpose, no try-catch # [command] # name = "setup wizard" # group = "configuration" # tags = ["setup", "interactive", "wizard"] # version = "2.0.0" # requires = ["forminquire.nu:1.0.0", "nushell:0.109.0"] # note = "Migrated to FormInquire with fallback to prompt-* functions" use ./mod.nu * use ./detection.nu * use ./validation.nu * use ../../forminquire/nulib/forminquire.nu * # ============================================================================ # INPUT HELPERS # ============================================================================ # Helper to read one line of input in Nushell 0.109.1 # Reads directly from /dev/tty for TTY mode, handles piped input gracefully def read-input-line []: string -> string { # Try to read from /dev/tty first (TTY/interactive mode) let tty_result = (try { open /dev/tty | lines | first | str trim } catch { null }) # If /dev/tty worked, return the line if $tty_result != null { $tty_result } else { # No /dev/tty (Windows, containers, or piped mode) # Return empty string - this will use defaults in calling code "" } } # Prompt user for simple yes/no question export def prompt-yes-no [ question: string ]: nothing -> bool { print "" print -n ($question + " (y/n): ") let response = (read-input-line) if ($response | is-empty) { false } else { ($response == "y" or $response == "Y") } } # Prompt user for text input export def prompt-text [ question: string default_value: string = "" ]: nothing -> string { print "" if ($default_value != "") { print ($question + " [" + $default_value + "]: ") } else { print ($question + ": ") } let response = (read-input-line) if ($response == "") { $default_value } else { $response } } # Prompt user for selection from list export def prompt-select [ question: string options: list ]: nothing -> string { print "" print $question let option_count = ($options | length) for i in (0..($option_count - 1)) { let option = ($options | get $i) let num = (($i + 1) | into string) print (" " + $num + ") " + $option) } print "" let count_str = ($option_count | into string) print ("Select option (1-" + $count_str + "): ") let choice_str = (read-input-line) let choice = ($choice_str | into int | default 1) if ($choice >= 1 and $choice <= $option_count) { $options | get ($choice - 1) } else { $options | get 0 } } # Prompt user for number with validation export def prompt-number [ question: string min_value: int = 1 max_value: int = 1000 default_value: int = 0 ]: nothing -> int { mut result = $default_value mut valid = false while (not $valid) { print "" if ($default_value != 0) { print $"$question [$default_value]: " } else { print $"$question: " } let input_value = (read-input-line) if ($input_value == "") { $result = $default_value $valid = true } else { let parsed = (do { $input_value | into int } | complete) if ($parsed.exit_code == 0) { let num = ($parsed.stdout | str trim | into int) if ($num >= $min_value and $num <= $max_value) { $result = $num $valid = true } else { print-setup-warning $"Please enter a number between ($min_value) and ($max_value)" } } else { print-setup-warning "Please enter a valid number" } } } $result } # ============================================================================ # SYSTEM CONFIGURATION PROMPTS # ============================================================================ # Prompt for system configuration details export def prompt-system-config []: nothing -> record { print-setup-header "System Configuration" print "" print "Let's configure your provisioning system. This will set up the base configuration." print "" let config_path = (get-config-base-path) print-setup-info $"Configuration will be stored in: ($config_path)" let use_defaults = (prompt-yes-no "Use recommended paths for your OS?") let confirmed_path = if $use_defaults { config_path } else { (prompt-text "Configuration base path" $config_path) } { config_path: $confirmed_path os_name: (detect-os) cpu_count: (get-cpu-count) memory_gb: (get-system-memory-gb) } } # ============================================================================ # DEPLOYMENT MODE PROMPTS # ============================================================================ # Prompt for deployment mode selection export def prompt-deployment-mode [ detection_report: record ]: nothing -> string { print-setup-header "Deployment Mode Selection" print "" print "Choose how platform services will be deployed:" print "" mut options = [] let caps = $detection_report.capabilities if ($caps.docker_available and $caps.docker_compose_available) { $options = ($options | append "docker-compose (Local Docker)") } if $caps.kubectl_available { $options = ($options | append "kubernetes (Kubernetes cluster)") } if $caps.ssh_available { $options = ($options | append "remote-ssh (Remote SSH)") } if $caps.systemd_available { $options = ($options | append "systemd (System services)") } if ($options | length) == 0 { print-setup-error "No deployment methods available" return "unknown" } let recommended = (recommend-deployment-mode $detection_report) print ("Recommended: " + $recommended) print "" let selected = (prompt-select "Select deployment mode" $options) match $selected { "docker-compose (Local Docker)" => { "docker-compose" } "kubernetes (Kubernetes cluster)" => { "kubernetes" } "remote-ssh (Remote SSH)" => { "remote-ssh" } "systemd (System services)" => { "systemd" } _ => { "docker-compose" } } } # ============================================================================ # PROVIDER CONFIGURATION PROMPTS # ============================================================================ # Prompt for provider selection export def prompt-providers []: nothing -> list { print-setup-header "Provider Selection" print "" print "Which infrastructure providers do you want to use?" print "(Select at least one)" print "" let available_providers = ["upcloud", "aws", "hetzner", "local"] mut selected = [] for provider in $available_providers { let use_provider = (prompt-yes-no $"Use ($provider)?") if $use_provider { $selected = ($selected | append $provider) } } if ($selected | length) == 0 { print-setup-info "At least 'local' provider is required" ["local"] } else { $selected } } # ============================================================================ # RESOURCE CONFIGURATION PROMPTS # ============================================================================ # Prompt for resource allocation export def prompt-resource-allocation [ detection_report: record ]: nothing -> record { print-setup-header "Resource Allocation" print "" let current_cpus = $detection_report.system.cpu_count let current_memory = $detection_report.system.memory_gb let cpu_count = (prompt-number "Number of CPUs to allocate" 1 $current_cpus $current_cpus) let memory_gb = (prompt-number "Memory in GB to allocate" 1 $current_memory $current_memory) print "" print $"✅ Allocated ($cpu_count) CPUs and ($memory_gb) GB memory" { cpu_count: $cpu_count memory_gb: $memory_gb } } # ============================================================================ # SECURITY CONFIGURATION PROMPTS # ============================================================================ # Prompt for security settings export def prompt-security-config []: nothing -> record { print-setup-header "Security Configuration" print "" let enable_mfa = (prompt-yes-no "Enable Multi-Factor Authentication (MFA)?") let enable_audit = (prompt-yes-no "Enable audit logging for all operations?") let require_approval = (prompt-yes-no "Require approval for destructive operations?") { enable_mfa: $enable_mfa enable_audit: $enable_audit require_approval_for_destructive: $require_approval } } # ============================================================================ # WORKSPACE CONFIGURATION PROMPTS # ============================================================================ # Prompt for initial workspace creation export def prompt-initial-workspace []: nothing -> record { print-setup-header "Initial Workspace" print "" print "Create an initial workspace for your infrastructure?" print "" let create_workspace = (prompt-yes-no "Create workspace now?") if not $create_workspace { return { create_workspace: false name: "" description: "" } } let workspace_name = (prompt-text "Workspace name" "default") let workspace_description = (prompt-text "Workspace description (optional)" "") { create_workspace: true name: $workspace_name description: $workspace_description } } # ============================================================================ # COMPLETE SETUP WIZARD # ============================================================================ # Run complete interactive setup wizard export def run-setup-wizard [ --verbose = false ]: nothing -> record { # Check if running in TTY or piped mode let is_interactive = (try { open /dev/tty | null true } catch { false }) if not $is_interactive { # In non-TTY mode, switch to defaults automatically print "ℹ️ Non-interactive mode detected. Using recommended defaults..." print "" return (run-setup-with-defaults) } # Force immediate output print "" print "╔═══════════════════════════════════════════════════════════════╗" print "║ PROVISIONING SYSTEM SETUP WIZARD ║" print "║ ║" print "║ This wizard will guide you through setting up provisioning ║" print "║ for your infrastructure automation needs. ║" print "╚═══════════════════════════════════════════════════════════════╝" print "" # Step 1: Environment Detection print-setup-header "Step 1: Environment Detection" print "Analyzing your system configuration..." print "" # Use simplified detection to avoid hangs let detection_report = { system: { os: (detect-os) architecture: (detect-architecture) hostname: "localhost" current_user: (get-current-user) cpu_count: 4 memory_gb: 8 disk_gb: 100 } capabilities: (get-deployment-capabilities) network: { internet_connected: true docker_port_available: true orchestrator_port_available: true control_center_port_available: true kms_port_available: true } existing_config: (get-existing-config-summary) platform_services: { orchestrator_running: false control_center_running: false kms_running: false } timestamp: (date now) } if $verbose { print-detection-report $detection_report } # Step 2: System Configuration let system_config = (prompt-system-config) # Step 3: Deployment Mode let deployment_mode = (prompt-deployment-mode $detection_report) print-setup-success $"Selected deployment mode: ($deployment_mode)" # Step 4: Provider Selection let providers = (prompt-providers) print-setup-success $"Selected providers: ($providers | str join ', ')" # Step 5: Resource Allocation let resources = (prompt-resource-allocation $detection_report) # Step 6: Security Settings let security = (prompt-security-config) # Step 7: Initial Workspace let workspace = (prompt-initial-workspace) # Summary print "" print-setup-header "Setup Summary" print "" print "Configuration Details:" print $" Config Path: ($system_config.config_path)" print $" OS: ($system_config.os_name)" print $" Deployment Mode: ($deployment_mode)" print $" Providers: ($providers | str join ', ')" print $" CPUs: ($resources.cpu_count)" print $" Memory: ($resources.memory_gb) GB" print $" MFA Enabled: (if $security.enable_mfa { 'Yes' } else { 'No' })" print $" Audit Logging: (if $security.enable_audit { 'Yes' } else { 'No' })" print "" let confirm = (prompt-yes-no "Proceed with this configuration?") if not $confirm { print-setup-warning "Setup cancelled" return { completed: false system_config: {} deployment_mode: "" providers: [] resources: {} security: {} workspace: {} } } print "" print-setup-success "Configuration confirmed!" print "" { completed: true system_config: $system_config deployment_mode: $deployment_mode providers: $providers resources: $resources security: $security workspace: $workspace timestamp: (date now) } } # ============================================================================ # QUICK SETUP (AUTOMATED WITH DEFAULTS) # ============================================================================ # Run setup with recommended defaults (no interaction) export def run-setup-with-defaults []: nothing -> record { print-setup-header "Quick Setup (Recommended Defaults)" print "" print "Configuring with system-recommended defaults..." print "" { completed: true system_config: { config_path: (get-config-base-path) os_name: (detect-os) cpu_count: (get-cpu-count) memory_gb: (get-system-memory-gb) } deployment_mode: "docker-compose" providers: ["local"] resources: { cpu_count: (get-cpu-count) memory_gb: (get-system-memory-gb) } security: { enable_mfa: true enable_audit: true require_approval_for_destructive: true } workspace: { create_workspace: true name: "default" description: "Default workspace" } timestamp: (date now) } } # Run minimal setup (only required settings) export def run-minimal-setup []: nothing -> record { print-setup-header "Minimal Setup" print "" print "Configuring with minimal required settings..." print "" { completed: true system_config: { config_path: (get-config-base-path) os_name: (detect-os) } deployment_mode: "docker-compose" providers: ["local"] resources: {} security: { enable_mfa: false enable_audit: true require_approval_for_destructive: false } workspace: { create_workspace: false } timestamp: (date now) } } # ============================================================================ # INTERACTIVE SETUP USING FORMINQUIRE (NEW) # ============================================================================ # Run setup wizard using FormInquire - modern TUI experience export def run-setup-wizard-interactive []: nothing -> record { print "" print "╔═══════════════════════════════════════════════════════════════╗" print "║ PROVISIONING SYSTEM SETUP WIZARD (FormInquire) ║" print "║ ║" print "║ This wizard will guide you through setting up provisioning ║" print "║ for your infrastructure automation needs. ║" print "╚═══════════════════════════════════════════════════════════════╝" print "" # Prepare context with system information for form defaults let context = { config_path: (get-config-base-path) cpu_count: (get-cpu-count) memory_gb: (get-system-memory-gb) disk_gb: 100 } # Run the FormInquire-based wizard let form_result = (setup-wizard-form) if not $form_result.success { print-setup-warning "Setup cancelled or failed" return { completed: false system_config: {} deployment_mode: "" providers: [] resources: {} security: {} workspace: {} } } # Extract values from form results let values = $form_result.values # Collect selected providers let providers = ( [] | if ($values.provider_upcloud? | default false) { append "upcloud" } else { . } | if ($values.provider_aws? | default false) { append "aws" } else { . } | if ($values.provider_hetzner? | default false) { append "hetzner" } else { . } | if ($values.provider_local? | default false) { append "local" } else { . } ) # Ensure at least one provider let providers_final = if ($providers | length) == 0 { ["local"] } else { $providers } # Create workspace config if requested let workspace_final = ( if ($values.create_workspace? | default false) { { create_workspace: true name: ($values.workspace_name? | default "default") description: ($values.workspace_description? | default "") } } else { { create_workspace: false } } ) # Display summary print "" print-setup-header "Setup Summary" print "" print "Configuration Details:" print $" Config Path: ($values.config_path)" print $" Deployment Mode: ($values.deployment_mode)" print $" Providers: ($providers_final | str join ', ')" print $" CPUs: ($values.cpu_count)" print $" Memory: ($values.memory_gb) GB" print $" Disk: ($values.disk_gb) GB" print $" MFA Enabled: (if ($values.enable_mfa? | default false) { 'Yes' } else { 'No' })" print $" Audit Logging: (if ($values.enable_audit_logging? | default false) { 'Yes' } else { 'No' })" print "" print-setup-success "Configuration confirmed!" print "" { completed: true system_config: { config_path: ($values.config_path) os_name: (detect-os) cpu_count: ($values.cpu_count | into int) memory_gb: ($values.memory_gb | into int) } deployment_mode: ($values.deployment_mode) providers: $providers_final resources: { cpu_count: ($values.cpu_count | into int) memory_gb: ($values.memory_gb | into int) disk_gb: ($values.disk_gb | into int) } security: { enable_mfa: ($values.enable_mfa? | default false) enable_audit: ($values.enable_audit_logging? | default false) require_approval_for_destructive: ($values.require_approval? | default false) } workspace: $workspace_final timestamp: (date now) } }