chore: forminquire full replace with typedialog and wrappers -tty.sh
This commit is contained in:
parent
a327f59bf7
commit
6c46596cb3
16
CHANGELOG.md
16
CHANGELOG.md
@ -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 |
|
||||||
|
|||||||
@ -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:
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 [
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
235
shlib/README.md
Normal 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
75
shlib/auth-login-tty.sh
Executable 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 "$@"
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
75
shlib/mfa-enroll-tty.sh
Executable 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
120
shlib/setup-wizard-tty.sh
Executable 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 "$@"
|
||||||
Loading…
x
Reference in New Issue
Block a user