# CTRL-C Handling Implementation Notes

## Overview

Implemented graceful CTRL-C handling for sudo password prompts during server creation/generation operations.

## Problem Statement

When `fix_local_hosts: true` is set, the provisioning tool requires sudo access to modify `/etc/hosts` and SSH config. When a user cancels the sudo password prompt (no password, wrong password, timeout), the system would:

1. Exit with code 1 (sudo failed)
2. Propagate null values up the call stack
3. Show cryptic Nushell errors about pipeline failures
4. Leave the operation in an inconsistent state

**Important Unix Limitation**: Pressing CTRL-C at the sudo password prompt sends SIGINT to the entire process group, interrupting Nushell before exit code handling can occur. This **cannot be caught** and is expected Unix behavior.

## Solution Architecture

### Key Principle: Return Values, Not Exit Codes

Instead of using `exit 130` which kills the entire process, we use **return values** to signal cancellation and let each layer of the call stack handle it gracefully.

### Three-Layer Approach

1. **Detection Layer** (ssh.nu helper functions)
   - Detects sudo cancellation via exit code + stderr
   - Returns `false` instead of calling `exit`

2. **Propagation Layer** (ssh.nu core functions)
   - `on_server_ssh()`: Returns `false` on cancellation
   - `server_ssh()`: Uses `reduce` to propagate failures

3. **Handling Layer** (create.nu, generate.nu)
   - Checks return values
   - Displays user-friendly messages
   - Returns `false` to caller

## Implementation Details

### 1. Helper Functions (ssh.nu:11-32)

```nushell
def check_sudo_cached []: nothing -> bool {
  let result = (do --ignore-errors { ^sudo -n true } | complete)
  $result.exit_code == 0
}

def run_sudo_with_interrupt_check [
  command: closure
  operation_name: string
]: nothing -> bool {
  let result = (do --ignore-errors { do $command } | complete)
  if $result.exit_code == 1 and ($result.stderr | str contains "password is required") {
    print "\n⚠ Operation cancelled - sudo password required but not provided"
    print "ℹ Run 'sudo -v' first to cache credentials, or run without --fix-local-hosts"
    return false  # Signal cancellation
  } else if $result.exit_code != 0 and $result.exit_code != 1 {
    error make {msg: $"($operation_name) failed: ($result.stderr)"}
  }
  true
}
```plaintext

**Design Decision**: Return `bool` instead of throwing error or calling `exit`. This allows the caller to decide how to handle cancellation.

### 2. Pre-emptive Warning (ssh.nu:155-160)

```nushell
if $server.fix_local_hosts and not (check_sudo_cached) {
  print "\n⚠ Sudo access required for --fix-local-hosts"
  print "ℹ You will be prompted for your password, or press CTRL-C to cancel"
  print "  Tip: Run 'sudo -v' beforehand to cache credentials\n"
}
```plaintext

**Design Decision**: Warn users upfront so they're not surprised by the password prompt.

### 3. CTRL-C Detection (ssh.nu:171-199)

All sudo commands wrapped with detection:

```nushell
let result = (do --ignore-errors { ^sudo <command> } | complete)
if $result.exit_code == 1 and ($result.stderr | str contains "password is required") {
  print "\n⚠ Operation cancelled"
  return false
}
```plaintext

**Design Decision**: Use `do --ignore-errors` + `complete` to capture both exit code and stderr without throwing exceptions.

### 4. State Accumulation Pattern (ssh.nu:122-129)

Using Nushell's `reduce` instead of mutable variables:

```nushell
let all_succeeded = ($settings.data.servers | reduce -f true { |server, acc|
  if $text_match == null or $server.hostname == $text_match {
    let result = (on_server_ssh $settings $server $ip_type $request_from $run)
    $acc and $result
  } else {
    $acc
  }
})
```plaintext

**Design Decision**: Nushell doesn't allow mutable variable capture in closures. Use `reduce` for accumulating boolean state across iterations.

### 5. Caller Handling (create.nu:262-266, generate.nu:269-273)

```nushell
let ssh_result = (on_server_ssh $settings $server "pub" "create" false)
if not $ssh_result {
  _print "\n✗ Server creation cancelled"
  return false
}
```plaintext

**Design Decision**: Check return value and provide context-specific message before returning.

## Error Flow Diagram

