# 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 = "3.0.0" # requires = ["nushell:0.109.0"] use ./mod.nu * use ./detection.nu * use ./validation.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 [] { # Try to read from /dev/tty first (TTY/interactive mode) let read_result = (do { open /dev/tty | lines | first | str trim } | complete) # If /dev/tty worked, return the line if $read_result.exit_code == 0 { ($read_result.stdout) } 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 ] { 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 = "" ] { 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 ] { 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 ] { 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 } # ============================================================================ # PROFILE SELECTION # ============================================================================ # Prompt for setup profile selection export def prompt-profile-selection [] { print "" print-setup-header "Profile Selection" print "" print "Choose a setup profile for your provisioning system:" print "" print " 1) Developer - Fast local setup (<5 min, Docker Compose, minimal config)" print " 2) Production - Full validated setup (Kubernetes/SSH, complete security, HA)" print " 3) CI/CD - Ephemeral pipeline setup (automated, Docker Compose, cleanup)" print "" let options = ["Developer", "Production", "CI/CD"] let choice = (prompt-select "Select profile" $options) match $choice { "Developer" => "developer" "Production" => "production" "CI/CD" => "cicd" _ => "developer" } } # ============================================================================ # SYSTEM CONFIGURATION PROMPTS # ============================================================================ # Prompt for system configuration details export def prompt-system-config [] { 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 ] { 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 [] { 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 ] { 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 [] { 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 [] { 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 ] { # Check if running in TTY or piped mode let tty_check = (do { open /dev/tty | null } | complete) let is_interactive = ($tty_check.exit_code == 0) 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: Profile Selection (NEW - determines setup approach) print "" let profile = (prompt-profile-selection) print-setup-success $"Selected profile: ($profile)" # Step 3: System Configuration let system_config = (prompt-system-config) # Step 5: Deployment Mode let deployment_mode = (prompt-deployment-mode $detection_report) print-setup-success $"Selected deployment mode: ($deployment_mode)" # Step 6: Provider Selection let providers = (prompt-providers) print-setup-success $"Selected providers: ($providers | str join ', ')" # Step 7: Resource Allocation let resources = (prompt-resource-allocation $detection_report) # Step 8: Security Settings let security = (prompt-security-config) # Step 9: Initial Workspace let workspace = (prompt-initial-workspace) # Summary print "" print-setup-header "Setup Summary" print "" print "Configuration Details:" print $" Profile: ($profile)" 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 profile: "" system_config: {} deployment_mode: "" providers: [] resources: {} security: {} workspace: {} } } print "" print-setup-success "Configuration confirmed!" print "" { completed: true profile: $profile 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 [] { 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 [] { 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) } } # ============================================================================ # TYPEDIALOG HELPER FUNCTIONS # ============================================================================ # Run TypeDialog form via bash wrapper and return parsed result # This pattern avoids TTY/input issues in Nushell's execution stack def run-typedialog-form [ wrapper_script: string --backend: string = "tui" ] { # Check if the wrapper script exists if not ($wrapper_script | path exists) { print-setup-warning "TypeDialog wrapper not found. Using fallback prompts." return { success: false error: "TypeDialog wrapper not available" use_fallback: true } } # Set backend environment variable $env.TYPEDIALOG_BACKEND = $backend # Run bash wrapper (handles TTY input properly) let result = (do { bash $wrapper_script } | complete) if $result.exit_code != 0 { print-setup-error "TypeDialog wizard failed or was cancelled" return { success: false error: $result.stderr use_fallback: true } } # Read the generated JSON file let json_output = ($wrapper_script | path dirname | path join "generated" | path join ($wrapper_script | path basename | str replace ".sh" "-result.json")) if not ($json_output | path exists) { print-setup-warning "TypeDialog output not found. Using fallback." return { success: false error: "Output file not found" use_fallback: true } } # Parse JSON output (no try-catch) let parse_result = (do { open $json_output | from json } | complete) if $parse_result.exit_code != 0 { return { success: false error: "Failed to parse TypeDialog output" use_fallback: true } } let values = ($parse_result.stdout) { success: true values: $values use_fallback: false } } # ============================================================================ # INTERACTIVE SETUP USING TYPEDIALOG # ============================================================================ # Run setup wizard using TypeDialog - modern TUI experience # Uses bash wrapper to handle TTY input properly export def run-setup-wizard-interactive [ --backend: string = "tui" ] { print "" print "╔═══════════════════════════════════════════════════════════════╗" print "║ PROVISIONING SYSTEM SETUP WIZARD (TypeDialog) ║" print "║ ║" print "║ This wizard will guide you through setting up provisioning ║" print "║ for your infrastructure automation needs. ║" print "╚═══════════════════════════════════════════════════════════════╝" print "" # Run the TypeDialog-based wizard via bash wrapper let wrapper_script = "provisioning/core/shlib/setup-wizard-tty.sh" let form_result = (run-typedialog-form $wrapper_script --backend $backend) # If TypeDialog not available or failed, fall back to basic wizard if (not $form_result.success or $form_result.use_fallback) { print-setup-info "Falling back to basic interactive wizard..." return (run-setup-wizard) } # Extract values from form results let values = $form_result.values # Collect selected providers let providers = ( [] | if ($values.providers?.upcloud? | default false) { append "upcloud" } else { . } | if ($values.providers?.aws? | default false) { append "aws" } else { . } | if ($values.providers?.hetzner? | default false) { append "hetzner" } else { . } | if ($values.providers?.local? | default false) { append "local" } else { . } ) # Ensure at least one provider let providers_final = if ($providers | length) == 0 { ["local"] } else { $providers } # Create workspace config let workspace_final = { create_workspace: ($values.workspace?.create_workspace? | default false) name: ($values.workspace?.name? | default "default") description: ($values.workspace?.description? | default "") } # Display summary print "" print-setup-header "Setup Summary" print "" print "Configuration Details:" print $" Config Path: ($values.system_config?.config_path? | default (get-config-base-path))" print $" Deployment Mode: ($values.deployment_mode? | default 'docker-compose')" print $" Providers: ($providers_final | str join ', ')" print $" CPUs: ($values.resources?.cpu_count? | default 4)" print $" Memory: ($values.resources?.memory_gb? | default 8) GB" print $" MFA Enabled: (if ($values.security?.enable_mfa? | default false) { 'Yes' } else { 'No' })" print $" Audit Logging: (if ($values.security?.enable_audit? | default false) { 'Yes' } else { 'No' })" print "" print-setup-success "Configuration confirmed!" print "" { completed: true system_config: { config_path: ($values.system_config?.config_path? | default (get-config-base-path)) os_name: (detect-os) cpu_count: ($values.resources?.cpu_count? | default 4) memory_gb: ($values.resources?.memory_gb? | default 8) } deployment_mode: ($values.deployment_mode? | default "docker-compose") providers: $providers_final resources: { cpu_count: ($values.resources?.cpu_count? | default 4) memory_gb: ($values.resources?.memory_gb? | default 8) } security: { enable_mfa: ($values.security?.enable_mfa? | default true) enable_audit: ($values.security?.enable_audit? | default true) require_approval_for_destructive: ($values.security?.require_approval_for_destructive? | default true) } workspace: $workspace_final timestamp: (date now) } }