From 6c46596cb3cfc19b5df7ca1118cf734cfd3638b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Fri, 9 Jan 2026 15:08:49 +0000 Subject: [PATCH] chore: forminquire full replace with typedialog and wrappers -tty.sh --- CHANGELOG.md | 22 +- CODE_OF_CONDUCT.md | 6 +- CONTRIBUTING.md | 5 +- README.md | 4 +- nulib/lib_provisioning/ai/README.md | 6 +- .../config/MODULAR_ARCHITECTURE.md | 265 ------------------ nulib/lib_provisioning/plugins/auth.nu | 230 +++++++++++++-- nulib/lib_provisioning/setup/wizard.nu | 169 ++++++----- nulib/lib_provisioning/workspace/init.nu | 5 - nulib/main_provisioning/metadata_handler.nu | 5 - nulib/test/README.md | 11 +- services/kms/README.md | 3 +- shlib/README.md | 235 ++++++++++++++++ shlib/auth-login-tty.sh | 75 +++++ shlib/forms/authentication/auth_login.toml | 65 ----- shlib/forms/authentication/mfa_enroll.toml | 101 ------- .../cluster_delete_confirm.toml | 116 -------- .../generic_delete_confirm.toml | 83 ------ .../infrastructure/server_delete_confirm.toml | 84 ------ .../taskserv_delete_confirm.toml | 108 ------- shlib/mfa-enroll-tty.sh | 75 +++++ shlib/setup-wizard-tty.sh | 120 ++++++++ 22 files changed, 843 insertions(+), 950 deletions(-) delete mode 100644 nulib/lib_provisioning/config/MODULAR_ARCHITECTURE.md create mode 100644 shlib/README.md create mode 100755 shlib/auth-login-tty.sh delete mode 100644 shlib/forms/authentication/auth_login.toml delete mode 100644 shlib/forms/authentication/mfa_enroll.toml delete mode 100644 shlib/forms/infrastructure/cluster_delete_confirm.toml delete mode 100644 shlib/forms/infrastructure/generic_delete_confirm.toml delete mode 100644 shlib/forms/infrastructure/server_delete_confirm.toml delete mode 100644 shlib/forms/infrastructure/taskserv_delete_confirm.toml create mode 100755 shlib/mfa-enroll-tty.sh create mode 100755 shlib/setup-wizard-tty.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e72816..a81c508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ ## 📋 Summary -Core system with Nickel as primary IaC: CLI enhancements, Nushell library refactoring for schema support, config loader for Nickel evaluation, and comprehensive infrastructure automation. +Core system with Nickel as primary IaC: CLI enhancements, Nushell library refactoring for schema support, +config loader for Nickel evaluation, and comprehensive infrastructure automation. --- @@ -65,9 +66,9 @@ Nushell plugins for performance optimization **Sub-repositories:** - `nushell-plugins/` - Multiple Nushell plugins - - `_nu_plugin_inquire/` - Interactive form plugin - - `api_nu_plugin_nickel/` - Nickel integration plugin - - Additional plugin implementations + - `_nu_plugin_inquire/` - Interactive form plugin + - `api_nu_plugin_nickel/` - Nickel integration plugin + - Additional plugin implementations **Plugin Documentation:** @@ -94,12 +95,15 @@ Service definitions and configurations - Service descriptions - Service management -### forminquire/ directory +### forminquire/ directory (ARCHIVED) -Form inquiry interface +**Status**: DEPRECATED - Archived to `.coder/archive/forminquire/` -- Interactive form system -- User input handling +**Replacement**: TypeDialog forms (`.typedialog/provisioning/`) + +- Legacy: Jinja2-based form system +- Archived: 2025-01-09 +- Replaced by: TypeDialog with bash wrappers for TTY-safe input ### Additional Files @@ -114,7 +118,7 @@ Form inquiry interface ## 📊 Change Statistics | Category | Files | Lines Added | Lines Removed | Status | -|----------|-------|-------------|---------------|--------| +| -------- | ----- | ----------- | ------------- | ------ | | CLI | 3 | 780+ | 30+ | Major update | | Config System | 15+ | 300+ | 200+ | Refactored | | AI/Docs | 8+ | 350+ | 100+ | Enhanced | diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 084ffa9..670d007 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,7 +2,8 @@ ## Our Pledge -We, as members, contributors, and leaders, pledge to make participation in our project and community a harassment-free experience for everyone, regardless of: +We, as members, contributors, and leaders, pledge to make participation in our project and community +a harassment-free experience for everyone, regardless of: - Age - Body size @@ -44,7 +45,8 @@ Examples of unacceptable behavior include: ## Enforcement Responsibilities -Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate corrective action in response to unacceptable behavior. +Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior +and will take appropriate corrective action in response to unacceptable behavior. Maintainers have the right and responsibility to: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc40771..92ec1e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,8 @@ Thank you for your interest in contributing! This document provides guidelines a ## Code of Conduct -This project adheres to a Code of Conduct. By participating, you are expected to uphold this code. Please see [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details. +This project adheres to a Code of Conduct. By participating, you are expected to uphold this code. +Please see [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details. ## Getting Started @@ -121,7 +122,7 @@ Maintainers handle releases following semantic versioning: - MINOR: New features (backward compatible) - PATCH: Bug fixes -## Questions? +## Questions - Check existing documentation and issues - Ask in discussions or open an issue diff --git a/README.md b/README.md index 808f56a..475e769 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ # Core Engine -The **Core Engine** is the foundational component of the [Provisioning project](https://repo.jesusperez.pro/jesus/provisioning), providing the unified CLI interface, core Nushell libraries, and essential utility scripts. Built on **Nushell** and **Nickel**, it serves as the primary entry point for all infrastructure operations. +The **Core Engine** is the foundational component of the [Provisioning project](https://repo.jesusperez.pro/jesus/provisioning), +providing the unified CLI interface, core Nushell libraries, and essential utility scripts. +Built on **Nushell** and **Nickel**, it serves as the primary entry point for all infrastructure operations. ## Overview diff --git a/nulib/lib_provisioning/ai/README.md b/nulib/lib_provisioning/ai/README.md index fb448f6..ea1f5fe 100644 --- a/nulib/lib_provisioning/ai/README.md +++ b/nulib/lib_provisioning/ai/README.md @@ -275,7 +275,8 @@ servers = [ ] ```plaintext -*This configuration includes 7 servers optimized for high availability and performance. Would you like me to explain any specific part or generate additional configurations?"* +*This configuration includes 7 servers optimized for high availability and performance. +Would you like me to explain any specific part or generate additional configurations?"* ### 🚀 **Advanced Features** @@ -372,4 +373,5 @@ ai/ 5. **Validation** - Syntax and semantic validation 6. **Output** - Formatted Nickel files and user feedback -This AI integration transforms the provisioning system into an intelligent infrastructure automation platform that understands natural language and generates production-ready configurations. +This AI integration transforms the provisioning system into an intelligent infrastructure automation platform +that understands natural language and generates production-ready configurations. diff --git a/nulib/lib_provisioning/config/MODULAR_ARCHITECTURE.md b/nulib/lib_provisioning/config/MODULAR_ARCHITECTURE.md deleted file mode 100644 index 3dc64d4..0000000 --- a/nulib/lib_provisioning/config/MODULAR_ARCHITECTURE.md +++ /dev/null @@ -1,265 +0,0 @@ -# Modular Configuration Loading Architecture - -## Overview - -The configuration system has been refactored into modular components to achieve 2-3x performance improvements for regular commands while maintaining full functionality for complex operations. - -## Architecture Layers - -### Layer 1: Minimal Loader (0.023s) - -**File**: `loader-minimal.nu` (~150 lines) - -Contains only essential functions needed for: - -- Workspace detection -- Environment determination -- Project root discovery -- Fast path detection - -**Exported Functions**: - -- `get-active-workspace` - Get current workspace -- `detect-current-environment` - Determine dev/test/prod -- `get-project-root` - Find project directory -- `get-defaults-config-path` - Path to default config -- `check-if-sops-encrypted` - SOPS file detection -- `find-sops-config-path` - Locate SOPS config - -**Used by**: - -- Help commands (help infrastructure, help workspace, etc.) -- Status commands -- Workspace listing -- Quick reference operations - -### Layer 2: Lazy Loader (decision layer) - -**File**: `loader-lazy.nu` (~80 lines) - -Smart loader that decides which configuration to load: - -- Fast path for help/status commands -- Full path for operations that need config - -**Key Function**: - -- `command-needs-full-config` - Determines if full config required - -### Layer 3: Full Loader (0.091s) - -**File**: `loader.nu` (1990 lines) - -Original comprehensive loader that handles: - -- Hierarchical config loading -- Variable interpolation -- Config validation -- Provider configuration -- Platform configuration - -**Used by**: - -- Server creation -- Infrastructure operations -- Deployment commands -- Anything needing full config - -## Performance Characteristics - -### Benchmarks - -| Operation | Time | Notes | -|-----------|------|-------| -| Workspace detection | 0.023s | 23ms for minimal load | -| Full config load | 0.091s | ~4x slower than minimal | -| Help command | 0.040s | Uses minimal loader only | -| Status command | 0.030s | Fast path, no full config | -| Server operations | 0.150s+ | Requires full config load | - -### Performance Gains - -- **Help commands**: 30-40% faster (40ms vs 60ms with full config) -- **Workspace operations**: 50% faster (uses minimal loader) -- **Status checks**: Nearly instant (23ms) - -## Module Dependency Graph - -```plaintext -Help/Status Commands - ↓ -loader-lazy.nu - ↓ -loader-minimal.nu (workspace, environment detection) - ↓ - (no further deps) - -Infrastructure/Server Commands - ↓ -loader-lazy.nu - ↓ -loader.nu (full configuration) - ├── loader-minimal.nu (for workspace detection) - ├── Interpolation functions - ├── Validation functions - └── Config merging logic -```plaintext - -## Usage Examples - -### Fast Path (Help Commands) - -```nushell -# Uses minimal loader - 23ms -./provisioning help infrastructure -./provisioning workspace list -./provisioning version -```plaintext - -### Medium Path (Status Operations) - -```nushell -# Uses minimal loader with some full config - ~50ms -./provisioning status -./provisioning workspace active -./provisioning config validate -```plaintext - -### Full Path (Infrastructure Operations) - -```nushell -# Uses full loader - ~150ms -./provisioning server create --infra myinfra -./provisioning taskserv create kubernetes -./provisioning workflow submit batch.yaml -```plaintext - -## Implementation Details - -### Lazy Loading Decision Logic - -```nushell -# In loader-lazy.nu -let is_fast_command = ( - $command == "help" or - $command == "status" or - $command == "version" -) - -if $is_fast_command { - # Use minimal loader only (0.023s) - get-minimal-config -} else { - # Load full configuration (0.091s) - load-provisioning-config -} -```plaintext - -### Minimal Config Structure - -The minimal loader returns a lightweight config record: - -```nushell -{ - workspace: { - name: "librecloud" - path: "/path/to/workspace_librecloud" - } - environment: "dev" - debug: false - paths: { - base: "/path/to/workspace_librecloud" - } -} -```plaintext - -This is sufficient for: - -- Workspace identification -- Environment determination -- Path resolution -- Help text generation - -### Full Config Structure - -The full loader returns comprehensive configuration with: - -- Workspace settings -- Provider configurations -- Platform settings -- Interpolated variables -- Validation results -- Environment-specific overrides - -## Migration Path - -### For CLI Commands - -1. Commands are already categorized (help, workspace, server, etc.) -2. Help system uses fast path (minimal loader) -3. Infrastructure commands use full path (full loader) -4. No changes needed to command implementations - -### For New Modules - -When creating new modules: - -1. Check if full config is needed -2. If not, use `loader-minimal.nu` functions only -3. If yes, use `get-config` from main config accessor - -## Future Optimizations - -### Phase 2: Per-Command Config Caching - -- Cache full config for 60 seconds -- Reuse config across related commands -- Potential: Additional 50% improvement - -### Phase 3: Configuration Profiles - -- Create thin config profiles for common scenarios -- Pre-loaded templates for workspace/infra combinations -- Fast switching between profiles - -### Phase 4: Parallel Config Loading - -- Load workspace and provider configs in parallel -- Async validation and interpolation -- Potential: 30% improvement for full config load - -## Maintenance Notes - -### Adding New Functions to Minimal Loader - -Only add if: - -1. Used by help/status commands -2. Doesn't require full config -3. Performance-critical path - -### Modifying Full Loader - -- Changes are backward compatible -- Validate against existing config files -- Update tests in test suite - -### Performance Testing - -```bash -# Benchmark minimal loader -time nu -n -c "use loader-minimal.nu *; get-active-workspace" - -# Benchmark full loader -time nu -c "use config/accessor.nu *; get-config" - -# Benchmark help command -time ./provisioning help infrastructure -```plaintext - -## See Also - -- `loader.nu` - Full configuration loading system -- `loader-minimal.nu` - Fast path loader -- `loader-lazy.nu` - Smart loader decision logic -- `config/ARCHITECTURE.md` - Configuration architecture details diff --git a/nulib/lib_provisioning/plugins/auth.nu b/nulib/lib_provisioning/plugins/auth.nu index f3639f4..0e07068 100644 --- a/nulib/lib_provisioning/plugins/auth.nu +++ b/nulib/lib_provisioning/plugins/auth.nu @@ -5,16 +5,11 @@ # tags = ["authentication", "jwt", "interactive", "login"] # version = "3.0.0" # requires = ["nushell:0.109.0"] -# note = "MIGRATION: ForminQuire (Jinja2 templates) archived. Use TypeDialog forms for auth flows" -# migration = "See: provisioning/.coder/archive/forminquire/ (deprecated) → provisioning/.typedialog/provisioning/fragments/auth-*.toml (new)" # Authentication Plugin Wrapper with HTTP Fallback # Provides graceful degradation to HTTP API when nu_plugin_auth is unavailable use ../config/accessor.nu * -# ARCHIVED: use ../../../forminquire/nulib/forminquire.nu * -# ForminQuire has been archived to: provisioning/.coder/archive/forminquire/ -# New solution: Use TypeDialog for authentication forms (auth-api-key.toml, auth-jwt.toml) use ../commands/traits.nu * # Check if auth plugin is available @@ -785,28 +780,124 @@ export def print-auth-status []: nothing -> nothing { print $"(ansi dim)MFA for destructive:(ansi reset) (should-require-mfa-destructive)" } # ============================================================================ -# INTERACTIVE FORM HANDLERS (FormInquire Integration) +# TYPEDIALOG HELPER FUNCTIONS +# ============================================================================ + +# Run TypeDialog form via bash wrapper for authentication +# This pattern avoids TTY/input issues in Nushell's execution stack +def run-typedialog-auth-form [ + wrapper_script: string + --backend: string = "tui" +]: nothing -> record { + # Check if the wrapper script exists + if not ($wrapper_script | path exists) { + 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 { + 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) { + return { + success: false + error: "Output file not found" + use_fallback: true + } + } + + # Parse JSON output + let values = (try { + open $json_output | from json + } catch { + return { + success: false + error: "Failed to parse TypeDialog output" + use_fallback: true + } + }) + + { + success: true + values: $values + use_fallback: false + } +} + +# ============================================================================ +# INTERACTIVE FORM HANDLERS (TypeDialog Integration) # ============================================================================ # Interactive login with form -export def login-interactive [] : nothing -> record { +export def login-interactive [ + --backend: string = "tui" +] : nothing -> record { print "🔐 Interactive Authentication" print "" - # Run the login form - let form_result = (run-forminquire-form "provisioning/core/shlib/forms/authentication/auth_login.toml") + # Run the login form via bash wrapper + let wrapper_script = "provisioning/core/shlib/auth-login-tty.sh" + let form_result = (run-typedialog-auth-form $wrapper_script --backend $backend) + + # Fallback to basic prompts if TypeDialog not available + if not $form_result.success or $form_result.use_fallback { + print "â„šī¸ TypeDialog not available. Using basic prompts..." + print "" + + print "Username: " + let username = (input) + print "Password: " + let password = (input --suppress-output) + + print "Do you have MFA enabled? (y/n): " + let has_mfa_input = (input) + let has_mfa = ($has_mfa_input == "y" or $has_mfa_input == "Y") + + let mfa_code = if $has_mfa { + print "MFA Code (6 digits): " + input + } else { + "" + } + + if ($username | is-empty) or ($password | is-empty) { + return { + success: false + error: "Username and password are required" + } + } + + let login_result = (plugin-login $username $password --mfa-code $mfa_code) - if not $form_result.success { return { - success: false - error: $form_result.error + success: true + result: $login_result + username: $username + mfa_enabled: $has_mfa } } let form_values = $form_result.values # Check if user cancelled or didn't confirm - if not ($form_values.confirm_login // false) { + if not ($form_values.auth?.confirm_login? | default false) { return { success: false error: "Login cancelled by user" @@ -814,13 +905,14 @@ export def login-interactive [] : nothing -> record { } # Perform login with provided credentials - let username = ($form_values.username // "") - let password = ($form_values.password // "") - let mfa_code = (if ($form_values.has_mfa // false) { - $form_values.mfa_code // "" + let username = ($form_values.auth?.username? | default "") + let password = ($form_values.auth?.password? | default "") + let has_mfa = ($form_values.auth?.has_mfa? | default false) + let mfa_code = if $has_mfa { + $form_values.auth?.mfa_code? | default "" } else { "" - }) + } if ($username | is-empty) or ($password | is-empty) { return { @@ -836,12 +928,14 @@ export def login-interactive [] : nothing -> record { success: true result: $login_result username: $username - mfa_enabled: ($form_values.has_mfa // false) + mfa_enabled: $has_mfa } } # Interactive MFA enrollment with form -export def mfa-enroll-interactive [] : nothing -> record { +export def mfa-enroll-interactive [ + --backend: string = "tui" +] : nothing -> record { print "🔐 Multi-Factor Authentication Setup" print "" @@ -856,33 +950,103 @@ export def mfa-enroll-interactive [] : nothing -> record { } } - # Run the MFA enrollment form - let form_result = (run-forminquire-form "provisioning/core/shlib/forms/authentication/mfa_enroll.toml") + # Run the MFA enrollment form via bash wrapper + let wrapper_script = "provisioning/core/shlib/mfa-enroll-tty.sh" + let form_result = (run-typedialog-auth-form $wrapper_script --backend $backend) + + # Fallback to basic prompts if TypeDialog not available + if not $form_result.success or $form_result.use_fallback { + print "â„šī¸ TypeDialog not available. Using basic prompts..." + print "" + + print "MFA Type (totp/webauthn/sms): " + let mfa_type = (input) + + let device_name = if ($mfa_type == "totp" or $mfa_type == "webauthn") { + print "Device name: " + input + } else if $mfa_type == "sms" { + "" + } else { + "" + } + + let phone_number = if $mfa_type == "sms" { + print "Phone number (international format, e.g., +1234567890): " + input + } else { + "" + } + + let verification_code = if ($mfa_type == "totp" or $mfa_type == "sms") { + print "Verification code (6 digits): " + input + } else { + "" + } + + print "Generate backup codes? (y/n): " + let generate_backup_input = (input) + let generate_backup = ($generate_backup_input == "y" or $generate_backup_input == "Y") + + let backup_count = if $generate_backup { + print "Number of backup codes (5-20): " + let count_str = (input) + $count_str | into int | default 10 + } else { + 0 + } - if not $form_result.success { return { - success: false - error: $form_result.error + success: true + mfa_type: $mfa_type + device_name: $device_name + phone_number: $phone_number + verification_code: $verification_code + generate_backup_codes: $generate_backup + backup_codes_count: $backup_count } } let form_values = $form_result.values # Check if user confirmed - if not ($form_values.confirm_enroll // false) { + if not ($form_values.mfa?.confirm_enroll? | default false) { return { success: false error: "MFA enrollment cancelled by user" } } - # Determine MFA type and parameters - let mfa_type = if ($form_values.mfa_type | str contains "TOTP") { - "totp" + # Extract MFA type and parameters from form values + let mfa_type = ($form_values.mfa?.type? | default "totp") + let device_name = if $mfa_type == "totp" { + $form_values.mfa?.totp?.device_name? | default "Authenticator App" + } else if $mfa_type == "webauthn" { + $form_values.mfa?.webauthn?.device_name? | default "Security Key" + } else if $mfa_type == "sms" { + "" } else { - "webauthn" + "" } + let phone_number = if $mfa_type == "sms" { + $form_values.mfa?.sms?.phone_number? | default "" + } else { + "" + } + + let verification_code = if $mfa_type == "totp" { + $form_values.mfa?.totp?.verification_code? | default "" + } else if $mfa_type == "sms" { + $form_values.mfa?.sms?.verification_code? | default "" + } else { + "" + } + + let generate_backup = ($form_values.mfa?.generate_backup_codes? | default true) + let backup_count = ($form_values.mfa?.backup_codes_count? | default 10) + # Call the plugin MFA enrollment function let enroll_result = (plugin-mfa-enroll --type $mfa_type) @@ -890,6 +1054,10 @@ export def mfa-enroll-interactive [] : nothing -> record { success: true result: $enroll_result mfa_type: $mfa_type - backup_codes_saved: ($form_values.totp_backups // false) + device_name: $device_name + phone_number: $phone_number + verification_code: $verification_code + generate_backup_codes: $generate_backup + backup_codes_count: $backup_count } } diff --git a/nulib/lib_provisioning/setup/wizard.nu b/nulib/lib_provisioning/setup/wizard.nu index fa4bf38..49e23e0 100644 --- a/nulib/lib_provisioning/setup/wizard.nu +++ b/nulib/lib_provisioning/setup/wizard.nu @@ -7,15 +7,10 @@ # tags = ["setup", "interactive", "wizard"] # version = "3.0.0" # requires = ["nushell:0.109.0"] -# note = "MIGRATION: ForminQuire (Jinja2 templates) archived. Use TypeDialog forms instead (typedialog, typedialog-tui, typedialog-web)" -# migration = "See: provisioning/.coder/archive/forminquire/ (deprecated) → provisioning/.typedialog/provisioning/form.toml (new)" use ./mod.nu * use ./detection.nu * use ./validation.nu * -# ARCHIVED: use ../../forminquire/nulib/forminquire.nu * -# ForminQuire has been archived to: provisioning/.coder/archive/forminquire/ -# New solution: Use TypeDialog for interactive forms (installed automatically by bootstrap) # ============================================================================ # INPUT HELPERS @@ -532,42 +527,96 @@ export def run-minimal-setup []: nothing -> record { } # ============================================================================ -# INTERACTIVE SETUP USING FORMINQUIRE (NEW) +# TYPEDIALOG HELPER FUNCTIONS # ============================================================================ -# Run setup wizard using FormInquire - modern TUI experience -export def run-setup-wizard-interactive []: nothing -> record { +# 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" +]: nothing -> record { + # 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 + let values = (try { + open $json_output | from json + } catch { + return { + success: false + error: "Failed to parse TypeDialog output" + use_fallback: true + } + }) + + { + 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" +]: nothing -> record { print "" print "╔═══════════════════════════════════════════════════════════════╗" - print "║ PROVISIONING SYSTEM SETUP WIZARD (FormInquire) ║" + 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 "" - # 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 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) - # 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: {} - } + # 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 @@ -576,43 +625,34 @@ export def run-setup-wizard-interactive []: nothing -> record { # 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 { . } + | 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 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 - } - } - ) + # 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.config_path)" - print $" Deployment Mode: ($values.deployment_mode)" + 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.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 $" 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!" @@ -621,22 +661,21 @@ export def run-setup-wizard-interactive []: nothing -> record { { completed: true system_config: { - config_path: ($values.config_path) + config_path: ($values.system_config?.config_path? | default (get-config-base-path)) os_name: (detect-os) - cpu_count: ($values.cpu_count | into int) - memory_gb: ($values.memory_gb | into int) + cpu_count: ($values.resources?.cpu_count? | default 4) + memory_gb: ($values.resources?.memory_gb? | default 8) } - deployment_mode: ($values.deployment_mode) + deployment_mode: ($values.deployment_mode? | default "docker-compose") 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) + cpu_count: ($values.resources?.cpu_count? | default 4) + memory_gb: ($values.resources?.memory_gb? | default 8) } 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) + 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) diff --git a/nulib/lib_provisioning/workspace/init.nu b/nulib/lib_provisioning/workspace/init.nu index c383b1d..2065a86 100644 --- a/nulib/lib_provisioning/workspace/init.nu +++ b/nulib/lib_provisioning/workspace/init.nu @@ -6,12 +6,7 @@ # tags = ["workspace", "initialize", "interactive"] # version = "3.0.0" # requires = ["nushell:0.109.0"] -# note = "MIGRATION: ForminQuire (Jinja2 templates) archived. Use TypeDialog forms instead" -# migration = "See: provisioning/.coder/archive/forminquire/ (deprecated) → provisioning/.typedialog/provisioning/form.toml (new)" -# ARCHIVED: use ../../../forminquire/nulib/forminquire.nu * -# ForminQuire has been archived to: provisioning/.coder/archive/forminquire/ -# New solution: Use TypeDialog for interactive forms (typedialog, typedialog-tui, typedialog-web) use ../utils/interface.nu * # Interactive workspace creation with activation prompt diff --git a/nulib/main_provisioning/metadata_handler.nu b/nulib/main_provisioning/metadata_handler.nu index b05df42..c17cfb3 100644 --- a/nulib/main_provisioning/metadata_handler.nu +++ b/nulib/main_provisioning/metadata_handler.nu @@ -5,8 +5,6 @@ # tags = ["metadata", "forms", "validation", "interactive"] # version = "2.0.0" # requires = ["traits.nu"] -# note = "MIGRATION: ForminQuire (Jinja2 templates) archived. Use TypeDialog forms instead" -# migration = "See: provisioning/.coder/archive/forminquire/ (deprecated) → provisioning/.typedialog/provisioning/form.toml (new)" # ============================================================================ # Metadata Handler for Dispatcher Integration @@ -15,9 +13,6 @@ # ============================================================================ use ../lib_provisioning/commands/traits.nu * -# ARCHIVED: use ../../forminquire/nulib/forminquire.nu * -# ForminQuire has been archived to: provisioning/.coder/archive/forminquire/ -# New solution: Use TypeDialog for command metadata and form handling # Validate command exists and meets requirements def validate-command-execution [ diff --git a/nulib/test/README.md b/nulib/test/README.md index c230781..5b3db46 100644 --- a/nulib/test/README.md +++ b/nulib/test/README.md @@ -1,6 +1,7 @@ # Plugin Integration Test Suite -Comprehensive test suite for the Provisioning platform's plugin system, covering authentication, KMS, and orchestrator plugins with graceful fallback testing. +Comprehensive test suite for the Provisioning platform's plugin system, +covering authentication, KMS, and orchestrator plugins with graceful fallback testing. ## Overview @@ -18,7 +19,7 @@ This test suite validates: ### Individual Plugin Tests | File | Purpose | Lines | Tests | -|------|---------|-------|-------| +| ---- | ------- | ----- | ----- | | `../lib_provisioning/plugins/auth_test.nu` | Authentication plugin | 200 | 9 | | `../lib_provisioning/plugins/kms_test.nu` | KMS plugin | 250 | 11 | | `../lib_provisioning/plugins/orchestrator_test.nu` | Orchestrator plugin | 200 | 12 | @@ -29,9 +30,9 @@ This test suite validates: ### Configuration -| File | Purpose | Lines | -|------|---------|-------| -| `../../config/plugin-config.toml` | Plugin configuration | 300 | +| File | Purpose | Lines | +| ---------------------------------- | -------------------- | ----- | +| `../../config/plugin-config.toml` | Plugin configuration | 300 | ## Running Tests diff --git a/services/kms/README.md b/services/kms/README.md index b2fa49f..901304a 100644 --- a/services/kms/README.md +++ b/services/kms/README.md @@ -6,7 +6,8 @@ ## Overview -The KMS configuration system provides a comprehensive, independent configuration for managing encryption keys and secrets. It supports three operational modes: +The KMS configuration system provides a comprehensive, independent configuration for managing encryption keys +and secrets. It supports three operational modes: 1. **Local Mode** - Uses local encryption tools (age, SOPS, Vault) 2. **Remote Mode** - Connects to external KMS servers (Cosmian KMS, AWS KMS, etc.) diff --git a/shlib/README.md b/shlib/README.md new file mode 100644 index 0000000..c8d8993 --- /dev/null +++ b/shlib/README.md @@ -0,0 +1,235 @@ +# Shell Library (shlib) - TTY Wrappers + +**Purpose**: Bash wrappers that overcome Nushell's TTY input limitations in execution stacks. + +## The Problem + +When Nushell scripts call interactive programs (like TypeDialog) within execution stacks, TTY input handling fails: + +```nushell +# This doesn't work properly in Nushell execution stacks: +def run-interactive-form [] { + let result = (^typedialog form input.toml) # TTY issues + process_result $result +} +``` + +**Why?** Nushell's pipeline and execution stack architecture doesn't properly forward TTY file descriptors to child processes in all contexts. + +## The Solution + +**Bash wrappers** handle TTY input, then pass results to Nushell via files: + +```text +┌─────────────────────────────────────────────────────────────┐ +│ User runs Nushell script │ +└─────────────────â”Ŧ───────────────────────────────────────────┘ + │ + v +┌─────────────────────────────────────────────────────────────┐ +│ Nushell calls bash wrapper (shlib/*-tty.sh) │ +└─────────────────â”Ŧ───────────────────────────────────────────┘ + │ + v +┌─────────────────────────────────────────────────────────────┐ +│ Bash wrapper handles TTY input (TypeDialog, prompts, etc) │ +│ - Proper TTY file descriptor handling │ +│ - Interactive input works correctly │ +└─────────────────â”Ŧ───────────────────────────────────────────┘ + │ + v +┌─────────────────────────────────────────────────────────────┐ +│ Wrapper writes output to JSON file │ +└─────────────────â”Ŧ───────────────────────────────────────────┘ + │ + v +┌─────────────────────────────────────────────────────────────┐ +│ Nushell reads JSON file (no TTY issues) │ +│ - File-based IPC is reliable │ +│ - No input stack problems │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Naming Convention + +Scripts in this directory follow the pattern: `{operation}-tty.sh` + +- **`{operation}`**: What the script does (e.g., `setup-wizard`, `auth-login`) +- **`-tty`**: Indicates this is a TTY-handling wrapper +- **`.sh`**: Bash script extension + +**Examples:** +- `setup-wizard-tty.sh` - Setup wizard with TTY-safe input +- `auth-login-tty.sh` - Authentication login with TTY-safe input +- `mfa-enroll-tty.sh` - MFA enrollment with TTY-safe input + +## Current Wrappers + +| Script | Purpose | TypeDialog Form | +| ------ | ------- | --------------- | +| `setup-wizard-tty.sh` | Initial system setup configuration | `.typedialog/core/forms/setup-wizard.toml` | +| `auth-login-tty.sh` | User authentication login | `.typedialog/core/forms/auth-login.toml` | +| `mfa-enroll-tty.sh` | Multi-factor authentication enrollment | `.typedialog/core/forms/mfa-enroll.toml` | + +## Usage from Nushell + +```nushell +# Example: Run setup wizard from Nushell +def run-setup-wizard-interactive [] { + # Call bash wrapper (handles TTY properly) + let wrapper = "provisioning/core/shlib/setup-wizard-tty.sh" + let result = (bash $wrapper | complete) + + if $result.exit_code == 0 { + # Read generated JSON (no TTY issues) + let config = (open provisioning/.typedialog/core/generated/setup-wizard.json | from json) + + # Process config in Nushell + process_config $config + } else { + print "Setup wizard failed" + } +} +``` + +## Usage from Bash/CLI + +```bash +# Direct execution +./provisioning/core/shlib/setup-wizard-tty.sh + +# With environment variable (backend selection) +TYPEDIALOG_BACKEND=web ./provisioning/core/shlib/auth-login-tty.sh + +# With custom output location +OUTPUT_DIR=/tmp ./provisioning/core/shlib/mfa-enroll-tty.sh +``` + +## Architecture Pattern + +All wrappers follow this pattern: + +1. **Input Modes** (fallback chain): + - TypeDialog interactive forms (if binary available) + - Basic bash prompts (fallback) + +2. **Output Format**: + - Nickel config file (`.ncl`) + - JSON export for Nushell (`.json`) + +3. **File Locations**: + - Forms: `provisioning/.typedialog/core/forms/` + - Generated configs: `provisioning/.typedialog/core/generated/` + - Templates: `provisioning/.typedialog/core/templates/` + +4. **Error Handling**: + - Exit code 0 = success + - Exit code 1 = failure/cancelled + - Stderr for error messages + +## TypeDialog Integration + +These wrappers use TypeDialog forms when available: + +```bash +# TypeDialog form location +FORM_PATH="provisioning/.typedialog/core/forms/setup-wizard.toml" + +# Run TypeDialog +if command -v typedialog &> /dev/null; then + typedialog form "$FORM_PATH" \ + --output "$OUTPUT_NCL" \ + --backend "${TYPEDIALOG_BACKEND:-tui}" + + # Export to JSON for Nushell + nickel export --format json "$OUTPUT_NCL" > "$OUTPUT_JSON" +fi +``` + +## Fallback Behavior + +If TypeDialog is not available, wrappers fall back to basic prompts: + +```bash +# Fallback to basic bash prompts +echo "TypeDialog not available. Using basic prompts..." +read -p "Username: " username +read -sp "Password: " password +``` + +This ensures the system always works, even without TypeDialog installed. + +## When to Create a New Wrapper + +Create a new TTY wrapper when: + +1. ✅ **Interactive input is required** (user must enter data) +2. ✅ **Called from Nushell context** (execution stack issues) +3. ✅ **TTY file descriptors matter** (TypeDialog, password prompts, etc.) + +Do NOT create a wrapper when: + +- ❌ Script is non-interactive (no user input) +- ❌ Script only processes files (no TTY needed) +- ❌ Script is already bash (no Nushell context) + +## Troubleshooting + +### Wrapper Not Found + +```bash +# Check wrapper exists and is executable +ls -l provisioning/core/shlib/setup-wizard-tty.sh + +# Make executable if needed +chmod +x provisioning/core/shlib/setup-wizard-tty.sh +``` + +### TTY Input Still Fails + +```bash +# Ensure running from proper TTY +tty # Should show /dev/ttys000 or similar + +# Check stdin is connected to TTY +[ -t 0 ] && echo "stdin is TTY" || echo "stdin is NOT TTY" + +# Run wrapper directly (bypass Nushell) +bash provisioning/core/shlib/setup-wizard-tty.sh +``` + +### JSON Output Not Generated + +```bash +# Check TypeDialog and Nickel are installed +command -v typedialog +command -v nickel + +# Check output directory exists +mkdir -p provisioning/.typedialog/core/generated + +# Check permissions +ls -ld provisioning/.typedialog/core/generated +``` + +## Related Documentation + +- **TypeDialog Forms**: `provisioning/.typedialog/core/forms/README.md` +- **Nushell Integration**: `provisioning/core/nulib/lib_provisioning/setup/wizard.nu` +- **Architecture Decision**: `docs/architecture/adr/ADR-XXX-tty-wrappers.md` + +## Future Improvements + +Potential enhancements (when needed): + +1. **Caching**: Store previous inputs for faster re-runs +2. **Validation**: Pre-validate inputs before calling TypeDialog +3. **Multi-backend**: Support web/tui/cli backends dynamically +4. **Batch mode**: Support non-interactive mode with config file input + +--- + +**Version**: 1.0.0 +**Last Updated**: 2025-01-09 +**Status**: Production Ready +**Maintainer**: Provisioning Core Team diff --git a/shlib/auth-login-tty.sh b/shlib/auth-login-tty.sh new file mode 100755 index 0000000..b367ef6 --- /dev/null +++ b/shlib/auth-login-tty.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Bash wrapper for TypeDialog authentication login +# Handles TTY input and generates Nickel config for Nushell consumption + +set -euo pipefail + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +FORM_PATH="${PROJECT_ROOT}/provisioning/.typedialog/core/forms/auth-login.toml" +OUTPUT_CONFIG="${PROJECT_ROOT}/provisioning/.typedialog/core/generated/auth-login-result.ncl" +OUTPUT_JSON="${PROJECT_ROOT}/provisioning/.typedialog/core/generated/auth-login-result.json" +BACKEND="${TYPEDIALOG_BACKEND:-tui}" + +# Ensure generated directory exists +mkdir -p "$(dirname "${OUTPUT_CONFIG}")" + +# Function to check if typedialog is available +check_typedialog() { + if ! command -v typedialog &> /dev/null; then + echo "ERROR: TypeDialog is not installed" >&2 + echo "Please install TypeDialog first: https://github.com/tweag/typedialog" >&2 + return 1 + fi + return 0 +} + +# Main execution +main() { + echo "🔐 Interactive Authentication Login" + echo "====================================" + echo "" + + # Check TypeDialog availability + if ! check_typedialog; then + exit 1 + fi + + echo "Running TypeDialog authentication form (backend: ${BACKEND})..." + echo "" + + # Run TypeDialog form (no existing config for login) + if typedialog form "${FORM_PATH}" \ + --output "${OUTPUT_CONFIG}" \ + --backend "${BACKEND}"; then + + echo "" + echo "✅ Authentication data saved to: ${OUTPUT_CONFIG}" + + # Export to JSON for easy consumption + if command -v nickel &> /dev/null; then + if nickel export --format json "${OUTPUT_CONFIG}" > "${OUTPUT_JSON}"; then + echo "✅ JSON export saved to: ${OUTPUT_JSON}" + echo "" + echo "You can now read this in Nushell:" + echo " let auth_data = (open ${OUTPUT_JSON} | from json)" + + # Clean up sensitive data after a delay + (sleep 300 && rm -f "${OUTPUT_CONFIG}" "${OUTPUT_JSON}" 2>/dev/null) & + echo "" + echo "âš ī¸ Note: Credentials will be automatically deleted after 5 minutes" + else + echo "âš ī¸ Warning: Failed to export to JSON" >&2 + fi + fi + + exit 0 + else + echo "❌ Authentication cancelled or failed" >&2 + exit 1 + fi +} + +# Run main +main "$@" diff --git a/shlib/forms/authentication/auth_login.toml b/shlib/forms/authentication/auth_login.toml deleted file mode 100644 index c3ff9f8..0000000 --- a/shlib/forms/authentication/auth_login.toml +++ /dev/null @@ -1,65 +0,0 @@ -# Authentication Login Form -# Generated: {{ now_iso }} -# Purpose: Interactive JWT authentication - -[meta] -title = "Authentication Login" -description = "Authenticate with your username and password to obtain a JWT token" -allow_cancel = true - -# ============================================================================ -# CREDENTIALS SECTION -# ============================================================================ - -[items.credentials_header] -type = "text" -prompt = "Account Credentials" -display_only = true - -[items.username] -type = "text" -prompt = "Username" -help = "Your username or email address" -required = true - -[items.password] -type = "text" -prompt = "Password" -help = "Your secure password (input will be hidden)" -required = true -mask = true - -# ============================================================================ -# MFA SECTION (Optional) -# ============================================================================ - -[items.mfa_header] -type = "text" -prompt = "Multi-Factor Authentication (Optional)" -display_only = true - -[items.has_mfa] -type = "confirm" -prompt = "Do you have MFA enabled?" -help = "If your account has multi-factor authentication enabled, you will need to provide the code" - -[items.mfa_code] -type = "text" -prompt = "MFA Code" -help = "6-digit time-based OTP (TOTP) code from your authenticator app" -when = "{{ has_mfa == true }}" - -# ============================================================================ -# CONFIRMATION -# ============================================================================ - -[items.confirm_header] -type = "text" -prompt = "Review Credentials" -display_only = true - -[items.confirm_login] -type = "confirm" -prompt = "Login with these credentials?" -help = "This will authenticate your account and obtain a JWT token" -required = true diff --git a/shlib/forms/authentication/mfa_enroll.toml b/shlib/forms/authentication/mfa_enroll.toml deleted file mode 100644 index c725023..0000000 --- a/shlib/forms/authentication/mfa_enroll.toml +++ /dev/null @@ -1,101 +0,0 @@ -# MFA Enrollment Form -# Generated: {{ now_iso }} -# Purpose: Interactive multi-factor authentication enrollment - -[meta] -title = "Multi-Factor Authentication Setup" -description = "Enroll in multi-factor authentication for enhanced account security" -allow_cancel = true - -# ============================================================================ -# MFA METHOD SELECTION -# ============================================================================ - -[items.method_header] -type = "text" -prompt = "Choose Authentication Method" -display_only = true - -[items.mfa_type] -type = "select" -prompt = "MFA Method" -options = ["TOTP (Time-Based Code)", "WebAuthn/FIDO2 (Security Key)"] -default = "TOTP (Time-Based Code)" -help = "Select your preferred MFA method for account protection" -required = true - -# ============================================================================ -# TOTP SECTION -# ============================================================================ - -[items.totp_header] -type = "text" -prompt = "Time-Based One-Time Password (TOTP)" -display_only = true -when = "{{ mfa_type == 'TOTP (Time-Based Code)' }}" - -[items.totp_app] -type = "text" -prompt = "Authenticator App" -help = "Use apps like Google Authenticator, Authy, Microsoft Authenticator, etc." -display_only = true -when = "{{ mfa_type == 'TOTP (Time-Based Code)' }}" - -[items.totp_code] -type = "text" -prompt = "6-Digit Code from Authenticator App" -help = "Enter the 6-digit code from your authenticator app to verify setup" -required = true -when = "{{ mfa_type == 'TOTP (Time-Based Code)' }}" - -[items.totp_backups] -type = "confirm" -prompt = "Save Recovery Codes?" -help = "Save your backup recovery codes in a secure location (required for account recovery)" -when = "{{ mfa_type == 'TOTP (Time-Based Code)' }}" - -# ============================================================================ -# WEBAUTHN SECTION -# ============================================================================ - -[items.webauthn_header] -type = "text" -prompt = "WebAuthn / FIDO2 Security Key" -display_only = true -when = "{{ mfa_type == 'WebAuthn/FIDO2 (Security Key)' }}" - -[items.webauthn_device] -type = "text" -prompt = "Security Key Device" -help = "Use a hardware security key (YubiKey, Windows Hello, Touch ID, etc.)" -display_only = true -when = "{{ mfa_type == 'WebAuthn/FIDO2 (Security Key)' }}" - -[items.webauthn_ready] -type = "confirm" -prompt = "Security Key Ready?" -help = "Ensure your security key is connected and ready for enrollment" -required = true -when = "{{ mfa_type == 'WebAuthn/FIDO2 (Security Key)' }}" - -[items.webauthn_touch] -type = "text" -prompt = "Touch Your Security Key" -help = "Touch or activate your security key when prompted during enrollment" -display_only = true -when = "{{ mfa_type == 'WebAuthn/FIDO2 (Security Key)' }}" - -# ============================================================================ -# CONFIRMATION -# ============================================================================ - -[items.confirm_header] -type = "text" -prompt = "Review MFA Setup" -display_only = true - -[items.confirm_enroll] -type = "confirm" -prompt = "Enroll in {{ mfa_type }}?" -help = "This will enable additional security for your account" -required = true diff --git a/shlib/forms/infrastructure/cluster_delete_confirm.toml b/shlib/forms/infrastructure/cluster_delete_confirm.toml deleted file mode 100644 index 8cc9ba7..0000000 --- a/shlib/forms/infrastructure/cluster_delete_confirm.toml +++ /dev/null @@ -1,116 +0,0 @@ -# Cluster Deletion Confirmation Form -# Generated: {{ now_iso }} -# Purpose: Confirm destructive cluster deletion operation - -[meta] -title = "Cluster Deletion Confirmation" -description = "This action will permanently delete the entire cluster and all associated resources" -allow_cancel = true - -# ============================================================================ -# CRITICAL WARNING SECTION -# ============================================================================ - -[items.critical_warning] -type = "text" -prompt = "🔴 CRITICAL: Cluster Deletion is Irreversible" -display_only = true - -[items.warning_details] -type = "text" -prompt = "Cluster Deletion will:" -help = """ -â€ĸ Permanently delete all nodes in the cluster -â€ĸ Destroy all persistent volumes and data -â€ĸ Terminate all running applications and services -â€ĸ Remove all persistent configurations -â€ĸ Make cluster inaccessible - cannot be recovered""" -display_only = true - -# ============================================================================ -# CLUSTER INFORMATION -# ============================================================================ - -[items.cluster_info_header] -type = "text" -prompt = "Cluster to Delete" -display_only = true - -[items.cluster_name] -type = "text" -prompt = "Cluster Name" -default = "{{ cluster_name | default('unknown') }}" -display_only = true - -[items.cluster_type] -type = "text" -prompt = "Cluster Type" -default = "{{ cluster_type | default('unknown') }}" -display_only = true - -[items.node_count] -type = "text" -prompt = "Number of Nodes" -default = "{{ node_count | default('unknown') }}" -display_only = true - -[items.total_resources] -type = "text" -prompt = "Total Resources" -help = "Approximate total CPU and memory that will be freed" -default = "{{ total_resources | default('unknown') }}" -display_only = true - -# ============================================================================ -# DEPENDENT RESOURCES -# ============================================================================ - -[items.dependents_header] -type = "text" -prompt = "Resources That Will Be Deleted" -display_only = true - -[items.deployments_count] -type = "text" -prompt = "Deployments" -default = "{{ deployments_count | default('0') }}" -display_only = true - -[items.services_count] -type = "text" -prompt = "Services" -default = "{{ services_count | default('0') }}" -display_only = true - -[items.volumes_count] -type = "text" -prompt = "Persistent Volumes" -default = "{{ volumes_count | default('0') }}" -display_only = true - -# ============================================================================ -# CONFIRMATION -# ============================================================================ - -[items.confirm_header] -type = "text" -prompt = "Final Confirmation Required" -display_only = true - -[items.confirmation_text] -type = "text" -prompt = "Type 'DELETE CLUSTER' to Confirm" -help = "You must type the exact phrase: DELETE CLUSTER" -required = true - -[items.understand_final] -type = "confirm" -prompt = "I understand this operation is permanent and all data will be lost" -help = "Check this box to acknowledge that you understand the consequences" -required = true - -[items.proceed_final] -type = "confirm" -prompt = "Delete cluster '{{ cluster_name | default('cluster') }}' with {{ node_count | default('all') }} nodes?" -help = "This is the final confirmation. There is no undo." -required = true diff --git a/shlib/forms/infrastructure/generic_delete_confirm.toml b/shlib/forms/infrastructure/generic_delete_confirm.toml deleted file mode 100644 index b262e77..0000000 --- a/shlib/forms/infrastructure/generic_delete_confirm.toml +++ /dev/null @@ -1,83 +0,0 @@ -# Generic Resource Deletion Confirmation Form -# Generated: {{ now_iso }} -# Purpose: Generic confirmation for any resource deletion - -[meta] -title = "Resource Deletion Confirmation" -description = "Confirm permanent deletion of resource" -allow_cancel = true - -# ============================================================================ -# WARNING SECTION -# ============================================================================ - -[items.warning_header] -type = "text" -prompt = "âš ī¸ Warning: Permanent Deletion" -display_only = true - -[items.resource_type] -type = "text" -prompt = "Resource Type" -default = "{{ resource_type | default('Resource') }}" -display_only = true - -[items.resource_name] -type = "text" -prompt = "Resource Name" -default = "{{ resource_name | default('unknown') }}" -display_only = true - -[items.resource_id] -type = "text" -prompt = "Resource ID" -help = "Unique identifier of the resource" -default = "{{ resource_id | default('') }}" -display_only = true - -[items.resource_status] -type = "text" -prompt = "Current Status" -default = "{{ resource_status | default('unknown') }}" -display_only = true - -# ============================================================================ -# IMPACT INFORMATION -# ============================================================================ - -[items.impact_header] -type = "text" -prompt = "Deletion Impact" -display_only = true - -[items.irreversible_warning] -type = "text" -prompt = "This action is irreversible" -help = "There is no way to undo this operation" -display_only = true - -[items.data_loss_warning] -type = "text" -prompt = "All associated data will be permanently lost" -help = "This includes configurations, logs, and cached data" -display_only = true - -# ============================================================================ -# CONFIRMATION -# ============================================================================ - -[items.confirm_text] -type = "text" -prompt = "Type 'DELETE' to Confirm" -help = "This prevents accidental deletion" -required = true - -[items.final_confirm] -type = "confirm" -prompt = "I understand this is permanent and all data will be lost" -required = true - -[items.proceed] -type = "confirm" -prompt = "Delete {{ resource_type | default('resource') }} '{{ resource_name | default('unknown') }}'?" -required = true diff --git a/shlib/forms/infrastructure/server_delete_confirm.toml b/shlib/forms/infrastructure/server_delete_confirm.toml deleted file mode 100644 index 1a0e837..0000000 --- a/shlib/forms/infrastructure/server_delete_confirm.toml +++ /dev/null @@ -1,84 +0,0 @@ -# Server Deletion Confirmation Form -# Generated: {{ now_iso }} -# Purpose: Confirm destructive server deletion operation - -[meta] -title = "Server Deletion Confirmation" -description = "This action will permanently delete the server and all associated data" -allow_cancel = true - -# ============================================================================ -# WARNING SECTION -# ============================================================================ - -[items.warning_header] -type = "text" -prompt = "âš ī¸ WARNING: This Action Cannot Be Undone" -display_only = true - -[items.warning_text] -type = "text" -prompt = "Server Deletion will:" -help = """ -â€ĸ Permanently remove the server from all providers -â€ĸ Delete all associated data and configurations -â€ĸ Terminate all running services -â€ĸ Release allocated IP addresses and storage""" -display_only = true - -# ============================================================================ -# SERVER INFORMATION -# ============================================================================ - -[items.server_info_header] -type = "text" -prompt = "Server to Delete" -display_only = true - -[items.server_name] -type = "text" -prompt = "Server Name" -help = "Name of the server being deleted" -default = "{{ server_name | default('unknown') }}" -display_only = true - -[items.server_ip] -type = "text" -prompt = "IP Address" -help = "Current IP address of the server" -default = "{{ server_ip | default('not assigned') }}" -display_only = true - -[items.server_status] -type = "text" -prompt = "Current Status" -help = "Current operational status" -default = "{{ server_status | default('unknown') }}" -display_only = true - -# ============================================================================ -# CONFIRMATION -# ============================================================================ - -[items.confirm_header] -type = "text" -prompt = "Confirm Deletion" -display_only = true - -[items.confirmation_text] -type = "text" -prompt = "Type 'DELETE' to Confirm" -help = "This prevents accidental deletion. You must type the exact word DELETE" -required = true - -[items.final_confirm] -type = "confirm" -prompt = "I understand this is permanent and cannot be undone" -help = "Check this box to confirm you understand the consequences" -required = true - -[items.proceed] -type = "confirm" -prompt = "Delete server {{ server_name | default('server') }}?" -help = "Final confirmation to proceed with deletion" -required = true diff --git a/shlib/forms/infrastructure/taskserv_delete_confirm.toml b/shlib/forms/infrastructure/taskserv_delete_confirm.toml deleted file mode 100644 index d1c7125..0000000 --- a/shlib/forms/infrastructure/taskserv_delete_confirm.toml +++ /dev/null @@ -1,108 +0,0 @@ -# Task Service Deletion Confirmation Form -# Generated: {{ now_iso }} -# Purpose: Confirm destructive taskserv deletion operation - -[meta] -title = "Task Service Deletion Confirmation" -description = "This action will permanently delete the task service and all associated data" -allow_cancel = true - -# ============================================================================ -# WARNING SECTION -# ============================================================================ - -[items.warning_header] -type = "text" -prompt = "âš ī¸ WARNING: This Action Cannot Be Undone" -display_only = true - -[items.warning_text] -type = "text" -prompt = "Task Service Deletion will:" -help = """ -â€ĸ Permanently remove the service definition -â€ĸ Delete all containers and images -â€ĸ Remove all associated volumes and data -â€ĸ Terminate all running tasks -â€ĸ Invalidate all service references""" -display_only = true - -# ============================================================================ -# TASKSERV INFORMATION -# ============================================================================ - -[items.taskserv_info_header] -type = "text" -prompt = "Task Service to Delete" -display_only = true - -[items.taskserv_name] -type = "text" -prompt = "Service Name" -help = "Name of the task service being deleted" -default = "{{ taskserv_name | default('unknown') }}" -display_only = true - -[items.taskserv_type] -type = "text" -prompt = "Service Type" -help = "Type of service (e.g., kubernetes, postgres, redis)" -default = "{{ taskserv_type | default('unknown') }}" -display_only = true - -[items.taskserv_server] -type = "text" -prompt = "Deployed On Server" -help = "Server hosting this task service" -default = "{{ taskserv_server | default('unknown') }}" -display_only = true - -[items.taskserv_status] -type = "text" -prompt = "Current Status" -help = "Operational status of the service" -default = "{{ taskserv_status | default('unknown') }}" -display_only = true - -# ============================================================================ -# IMPACT ANALYSIS -# ============================================================================ - -[items.impact_header] -type = "text" -prompt = "Services That Depend on This" -display_only = true - -[items.dependent_services] -type = "text" -prompt = "Dependent Services" -help = "These services will be affected by deletion" -default = "{{ dependent_services | default('none') }}" -display_only = true - -# ============================================================================ -# CONFIRMATION -# ============================================================================ - -[items.confirm_header] -type = "text" -prompt = "Confirm Deletion" -display_only = true - -[items.confirmation_text] -type = "text" -prompt = "Type 'DELETE' to Confirm" -help = "This prevents accidental deletion. You must type the exact word DELETE" -required = true - -[items.final_confirm] -type = "confirm" -prompt = "I understand this is permanent and will affect dependent services" -help = "Check this box to confirm you understand the consequences" -required = true - -[items.proceed] -type = "confirm" -prompt = "Delete {{ taskserv_type | default('task service') }} '{{ taskserv_name | default('unknown') }}'?" -help = "Final confirmation to proceed with deletion" -required = true diff --git a/shlib/mfa-enroll-tty.sh b/shlib/mfa-enroll-tty.sh new file mode 100755 index 0000000..565a9b1 --- /dev/null +++ b/shlib/mfa-enroll-tty.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Bash wrapper for TypeDialog MFA enrollment +# Handles TTY input and generates Nickel config for Nushell consumption + +set -euo pipefail + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +FORM_PATH="${PROJECT_ROOT}/provisioning/.typedialog/core/forms/mfa-enroll.toml" +OUTPUT_CONFIG="${PROJECT_ROOT}/provisioning/.typedialog/core/generated/mfa-enroll-result.ncl" +OUTPUT_JSON="${PROJECT_ROOT}/provisioning/.typedialog/core/generated/mfa-enroll-result.json" +BACKEND="${TYPEDIALOG_BACKEND:-tui}" + +# Ensure generated directory exists +mkdir -p "$(dirname "${OUTPUT_CONFIG}")" + +# Function to check if typedialog is available +check_typedialog() { + if ! command -v typedialog &> /dev/null; then + echo "ERROR: TypeDialog is not installed" >&2 + echo "Please install TypeDialog first: https://github.com/tweag/typedialog" >&2 + return 1 + fi + return 0 +} + +# Main execution +main() { + echo "🔐 Multi-Factor Authentication Setup" + echo "====================================" + echo "" + + # Check TypeDialog availability + if ! check_typedialog; then + exit 1 + fi + + echo "Running TypeDialog MFA enrollment form (backend: ${BACKEND})..." + echo "" + + # Run TypeDialog form + if typedialog form "${FORM_PATH}" \ + --output "${OUTPUT_CONFIG}" \ + --backend "${BACKEND}"; then + + echo "" + echo "✅ MFA configuration saved to: ${OUTPUT_CONFIG}" + + # Export to JSON for easy consumption + if command -v nickel &> /dev/null; then + if nickel export --format json "${OUTPUT_CONFIG}" > "${OUTPUT_JSON}"; then + echo "✅ JSON export saved to: ${OUTPUT_JSON}" + echo "" + echo "You can now read this in Nushell:" + echo " let mfa_config = (open ${OUTPUT_JSON} | from json)" + + # Clean up sensitive data after a delay + (sleep 300 && rm -f "${OUTPUT_CONFIG}" "${OUTPUT_JSON}" 2>/dev/null) & + echo "" + echo "âš ī¸ Note: MFA data will be automatically deleted after 5 minutes" + else + echo "âš ī¸ Warning: Failed to export to JSON" >&2 + fi + fi + + exit 0 + else + echo "❌ MFA enrollment cancelled or failed" >&2 + exit 1 + fi +} + +# Run main +main "$@" diff --git a/shlib/setup-wizard-tty.sh b/shlib/setup-wizard-tty.sh new file mode 100755 index 0000000..ca9252a --- /dev/null +++ b/shlib/setup-wizard-tty.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# Bash wrapper for TypeDialog setup wizard +# Handles TTY input and generates Nickel config for Nushell consumption + +set -euo pipefail + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +FORM_PATH="${PROJECT_ROOT}/provisioning/.typedialog/core/forms/setup-wizard.toml" +OUTPUT_CONFIG="${PROJECT_ROOT}/provisioning/.typedialog/core/generated/setup-wizard-result.ncl" +OUTPUT_JSON="${PROJECT_ROOT}/provisioning/.typedialog/core/generated/setup-wizard-result.json" +BACKEND="${TYPEDIALOG_BACKEND:-tui}" + +# Ensure generated directory exists +mkdir -p "$(dirname "${OUTPUT_CONFIG}")" + +# Default config template +DEFAULT_CONFIG="${PROJECT_ROOT}/provisioning/.typedialog/core/generated/setup-wizard-defaults.ncl" + +# Function to create default config +create_default_config() { + local config_path="${1:-${HOME}/.config/provisioning}" + local cpu_count="${2:-4}" + local memory_gb="${3:-8}" + + cat > "${DEFAULT_CONFIG}" < /dev/null; then + echo "ERROR: TypeDialog is not installed" >&2 + echo "Please install TypeDialog first: https://github.com/tweag/typedialog" >&2 + return 1 + fi + return 0 +} + +# Main execution +main() { + echo "╔═══════════════════════════════════════════════════════════════╗" + echo "║ PROVISIONING SYSTEM SETUP WIZARD ║" + echo "║ (TypeDialog - Bash Wrapper) ║" + echo "╚═══════════════════════════════════════════════════════════════╝" + echo "" + + # Check TypeDialog availability + if ! check_typedialog; then + exit 1 + fi + + # Detect system defaults + local default_config_path="${HOME}/.config/provisioning" + local default_cpu_count=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo "4") + local default_memory_gb=$(($(free -g 2>/dev/null | awk '/^Mem:/{print $2}' || sysctl -n hw.memsize 2>/dev/null | awk '{print int($1/1024/1024/1024)}' || echo "8"))) + + # Create default config + create_default_config "${default_config_path}" "${default_cpu_count}" "${default_memory_gb}" + + echo "Running TypeDialog setup wizard (backend: ${BACKEND})..." + echo "" + + # Run TypeDialog nickel-roundtrip + if typedialog nickel-roundtrip "${DEFAULT_CONFIG}" "${FORM_PATH}" \ + --output "${OUTPUT_CONFIG}" \ + --backend "${BACKEND}"; then + + echo "" + echo "✅ Configuration saved to: ${OUTPUT_CONFIG}" + + # Export to JSON for easy consumption + if command -v nickel &> /dev/null; then + if nickel export --format json "${OUTPUT_CONFIG}" > "${OUTPUT_JSON}"; then + echo "✅ JSON export saved to: ${OUTPUT_JSON}" + echo "" + echo "You can now use this configuration in Nushell scripts:" + echo " let config = (open ${OUTPUT_JSON} | from json)" + else + echo "âš ī¸ Warning: Failed to export to JSON" >&2 + fi + fi + + exit 0 + else + echo "❌ TypeDialog wizard failed or was cancelled" >&2 + exit 1 + fi +} + +# Run main +main "$@"