```plaintext
User presses CTRL-C during password prompt
    ↓
sudo exits with code 1, stderr: "password is required"
    ↓
do --ignore-errors captures exit code & stderr
    ↓
Detection logic identifies cancellation
    ↓
Print user-friendly message
    ↓
Return false (not exit!)
    ↓
on_server_ssh returns false
    ↓
Caller (create.nu/generate.nu) checks return value
    ↓
Print "✗ Server creation cancelled"
    ↓
Return false to settings.nu
    ↓
settings.nu handles false gracefully (no append)
    ↓
Clean exit, no cryptic errors
```plaintext

## Nushell Idioms Used

### 1. `do --ignore-errors` + `complete`

Captures both stdout, stderr, and exit code without throwing:

```nushell
let result = (do --ignore-errors { ^sudo command } | complete)
# result = { stdout: "...", stderr: "...", exit_code: 1 }
```plaintext

### 2. `reduce` for Accumulation

Instead of mutable variables in loops:

```nushell
# ❌ BAD - mutable capture in closure
mut all_succeeded = true
$servers | each { |s|
  $all_succeeded = false  # Error: capture of mutable variable
}

# ✅ GOOD - reduce with accumulator
let all_succeeded = ($servers | reduce -f true { |s, acc|
  $acc and (check_server $s)
})
```plaintext

### 3. Early Returns for Error Handling

```nushell
if not $condition {
  print "Error message"
  return false
}
# Continue with happy path
```plaintext

## Testing Scenarios

### Scenario 1: CTRL-C During First Sudo Command

```bash
provisioning -c server create
# Password: [CTRL-C]

# Expected Output:
# ⚠ Operation cancelled - sudo password required but not provided
# ℹ Run 'sudo -v' first to cache credentials
# ✗ Server creation cancelled
```plaintext

### Scenario 2: Pre-cached Credentials

```bash
sudo -v
provisioning -c server create

# Expected: No password prompt, smooth operation
```plaintext

### Scenario 3: Wrong Password 3 Times

```bash
provisioning -c server create
# Password: [wrong]
# Password: [wrong]
# Password: [wrong]

# Expected: Same as CTRL-C (treated as cancellation)
```plaintext

### Scenario 4: Multiple Servers, Cancel on Second

```bash
# If creating multiple servers and CTRL-C on second:
# - First server completes successfully
# - Second server shows cancellation message
# - Operation stops, doesn't proceed to third
```plaintext

## Maintenance Notes

### Adding New Sudo Commands

When adding new sudo commands to the codebase:

1. Wrap with `do --ignore-errors` + `complete`
2. Check for exit code 1 + "password is required"
3. Return `false` on cancellation
4. Let caller handle the `false` return value

Example template:

```nushell
let result = (do --ignore-errors { ^sudo new-command } | complete)
if $result.exit_code == 1 and ($result.stderr | str contains "password is required") {
  print "\n⚠ Operation cancelled - sudo password required"
  return false
}
```plaintext

### Common Pitfalls

1. **Don't use `exit`**: It kills the entire process
2. **Don't use mutable variables in closures**: Use `reduce` instead
3. **Don't ignore return values**: Always check and propagate
4. **Don't forget the pre-check warning**: Users should know sudo is needed

## Future Improvements

1. **Sudo Credential Manager**: Optionally use a credential manager (keychain, etc.)
2. **Sudo-less Mode**: Alternative implementation that doesn't require root
3. **Timeout Handling**: Detect when sudo times out waiting for password
4. **Multiple Password Attempts**: Distinguish between CTRL-C and wrong password

## References

- Nushell `complete` command: <https://www.nushell.sh/commands/docs/complete.html>
- Nushell `reduce` command: <https://www.nushell.sh/commands/docs/reduce.html>
- Sudo exit codes: man sudo (exit code 1 = authentication failure)
- POSIX signal conventions: SIGINT (CTRL-C) = 130

## Related Files

- `provisioning/core/nulib/servers/ssh.nu` - Core implementation
- `provisioning/core/nulib/servers/create.nu` - Calls on_server_ssh
- `provisioning/core/nulib/servers/generate.nu` - Calls on_server_ssh
- `docs/troubleshooting/CTRL-C_SUDO_HANDLING.md` - User-facing docs
- `docs/quick-reference/SUDO_PASSWORD_HANDLING.md` - Quick reference

## Changelog

- **2025-01-XX**: Initial implementation with return values (v2)
- **2025-01-XX**: Fixed mutable variable capture with `reduce` pattern
- **2025-01-XX**: First attempt with `exit 130` (reverted, caused process termination)
