Update core components including CLI, Nushell libraries, plugins system, and utility scripts for the provisioning system. CLI Updates: - Command implementations - CLI utilities and dispatching - Help system improvements - Command validation Library Updates: - Configuration management system - Infrastructure validation - Extension system improvements - Secrets management - Workspace operations - Cache management system Plugin System: - Interactive form plugin (inquire) - KCL integration plugin - Performance optimization plugins - Plugin registration system Utilities: - Build and distribution scripts - Installation procedures - Testing utilities - Development tools Documentation: - Library module documentation - Extension API guides - Plugin usage guides - Service management documentation All changes are backward compatible. No breaking changes.
642 lines
20 KiB
Plaintext
642 lines
20 KiB
Plaintext
# 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<string>
|
||
]: 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<string> {
|
||
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)
|
||
}
|
||
}
|