chore: forminquire full replace with typedialog and wrappers -tty.sh

This commit is contained in:
Jesús Pérez 2026-01-09 15:08:49 +00:00
parent a327f59bf7
commit 6c46596cb3
Signed by: jesus
GPG Key ID: 9F243E355E0BC939
22 changed files with 843 additions and 950 deletions

View File

@ -8,7 +8,8 @@
## 📋 Summary ## 📋 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.
--- ---
@ -94,12 +95,15 @@ Service definitions and configurations
- Service descriptions - Service descriptions
- Service management - Service management
### forminquire/ directory ### forminquire/ directory (ARCHIVED)
Form inquiry interface **Status**: DEPRECATED - Archived to `.coder/archive/forminquire/`
- Interactive form system **Replacement**: TypeDialog forms (`.typedialog/provisioning/`)
- User input handling
- Legacy: Jinja2-based form system
- Archived: 2025-01-09
- Replaced by: TypeDialog with bash wrappers for TTY-safe input
### Additional Files ### Additional Files
@ -114,7 +118,7 @@ Form inquiry interface
## 📊 Change Statistics ## 📊 Change Statistics
| Category | Files | Lines Added | Lines Removed | Status | | Category | Files | Lines Added | Lines Removed | Status |
|----------|-------|-------------|---------------|--------| | -------- | ----- | ----------- | ------------- | ------ |
| CLI | 3 | 780+ | 30+ | Major update | | CLI | 3 | 780+ | 30+ | Major update |
| Config System | 15+ | 300+ | 200+ | Refactored | | Config System | 15+ | 300+ | 200+ | Refactored |
| AI/Docs | 8+ | 350+ | 100+ | Enhanced | | AI/Docs | 8+ | 350+ | 100+ | Enhanced |

View File

@ -2,7 +2,8 @@
## Our Pledge ## 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 - Age
- Body size - Body size
@ -44,7 +45,8 @@ Examples of unacceptable behavior include:
## Enforcement Responsibilities ## 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: Maintainers have the right and responsibility to:

View File

@ -4,7 +4,8 @@ Thank you for your interest in contributing! This document provides guidelines a
## Code of Conduct ## 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 ## Getting Started
@ -121,7 +122,7 @@ Maintainers handle releases following semantic versioning:
- MINOR: New features (backward compatible) - MINOR: New features (backward compatible)
- PATCH: Bug fixes - PATCH: Bug fixes
## Questions? ## Questions
- Check existing documentation and issues - Check existing documentation and issues
- Ask in discussions or open an issue - Ask in discussions or open an issue

View File

@ -9,7 +9,9 @@
# Core Engine # 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 ## Overview

View File

@ -275,7 +275,8 @@ servers = [
] ]
```plaintext ```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** ### 🚀 **Advanced Features**
@ -372,4 +373,5 @@ ai/
5. **Validation** - Syntax and semantic validation 5. **Validation** - Syntax and semantic validation
6. **Output** - Formatted Nickel files and user feedback 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.

View File

@ -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

View File

