- DAG architecture: `dag show/validate/export` (nulib/main_provisioning/dag.nu),
config loader (lib_provisioning/config/loader/dag.nu), taskserv dag-executor.
Backed by schemas/lib/dag/*.ncl; orchestrator emits NATS events via
WorkspaceComposition::into_workflow. See ADR-020, ADR-021.
- Unified Component Architecture: components/mod.nu, main_provisioning/
{components,workflow,extensions,ontoref-queries}.nu. Full workflow engine with
topological sort and NATS subject emission. Blocks A-H complete (libre-daoshi).
- Commands-registry: nulib/commands-registry.ncl (Nickel source, 314 lines) +
JSON cache at ~/.cache/provisioning/commands-registry.json rebuilt on source
change. cli/provisioning fast-path alias expansion avoids cold Nu startup.
ADDING_COMMANDS.md documents new-command workflow.
- Platform service manager: service-manager.nu (+573), startup.nu (+611),
service-check.nu (+255); autostart/bootstrap/health/target refactored.
- Nushell 0.112.2 migration: removed all try/catch and bash redirections;
external commands prefixed with ^; type signatures enforced. Driven by
scripts/refactor-try-catch{,-simplified}.nu.
- TTY stack: removed shlib/*-tty.sh; replaced by cli/tty-dispatch.sh,
tty-filter.sh, tty-commands.conf.
- New domain modules: images/ (golden image lifecycle), workspace/{state,sync}.nu,
main_provisioning/{bootstrap,cluster-deploy,fip,state}.nu, commands/{state,
build,integrations/auth,utilities/alias}.nu, platform.nu expanded (+874).
- Config loader overhaul: loader/core.nu slimmed (-759), cache/core.nu
refactored (-454), removed legacy loaders/file_loader.nu (-330).
- Thirteen new provisioning-<domain>.nu top-level modules for bash dispatcher.
- Tests: test_workspace_state.nu (+351); updates to test_oci_registry,
test_services.
- README + CHANGELOG updated.
467 lines
13 KiB
Markdown
467 lines
13 KiB
Markdown
# Provisioning CLI - Flow-Aware TTY Command Management
|
|
|
|
## Architecture Overview
|
|
|
|
The provisioning wrapper (`provisioning/core/cli/provisioning`) is a **flow controller** that manages three execution paths for command handling:
|
|
|
|
1. **Standalone TTY** - Interactive commands that exit after execution
|
|
2. **Pipeline TTY** - Interactive commands that output for piping to other commands
|
|
3. **Regular** - Standard Nushell command processing
|
|
|
|
This design enables:
|
|
- Interactive commands (TTY input) without blocking Nushell
|
|
- Inter-command piping of TTY output to subsequent commands
|
|
- Same-command flow (TTY input → Nushell processing in one execution)
|
|
- Daemon optimization for non-interactive commands
|
|
|
|
## How Flow Management Works
|
|
|
|
### Execution Flow
|
|
|
|
```text
|
|
User Command: provisioning <cmd> <args>
|
|
↓
|
|
Bash wrapper (provisioning)
|
|
↓
|
|
┌──────────────────────────────────────┐
|
|
│ Phase 1: TTY Command Detection │
|
|
│ - Read tty-commands.conf registry │
|
|
│ - Match command pattern │
|
|
└──────────────────────────────────────┘
|
|
↓
|
|
├─→ Not a TTY command → Continue to Nushell (normal processing)
|
|
│
|
|
└─→ TTY command found → Check flow type
|
|
↓
|
|
├─→ flow=exit → Execute wrapper, exit immediately
|
|
├─→ flow=pipe → Execute wrapper, output to stdout, exit (allows piping)
|
|
└─→ flow=continue → Execute wrapper, capture output, continue to Nushell
|
|
($env.TTY_OUTPUT available in Nushell)
|
|
```
|
|
|
|
### Flow Types Explained
|
|
|
|
#### 1. Standalone TTY Commands (flow=exit)
|
|
|
|
**Use case**: Interactive forms, setup wizards, authentication dialogs
|
|
|
|
**Example**: `provisioning setup wizard`
|
|
|
|
**Flow**:
|
|
|
|
```bash
|
|
Bash wrapper → TTY filter detects "setup wizard" → flow=exit
|
|
↓
|
|
Execute wrapper: core/shlib/setup-wizard-tty.sh
|
|
↓
|
|
User interaction (TypeDialog form)
|
|
↓
|
|
Exit wrapper → Exit bash wrapper
|
|
↓
|
|
Never reaches Nushell
|
|
```
|
|
|
|
**Registry entry**:
|
|
|
|
```bash
|
|
"setup wizard" "core/shlib/setup-wizard-tty.sh" "exit"
|
|
```
|
|
|
|
#### 2. Pipeline TTY Commands (flow=pipe)
|
|
|
|
**Use case**: Getting user input to pipe to another command
|
|
|
|
**Example**: `provisioning auth get-key | provisioning deploy --api-key-stdin`
|
|
|
|
**Flow**:
|
|
|
|
```bash
|
|
Bash wrapper → TTY filter detects "auth get-key" → flow=pipe
|
|
↓
|
|
Execute wrapper: core/shlib/auth-get-key-tty.sh
|
|
↓
|
|
User provides API key via TTY prompt
|
|
↓
|
|
Wrapper outputs API key to stdout
|
|
↓
|
|
Exit wrapper (process exits, pipe has captured output)
|
|
↓
|
|
Next command receives API key from stdin
|
|
```
|
|
|
|
**Registry entry**:
|
|
|
|
```bash
|
|
"auth get-key" "core/shlib/auth-get-key-tty.sh" "pipe"
|
|
```
|
|
|
|
**Wrapper requirements** (flow=pipe):
|
|
- Must output result to stdout
|
|
- Output must be newline-terminated
|
|
- Exit with proper code (0=success, non-zero=error)
|
|
|
|
#### 3. Continue-to-Nushell TTY Commands (flow=continue)
|
|
|
|
**Use case**: TTY input that needs further processing in Nushell
|
|
|
|
**Example**: `provisioning auth integrate --provider azure`
|
|
|
|
**Flow**:
|
|
|
|
```bash
|
|
Bash wrapper → TTY filter detects "auth integrate" → flow=continue
|
|
↓
|
|
Execute wrapper: core/shlib/auth-integrate-tty.sh
|
|
↓
|
|
User provides credentials via TTY prompt
|
|
↓
|
|
Wrapper outputs credentials (usually JSON) to stdout
|
|
↓
|
|
Filter CAPTURES output to $TTY_OUTPUT environment variable
|
|
↓
|
|
Set $env.PROVISIONING_BYPASS_DAEMON=true (skip daemon)
|
|
↓
|
|
Return 0 WITHOUT EXITING (continue to Nushell)
|
|
↓
|
|
Nushell dispatcher receives both:
|
|
- CLI args: --provider azure
|
|
- TTY output: $env.TTY_OUTPUT (credentials JSON)
|
|
↓
|
|
Nushell script processes both, completes integration
|
|
```
|
|
|
|
**Registry entry**:
|
|
|
|
```bash
|
|
"auth integrate" "core/shlib/auth-integrate-tty.sh" "continue"
|
|
```
|
|
|
|
**Wrapper requirements** (flow=continue):
|
|
- Must output result to stdout (usually JSON for structured data)
|
|
- Exit with proper code (0=success, non-zero=error)
|
|
|
|
**Nushell script requirements** (receives flow=continue output):
|
|
|
|
```nushell
|
|
export def "provisioning auth integrate" [--provider: string] {
|
|
# Check if TTY output exists (guard pattern)
|
|
let tty_output = ($env.TTY_OUTPUT? | default "")
|
|
if ($tty_output | is-empty) {
|
|
error make {msg: "No credentials provided via TTY"}
|
|
}
|
|
|
|
# Parse TTY output (credentials)
|
|
let credentials = ($tty_output | from json)
|
|
|
|
# Use both TTY input ($credentials) and CLI args ($provider)
|
|
# Complete integration logic...
|
|
|
|
# Clear sensitive data after use
|
|
hide-env TTY_OUTPUT
|
|
}
|
|
```
|
|
|
|
#### 4. Regular Commands
|
|
|
|
**Use case**: Standard provisioning operations
|
|
|
|
**Example**: `provisioning server list`
|
|
|
|
**Flow**:
|
|
|
|
```bash
|
|
Bash wrapper → TTY filter checks registry → Not found → Return 1
|
|
↓
|
|
Continue to normal processing:
|
|
- Fast-path checks (help, workspace, env, etc.)
|
|
- Daemon check (if applicable)
|
|
- Nushell dispatcher
|
|
```
|
|
|
|
## Registry Format
|
|
|
|
**File**: `provisioning/core/cli/tty-commands.conf`
|
|
|
|
**Three-field format**: `"PATTERN" "WRAPPER_PATH" "FLOW_TYPE"`
|
|
|
|
```bash
|
|
# Exact command match (e.g., "setup wizard" matches "provisioning setup wizard")
|
|
"setup wizard" "core/shlib/setup-wizard-tty.sh" "exit"
|
|
|
|
# Paths are relative to $PROVISIONING
|
|
"auth get-key" "core/shlib/auth-get-key-tty.sh" "pipe"
|
|
|
|
# Flow types: exit | pipe | continue
|
|
"auth integrate" "core/shlib/auth-integrate-tty.sh" "continue"
|
|
```
|
|
|
|
### Flow Type Decision Matrix
|
|
|
|
| Interaction | Flow Type | Example |
|
|
| ----------- | --------- | ------- |
|
|
| Interactive form, no output needed | `exit` | Setup wizard, auth login |
|
|
| User input → pipe to next command | `pipe` | API key for piping to deploy |
|
|
| User input → same-command Nushell processing | `continue` | Credentials for integration |
|
|
|
|
## Adding New TTY Commands
|
|
|
|
### Step 1: Create Wrapper Script
|
|
|
|
Create wrapper in `provisioning/core/shlib/`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
main() {
|
|
local input
|
|
|
|
# Get input from user
|
|
read -rsp "Prompt: " input
|
|
echo # Newline
|
|
|
|
# For flow=pipe: output to stdout
|
|
# For flow=continue: output to stdout (will be captured by filter)
|
|
echo "$input"
|
|
|
|
return 0
|
|
}
|
|
|
|
main "$@"
|
|
```
|
|
|
|
Make it executable:
|
|
|
|
```bash
|
|
chmod +x provisioning/core/shlib/your-wrapper-tty.sh
|
|
```
|
|
|
|
### Step 2: Add Registry Entry
|
|
|
|
Edit `provisioning/core/cli/tty-commands.conf`:
|
|
|
|
```bash
|
|
# Standalone TTY
|
|
"your command" "core/shlib/your-wrapper-tty.sh" "exit"
|
|
|
|
# Pipeline TTY
|
|
"get something" "core/shlib/get-something-tty.sh" "pipe"
|
|
|
|
# Continue-to-Nushell TTY
|
|
"setup something" "core/shlib/setup-something-tty.sh" "continue"
|
|
```
|
|
|
|
### Step 3: No Wrapper Modifications Required
|
|
|
|
The provisioning wrapper automatically:
|
|
- Reads registry
|
|
- Matches command pattern
|
|
- Routes based on flow type
|
|
- Handles all three flows
|
|
|
|
**No need to modify provisioning wrapper for new commands!**
|
|
|
|
## Wrapper Script Requirements
|
|
|
|
### For All Wrappers
|
|
|
|
- **Shebang**: `#!/bin/bash`
|
|
- **Safety**: `set -euo pipefail`
|
|
- **Arguments**: Accept `"${@}"` from wrapper
|
|
- **Exit codes**: 0=success, non-zero=error
|
|
- **Validation**: `shellcheck` passes without warnings
|
|
|
|
### For flow=exit Wrappers
|
|
|
|
- Complete all interaction in wrapper
|
|
- Exit with proper code (0=success, non-zero=error)
|
|
- Output shown directly to user (from wrapper)
|
|
|
|
### For flow=pipe Wrappers
|
|
|
|
- Get input from user (TTY)
|
|
- Output result to stdout
|
|
- Output must be newline-terminated
|
|
- Exit with proper code (0=success, non-zero=error)
|
|
|
|
### For flow=continue Wrappers
|
|
|
|
- Get input from user (TTY)
|
|
- Output result to stdout (usually JSON)
|
|
- Exit with proper code (0=success, non-zero=error)
|
|
- Filter captures output → $TTY_OUTPUT
|
|
- Nushell script reads $env.TTY_OUTPUT
|
|
|
|
## Environment Variables
|
|
|
|
### Exported by Filter (flow=continue only)
|
|
|
|
- **`$TTY_OUTPUT`**: Captured output from wrapper (available in Nushell as `$env.TTY_OUTPUT`)
|
|
- **`$PROVISIONING_BYPASS_DAEMON`**: Set to "true" to skip daemon (flow=continue automatically sets this)
|
|
- **`$TTY_WRAPPER_EXECUTED`**: Set to "true" when TTY wrapper was executed
|
|
|
|
### Usage in Nushell
|
|
|
|
```nushell
|
|
# Access TTY output in Nushell script
|
|
export def "provisioning auth integrate" [--provider: string] {
|
|
let tty_output = ($env.TTY_OUTPUT? | default "")
|
|
|
|
# Parse if JSON
|
|
let creds = ($tty_output | from json)
|
|
|
|
# Use both TTY output and CLI args
|
|
integration-logic $provider $creds
|
|
|
|
# Clear after use (security)
|
|
hide-env TTY_OUTPUT
|
|
}
|
|
```
|
|
|
|
## Daemon Interaction
|
|
|
|
The flow filter intelligently manages daemon usage:
|
|
|
|
### For flow=exit and flow=pipe
|
|
- ✅ **Daemon can be used** - No stdin required
|
|
- No output needs to be captured and passed to Nushell
|
|
- Daemon optimization available (~100ms startup improvement)
|
|
|
|
### For flow=continue
|
|
- ❌ **Daemon MUST be bypassed** - stdin required for TTY interaction
|
|
- `PROVISIONING_BYPASS_DAEMON=true` automatically set by filter
|
|
- Direct Nushell execution (preserves stdin for TTY)
|
|
- Zero overhead (same as non-daemon path)
|
|
|
|
## Testing TTY Commands
|
|
|
|
### Test Standalone (flow=exit)
|
|
|
|
```bash
|
|
provisioning setup wizard
|
|
# Expected: TypeDialog form, user interaction, exits
|
|
```
|
|
|
|
### Test Pipeline (flow=pipe)
|
|
|
|
```bash
|
|
provisioning auth get-key | wc -c
|
|
# Expected: Prompts for API key, outputs to pipe
|
|
```
|
|
|
|
### Test Continue (flow=continue)
|
|
|
|
```bash
|
|
provisioning auth integrate --provider azure
|
|
# Expected: Prompts for credentials, passes to Nushell with $env.TTY_OUTPUT
|
|
```
|
|
|
|
### Test Regular Command
|
|
|
|
```bash
|
|
provisioning server list
|
|
# Expected: Normal Nushell processing
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Command Not Executed
|
|
- **Check**: Is command in tty-commands.conf?
|
|
- **Check**: Does pattern exactly match command?
|
|
- **Check**: Is wrapper path correct and executable?
|
|
|
|
### Wrapper Not Found
|
|
- **Error message**: `Warning: TTY wrapper not found or not executable: /path/to/wrapper`
|
|
- **Check**: File exists at `$PROVISIONING/wrapper-path`
|
|
- **Check**: File is executable: `chmod +x wrapper-path`
|
|
|
|
### Output Not Piping (flow=pipe)
|
|
- **Check**: Wrapper outputs to stdout (not stderr)
|
|
- **Check**: Output is newline-terminated: `echo "output"`
|
|
- **Check**: No daemon interference (PROVISIONING_BYPASS_DAEMON not set)
|
|
|
|
### Nushell Not Receiving Output (flow=continue)
|
|
- **Check**: `$env.TTY_OUTPUT` accessible in Nushell: `echo $env.TTY_OUTPUT`
|
|
- **Check**: Output format (usually JSON): `echo $env.TTY_OUTPUT | from json`
|
|
- **Check**: Wrapper exits with 0: `echo $?`
|
|
|
|
## Implementation Details
|
|
|
|
### Filter Location and Function
|
|
|
|
**File**: `provisioning/core/cli/tty-filter.sh`
|
|
**Function**: `filter_tty_command()`
|
|
**Lines**: ~104 (includes documentation and three flow paths)
|
|
|
|
### Integration in Wrapper
|
|
|
|
**File**: `provisioning/core/cli/provisioning`
|
|
**Lines**: ~20 (sources filter, calls function, continues to Nushell)
|
|
|
|
### Registry Parsing
|
|
|
|
- **File**: `provisioning/core/cli/tty-commands.conf`
|
|
- **Method**: Line-by-line bash read (no jq dependency)
|
|
- **Format**: Three-field bash array (bash-compatible)
|
|
- **Sections**: Organized by flow type for clarity
|
|
|
|
## Performance Implications
|
|
|
|
### startup time
|
|
- **flow=exit/pipe**: Daemon available for startup optimization (~100ms improvement)
|
|
- **flow=continue**: Daemon bypassed (stdin needed), ~500ms traditional path
|
|
- **Regular commands**: Normal daemon/non-daemon path selection
|
|
|
|
### Memory
|
|
- **flow=continue**: Wrapper output stored in `$TTY_OUTPUT` environment variable
|
|
- Typical size: < 1KB (credentials, keys, etc.)
|
|
- Cleared after Nushell processing (or via `hide-env`)
|
|
|
|
## Security Considerations
|
|
|
|
### Sensitive Data in $TTY_OUTPUT
|
|
|
|
- **Credentials** captured in `$TTY_OUTPUT`
|
|
- **Nushell scripts should clear after use**: `hide-env TTY_OUTPUT`
|
|
- **Wrapper output may be logged**: Use standard Unix conventions (hide passwords from output)
|
|
|
|
### Wrapper Location Restriction
|
|
|
|
- Wrappers should be in `provisioning/core/shlib/` or `provisioning/scripts/`
|
|
- Registry reads only wrappers from these trusted locations
|
|
- Pattern validation prevents arbitrary script execution
|
|
|
|
### No Shell Injection
|
|
|
|
- All variables quoted: `"$variable"`
|
|
- No eval or command substitution with user input
|
|
- Pattern matching uses exact string match (no regex)
|
|
|
|
## Related Files
|
|
|
|
- **Filter**: `provisioning/core/cli/tty-filter.sh`
|
|
- **Registry**: `provisioning/core/cli/tty-commands.conf`
|
|
- **Wrapper**: `provisioning/core/cli/provisioning`
|
|
- **Example wrappers**: `provisioning/core/shlib/auth-get-key-tty.sh`, `provisioning/core/shlib/auth-integrate-tty.sh`
|
|
|
|
## Key Insights
|
|
|
|
The provisioning wrapper is not just a pass-through - it's a **flow controller** that:
|
|
|
|
1. **Detects TTY requirements** (registry matching)
|
|
2. **Manages execution paths** (three flows: exit, pipe, continue)
|
|
3. **Controls exit behavior** (standalone vs pipeline vs same-command)
|
|
4. **Enables inter-command piping** (TTY output to pipes)
|
|
5. **Supports Nushell integration** (TTY→Nushell continuation)
|
|
6. **Optimizes with daemon** (skip when stdin needed)
|
|
|
|
This solves:
|
|
- "el tema no es sólo un filter" → ✅ Flow controller with three execution paths
|
|
- "cómo gestionar el flow por medio del provisioning command" → ✅ Registry + flow types
|
|
- "usamos tty para input de una API key, se lo pasamos a un script de nushell" → ✅ Pipeline + continue flows
|
|
|
|
---
|
|
|
|
**Version**: 1.0.0
|
|
**Last Updated**: January 2026
|
|
**Status**: ✅ Production Ready
|