@ -5,16 +5,11 @@
# tags = ["authentication", "jwt", "interactive", "login"] # tags = ["authentication", "jwt", "interactive", "login"]
# version = "3.0.0" # version = "3.0.0"
# requires = ["nushell:0.109.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 # Authentication Plugin Wrapper with HTTP Fallback
# Provides graceful degradation to HTTP API when nu_plugin_auth is unavailable # Provides graceful degradation to HTTP API when nu_plugin_auth is unavailable
use ../config/accessor.nu * 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 * use ../commands/traits.nu *
# Check if auth plugin is available # 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)" 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 # Interactive login with form
export def login-interactive [] : nothing -> record { export def login-interactive [
--backend: string = "tui"
] : nothing -> record {
print "🔐 Interactive Authentication" print "🔐 Interactive Authentication"
print "" print ""
# Run the login form # Run the login form via bash wrapper
let form_result = (run-forminquire-form "provisioning/core/shlib/forms/authentication/auth_login.toml") let wrapper_script = "provisioning/core/shlib/auth-login-tty.sh"
let form_result = (run-typedialog-auth-form $wrapper_script --backend $backend)
if not $form_result.success { # 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 { return {
success: false success: false
error: $form_result.error error: "Username and password are required"
}
}
let login_result = (plugin-login $username $password --mfa-code $mfa_code)
return {
success: true
result: $login_result
username: $username
mfa_enabled: $has_mfa
} }
} }
let form_values = $form_result.values let form_values = $form_result.values
# Check if user cancelled or didn't confirm # 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 { return {
success: false success: false
error: "Login cancelled by user" error: "Login cancelled by user"
@ -814,13 +905,14 @@ export def login-interactive [] : nothing -> record {
} }
# Perform login with provided credentials # Perform login with provided credentials
let username = ($form_values.username // "") let username = ($form_values.auth?.username? | default "")
let password = ($form_values.password // "") let password = ($form_values.auth?.password? | default "")
let mfa_code = (if ($form_values.has_mfa // false) { let has_mfa = ($form_values.auth?.has_mfa? | default false)
$form_values.mfa_code // "" let mfa_code = if $has_mfa {
$form_values.auth?.mfa_code? | default ""
} else { } else {
"" ""
}) }
if ($username | is-empty) or ($password | is-empty) { if ($username | is-empty) or ($password | is-empty) {
return { return {
@ -836,12 +928,14 @@ export def login-interactive [] : nothing -> record {
success: true success: true
result: $login_result result: $login_result
username: $username username: $username
mfa_enabled: ($form_values.has_mfa // false) mfa_enabled: $has_mfa
} }
} }
# Interactive MFA enrollment with form # 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 "🔐 Multi-Factor Authentication Setup"
print "" print ""
@ -856,33 +950,103 @@ export def mfa-enroll-interactive [] : nothing -> record {
} }
} }
# Run the MFA enrollment form # Run the MFA enrollment form via bash wrapper
let form_result = (run-forminquire-form "provisioning/core/shlib/forms/authentication/mfa_enroll.toml") 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 { return {
success: false success: true
error: $form_result.error 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 let form_values = $form_result.values
# Check if user confirmed # Check if user confirmed
if not ($form_values.confirm_enroll // false) { if not ($form_values.mfa?.confirm_enroll? | default false) {
return { return {
success: false success: false
error: "MFA enrollment cancelled by user" error: "MFA enrollment cancelled by user"
} }
} }
# Determine MFA type and parameters # Extract MFA type and parameters from form values
let mfa_type = if ($form_values.mfa_type | str contains "TOTP") { let mfa_type = ($form_values.mfa?.type? | default "totp")
"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 { } 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 # Call the plugin MFA enrollment function
let enroll_result = (plugin-mfa-enroll --type $mfa_type) let enroll_result = (plugin-mfa-enroll --type $mfa_type)
@ -890,6 +1054,10 @@ export def mfa-enroll-interactive [] : nothing -> record {
success: true success: true
result: $enroll_result result: $enroll_result
mfa_type: $mfa_type 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
} }
} }

View File

@ -7,15 +7,10 @@
# tags = ["setup", "interactive", "wizard"] # tags = ["setup", "interactive", "wizard"]
# version = "3.0.0" # version = "3.0.0"
# requires = ["nushell:0.109.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 ./mod.nu *
use ./detection.nu * use ./detection.nu *
use ./validation.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 # 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 # Run TypeDialog form via bash wrapper and return parsed result
export def run-setup-wizard-interactive []: nothing -> record { # 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 "╔═══════════════════════════════════════════════════════════════╗" print "╔═══════════════════════════════════════════════════════════════╗"
print "║ PROVISIONING SYSTEM SETUP WIZARD (FormInquire) ║" print "║ PROVISIONING SYSTEM SETUP WIZARD (TypeDialog) ║"
print "║ ║" print "║ ║"
print "║ This wizard will guide you through setting up provisioning ║" print "║ This wizard will guide you through setting up provisioning ║"
print "║ for your infrastructure automation needs. ║" print "║ for your infrastructure automation needs. ║"
print "╚═══════════════════════════════════════════════════════════════╝" print "╚═══════════════════════════════════════════════════════════════╝"
print "" print ""
# Prepare context with system information for form defaults # Run the TypeDialog-based wizard via bash wrapper
let context = { let wrapper_script = "provisioning/core/shlib/setup-wizard-tty.sh"
config_path: (get-config-base-path) let form_result = (run-typedialog-form $wrapper_script --backend $backend)
cpu_count: (get-cpu-count)
memory_gb: (get-system-memory-gb)
disk_gb: 100
}
# Run the FormInquire-based wizard # If TypeDialog not available or failed, fall back to basic wizard
let form_result = (setup-wizard-form) if (not $form_result.success or $form_result.use_fallback) {
print-setup-info "Falling back to basic interactive wizard..."
if not $form_result.success { return (run-setup-wizard)
print-setup-warning "Setup cancelled or failed"
return {
completed: false
system_config: {}
deployment_mode: ""
providers: []
resources: {}
security: {}
workspace: {}
}
} }
# Extract values from form results # Extract values from form results
@ -576,43 +625,34 @@ export def run-setup-wizard-interactive []: nothing -> record {
# Collect selected providers # Collect selected providers
let providers = ( let providers = (
[] []
| if ($values.provider_upcloud? | default false) { append "upcloud" } else { . } | if ($values.providers?.upcloud? | default false) { append "upcloud" } else { . }
| if ($values.provider_aws? | default false) { append "aws" } else { . } | if ($values.providers?.aws? | default false) { append "aws" } else { . }
| if ($values.provider_hetzner? | default false) { append "hetzner" } else { . } | if ($values.providers?.hetzner? | default false) { append "hetzner" } else { . }
| if ($values.provider_local? | default false) { append "local" } else { . } | if ($values.providers?.local? | default false) { append "local" } else { . }
) )
# Ensure at least one provider # Ensure at least one provider
let providers_final = if ($providers | length) == 0 { ["local"] } else { $providers } let providers_final = if ($providers | length) == 0 { ["local"] } else { $providers }
# Create workspace config if requested # Create workspace config
let workspace_final = ( let workspace_final = {
if ($values.create_workspace? | default false) { create_workspace: ($values.workspace?.create_workspace? | default false)
{ name: ($values.workspace?.name? | default "default")
create_workspace: true description: ($values.workspace?.description? | default "")
name: ($values.workspace_name? | default "default")
description: ($values.workspace_description? | default "")
} }
} else {
{
create_workspace: false
}
}
)
# Display summary # Display summary
print "" print ""
print-setup-header "Setup Summary" print-setup-header "Setup Summary"
print "" print ""
print "Configuration Details:" print "Configuration Details:"
print $" Config Path: ($values.config_path)" print $" Config Path: ($values.system_config?.config_path? | default (get-config-base-path))"
print $" Deployment Mode: ($values.deployment_mode)" print $" Deployment Mode: ($values.deployment_mode? | default 'docker-compose')"
print $" Providers: ($providers_final | str join ', ')" print $" Providers: ($providers_final | str join ', ')"
print $" CPUs: ($values.cpu_count)" print $" CPUs: ($values.resources?.cpu_count? | default 4)"
print $" Memory: ($values.memory_gb) GB" print $" Memory: ($values.resources?.memory_gb? | default 8) GB"
print $" Disk: ($values.disk_gb) GB" print $" MFA Enabled: (if ($values.security?.enable_mfa? | default false) { 'Yes' } else { 'No' })"
print $" MFA Enabled: (if ($values.enable_mfa? | default false) { 'Yes' } else { 'No' })" print $" Audit Logging: (if ($values.security?.enable_audit? | default false) { 'Yes' } else { 'No' })"
print $" Audit Logging: (if ($values.enable_audit_logging? | default false) { 'Yes' } else { 'No' })"
print "" print ""
print-setup-success "Configuration confirmed!" print-setup-success "Configuration confirmed!"
@ -621,22 +661,21 @@ export def run-setup-wizard-interactive []: nothing -> record {
{ {
completed: true completed: true
system_config: { system_config: {
config_path: ($values.config_path) config_path: ($values.system_config?.config_path? | default (get-config-base-path))
os_name: (detect-os) os_name: (detect-os)
cpu_count: ($values.cpu_count | into int) cpu_count: ($values.resources?.cpu_count? | default 4)
memory_gb: ($values.memory_gb | into int) memory_gb: ($values.resources?.memory_gb? | default 8)
} }
deployment_mode: ($values.deployment_mode) deployment_mode: ($values.deployment_mode? | default "docker-compose")
providers: $providers_final providers: $providers_final
resources: { resources: {
cpu_count: ($values.cpu_count | into int) cpu_count: ($values.resources?.cpu_count? | default 4)
memory_gb: ($values.memory_gb | into int) memory_gb: ($values.resources?.memory_gb? | default 8)
disk_gb: ($values.disk_gb | into int)
} }
security: { security: {
enable_mfa: ($values.enable_mfa? | default false) enable_mfa: ($values.security?.enable_mfa? | default true)
enable_audit: ($values.enable_audit_logging? | default false) enable_audit: ($values.security?.enable_audit? | default true)
require_approval_for_destructive: ($values.require_approval? | default false) require_approval_for_destructive: ($values.security?.require_approval_for_destructive? | default true)
} }
workspace: $workspace_final workspace: $workspace_final
timestamp: (date now) timestamp: (date now)

View File

@ -6,12 +6,7 @@
# tags = ["workspace", "initialize", "interactive"] # tags = ["workspace", "initialize", "interactive"]
# version = "3.0.0" # version = "3.0.0"
# requires = ["nushell:0.109.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 * use ../utils/interface.nu *
# Interactive workspace creation with activation prompt # Interactive workspace creation with activation prompt

View File

@ -5,8 +5,6 @@
# tags = ["metadata", "forms", "validation", "interactive"] # tags = ["metadata", "forms", "validation", "interactive"]
# version = "2.0.0" # version = "2.0.0"
# requires = ["traits.nu"] # 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 # Metadata Handler for Dispatcher Integration
@ -15,9 +13,6 @@
# ============================================================================ # ============================================================================
use ../lib_provisioning/commands/traits.nu * 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 # Validate command exists and meets requirements
def validate-command-execution [ def validate-command-execution [

View File

@ -1,6 +1,7 @@
# Plugin Integration Test Suite # 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 ## Overview
@ -18,7 +19,7 @@ This test suite validates:
### Individual Plugin Tests ### Individual Plugin Tests
| File | Purpose | Lines | Tests | | File | Purpose | Lines | Tests |
|------|---------|-------|-------| | ---- | ------- | ----- | ----- |
| `../lib_provisioning/plugins/auth_test.nu` | Authentication plugin | 200 | 9 | | `../lib_provisioning/plugins/auth_test.nu` | Authentication plugin | 200 | 9 |
| `../lib_provisioning/plugins/kms_test.nu` | KMS plugin | 250 | 11 | | `../lib_provisioning/plugins/kms_test.nu` | KMS plugin | 250 | 11 |
| `../lib_provisioning/plugins/orchestrator_test.nu` | Orchestrator plugin | 200 | 12 | | `../lib_provisioning/plugins/orchestrator_test.nu` | Orchestrator plugin | 200 | 12 |
@ -30,7 +31,7 @@ This test suite validates:
### Configuration ### Configuration
| File | Purpose | Lines | | File | Purpose | Lines |
|------|---------|-------| | ---------------------------------- | -------------------- | ----- |
| `../../config/plugin-config.toml` | Plugin configuration | 300 | | `../../config/plugin-config.toml` | Plugin configuration | 300 |
## Running Tests ## Running Tests

View File

@ -6,7 +6,8 @@
## Overview ## 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) 1. **Local Mode** - Uses local encryption tools (age, SOPS, Vault)
2. **Remote Mode** - Connects to external KMS servers (Cosmian KMS, AWS KMS, etc.) 2. **Remote Mode** - Connects to external KMS servers (Cosmian KMS, AWS KMS, etc.)

235
shlib/README.md Normal file
View File

@ -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

75
shlib/auth-login-tty.sh Executable file
View File

@ -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 "$@"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

75
shlib/mfa-enroll-tty.sh Executable file
View File

@ -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 "$@"

120
shlib/setup-wizard-tty.sh Executable file
View File

@ -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}" <<EOF
{
system_config = {
config_path = "${config_path}",
use_defaults = true,
},
deployment_mode = "docker-compose",
providers = {
upcloud = false,
aws = false,
hetzner = false,
local = true,
},
resources = {
cpu_count = ${cpu_count},
memory_gb = ${memory_gb},
},
security = {
enable_mfa = true,
enable_audit = true,
require_approval_for_destructive = true,
},
workspace = {
create_workspace = true,
name = "default",
description = "Default workspace",
},
}
EOF
}
# 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 "╔═══════════════════════════════════════════════════════════════╗"
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 "$@"