1171 lines
48 KiB
Bash
Executable file
1171 lines
48 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# Info: Script to run Provisioning
|
||
# Author: Jesus Perez Lorenzo
|
||
# Release: 3.0.11
|
||
# Date: 2026-01-14
|
||
|
||
set +o errexit
|
||
set +o pipefail
|
||
|
||
# Debug: log startup
|
||
[ "${PROVISIONING_DEBUG_STARTUP:-false}" = "true" ] && echo "[DEBUG] Wrapper started with args: $@" >&2
|
||
|
||
export NU=$(type -P nu)
|
||
|
||
_release() {
|
||
grep "^# Release:" "$0" | sed "s/# Release: //g"
|
||
}
|
||
|
||
export PROVISIONING_VERS=$(_release)
|
||
|
||
set -o allexport
|
||
## shellcheck disable=SC1090
|
||
[ -n "$PROVISIONING_ENV" ] && [ -r "$PROVISIONING_ENV" ] && source "$PROVISIONING_ENV"
|
||
[ -r "../env-provisioning" ] && source ../env-provisioning
|
||
[ -r "env-provisioning" ] && source ./env-provisioning
|
||
#[ -r ".env" ] && source .env set
|
||
|
||
# Show provisioning logo/banner by default (can be overridden by env var)
|
||
export PROVISIONING_NO_TITLES=${PROVISIONING_NO_TITLES:-true}
|
||
|
||
set +o allexport
|
||
|
||
export PROVISIONING=${PROVISIONING:-/usr/local/provisioning}
|
||
|
||
# For development: search upward from script location to find provisioning directory
|
||
if [ ! -d "$PROVISIONING/resources" ]; then
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
current="$SCRIPT_DIR"
|
||
# Search up to 5 levels up from script directory
|
||
for _ in {1..5}; do
|
||
if [ -d "$current/provisioning/resources" ]; then
|
||
export PROVISIONING="$current/provisioning"
|
||
break
|
||
fi
|
||
parent="$(dirname "$current")"
|
||
[ "$parent" = "$current" ] && break # Stop at filesystem root
|
||
current="$parent"
|
||
done
|
||
fi
|
||
|
||
export PROVISIONING_RESOURCES=${PROVISIONING_RESOURCES:-"$PROVISIONING/resources"}
|
||
PROVIISONING_WKPATH=${PROVIISONING_WKPATH:-/tmp/tmp.}
|
||
|
||
RUNNER="provisioning-cli.nu"
|
||
PROVISIONING_MODULE=""
|
||
PROVISIONING_MODULE_TASK=""
|
||
|
||
# Main help function (defined early for early help detection)
|
||
_show_help() {
|
||
local category="${1:-}"
|
||
|
||
# If help cache available and fresh, use it for speed
|
||
if [ -n "$HELP_CACHE_DIR" ] && [ -f "$HELP_CACHE_DIR/main.txt" ]; then
|
||
local cache_age=$(($(date +%s) - $(stat -f %m "$HELP_CACHE_DIR/main.txt" 2>/dev/null || echo 0)))
|
||
if [ "$cache_age" -lt "$HELP_CACHE_TTL" ]; then
|
||
cat "$HELP_CACHE_DIR/main.txt"
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
# Fallback: Call Nushell for help via single CLI entry
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" help $category
|
||
}
|
||
|
||
# Workflow help function (defined early for early help detection)
|
||
_workflow_help() {
|
||
echo "Workflow Management Commands"
|
||
echo ""
|
||
echo "Available commands:"
|
||
echo " l | list - List workflows"
|
||
echo " s | status - Show workflow status"
|
||
echo " m | monitor - Monitor workflow progress"
|
||
echo " st | stats - Show workflow statistics"
|
||
echo " c | cleanup - Clean up old workflows"
|
||
echo " b | browse - Browse workflows"
|
||
echo " o | orchestrator - Show orchestrator health"
|
||
echo ""
|
||
echo "Usage:"
|
||
echo " provisioning workflow [command] [arguments]"
|
||
echo " provisioning workflow <number> - List with limit"
|
||
echo ""
|
||
echo "Examples:"
|
||
echo " provisioning wf l - List workflows"
|
||
echo " provisioning wf 5 - List last 5 workflows"
|
||
echo " provisioning wf st - Show statistics"
|
||
echo " provisioning wf s <id> - Show status of specific task"
|
||
}
|
||
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
# Daemon Routing Helpers - Route operations to provisioning-daemon (port 9095)
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
|
||
# Get daemon port from user configuration (or default to 9095)
|
||
# Reads from: ~/.config/provisioning/daemon.conf or PROVISIONING_DAEMON_PORT env var
|
||
_get_daemon_port() {
|
||
local port
|
||
# Priority 1: Environment variable
|
||
if [ -n "${PROVISIONING_DAEMON_PORT:-}" ]; then
|
||
echo "$PROVISIONING_DAEMON_PORT"
|
||
return
|
||
fi
|
||
|
||
# Priority 2: User config file
|
||
local config_file="${HOME}/.config/provisioning/daemon.conf"
|
||
if [ -f "$config_file" ]; then
|
||
port=$(grep "^DAEMON_PORT=" "$config_file" | cut -d'=' -f2 | tr -d '[:space:]')
|
||
if [ -n "$port" ]; then
|
||
echo "$port"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
# Default port
|
||
echo "9095"
|
||
}
|
||
|
||
DAEMON_PORT=$(_get_daemon_port)
|
||
DAEMON_ENDPOINT="http://127.0.0.1:${DAEMON_PORT}"
|
||
DAEMON_EXECUTE_ENDPOINT="${DAEMON_ENDPOINT}/api/v1/execute"
|
||
DAEMON_TIMEOUT_FAST="0.5" # Help/quick operations: 500ms
|
||
DAEMON_TIMEOUT_NORMAL="1.0" # Template rendering: 1s
|
||
DAEMON_TIMEOUT_BATCH="5.0" # Batch operations: 5s
|
||
|
||
# Cache directory for help and other CLI outputs
|
||
HELP_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/provisioning/help"
|
||
HELP_CACHE_TTL=86400 # 24 hours in seconds
|
||
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
# Help Cache Functions - Instant help output (after first run)
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
|
||
# Get cache file path for a help category
|
||
_get_cache_path() {
|
||
echo "${HELP_CACHE_DIR}/$1.txt"
|
||
}
|
||
|
||
# Check if cache is valid (not expired)
|
||
_is_cache_valid() {
|
||
local cache_file="$1"
|
||
local now
|
||
local mtime
|
||
local age
|
||
[ ! -f "$cache_file" ] && return 1
|
||
|
||
now=$(date +%s)
|
||
mtime=$(stat -f%m "$cache_file" 2>/dev/null || stat -c%Y "$cache_file" 2>/dev/null || echo 0)
|
||
age=$((now - mtime))
|
||
|
||
[ $age -lt $HELP_CACHE_TTL ] && return 0
|
||
return 1
|
||
}
|
||
|
||
# Store help output in cache (handle special characters safely)
|
||
_cache_help() {
|
||
local category="$1"
|
||
local content="$2"
|
||
|
||
mkdir -p "$HELP_CACHE_DIR"
|
||
# Use printf to safely handle newlines and special characters
|
||
printf '%s\n' "$content" >"$(_get_cache_path "$category")"
|
||
}
|
||
|
||
# Get help from cache (if valid) or fetch fresh
|
||
_get_help_cached() {
|
||
local category="$1"
|
||
local cache_file
|
||
cache_file="$(_get_cache_path "$category")"
|
||
|
||
# Try cache first (instant!)
|
||
if _is_cache_valid "$cache_file"; then
|
||
cat "$cache_file"
|
||
return 0
|
||
fi
|
||
|
||
# Cache miss or expired - fetch fresh from daemon or Nushell
|
||
return 1
|
||
}
|
||
|
||
# Try daemon first with timeout, fall back to direct execution
|
||
# Usage: _route_daemon_or_fallback "command_name" "timeout" "fallback_cmd"
|
||
_route_daemon_or_fallback() {
|
||
local cmd_name="$1"
|
||
local timeout="$2"
|
||
local fallback_cmd="$3"
|
||
shift 3
|
||
local cmd_args=("$@")
|
||
local response
|
||
local json_args
|
||
|
||
if command -v timeout &>/dev/null && command -v curl &>/dev/null; then
|
||
# Build JSON payload for daemon
|
||
json_args=$(printf '%s\n' "${cmd_args[@]}" | jq -R . | jq -s .)
|
||
payload="{\"command\": \"$cmd_name\", \"args\": $json_args}"
|
||
|
||
# Try daemon with timeout
|
||
response=$(timeout "$timeout" curl -s -m "$timeout" -X POST "$DAEMON_ENDPOINT" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$payload" 2>/dev/null)
|
||
|
||
if [ -n "$response" ] && [ "$response" != "null" ] && [ "$response" != "{}" ]; then
|
||
echo "$response"
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
# Fallback: execute directly
|
||
eval "$fallback_cmd"
|
||
}
|
||
|
||
# Daemon render wrapper for tera templates
|
||
# Usage: _daemon_render "template_path" "context_json_file"
|
||
_daemon_render() {
|
||
local template_path="$1"
|
||
local context_file="$2"
|
||
local context
|
||
local payload
|
||
|
||
context=$(cat "$context_file" 2>/dev/null)
|
||
payload="{\"command\": \"tera-render\", \"template\": \"$(cat "$template_path")\", \"context\": $context}"
|
||
|
||
if command -v timeout &>/dev/null && command -v curl &>/dev/null; then
|
||
timeout "$DAEMON_TIMEOUT_NORMAL" curl -s -m "$DAEMON_TIMEOUT_NORMAL" -X POST "$DAEMON_ENDPOINT" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$payload" 2>/dev/null
|
||
return $?
|
||
fi
|
||
|
||
return 1
|
||
}
|
||
|
||
# Safe argument handling - use default empty value if unbound
|
||
[ "${1:-}" == "" ] && shift
|
||
|
||
[ -z "$NU" ] || [ "${1:-}" == "install" ] || [ "${1:-}" == "reinstall" ] || [ "${1:-}" == "mode" ] && exec bash $PROVISIONING/core/bin/install_nu.sh $PROVISIONING ${1:-} ${2:-}
|
||
|
||
[ "${1:-}" == "rmwk" ] && rm -rf "$PROVIISONING_WKPATH"* && echo "$PROVIISONING_WKPATH deleted" && exit
|
||
[ "${1:-}" == "-x" ] && debug=-x && export PROVISIONING_DEBUG=true && shift
|
||
[ "${1:-}" == "-xm" ] && export PROVISIONING_METADATA=true && shift
|
||
[ "${1:-}" == "nu" ] && export PROVISIONING_DEBUG=true
|
||
[ "${1:-}" == "--x" ] && set -x && debug=-x && export PROVISIONING_DEBUG=true && shift
|
||
[ "${1:-}" == "-i" ] || [ "${2:-}" == "-i" ] && echo "$(basename "$0") $(grep "^# Info:" "$0" | sed "s/# Info: //g") " && exit
|
||
[ "${1:-}" == "-v" ] || [ "${1:-}" == "--version" ] || [ "${2:-}" == "-v" ] && _release && exit
|
||
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
# EARLY DETECTION - Avoid expensive parsing for no-args and workflow help
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
|
||
# No arguments at all - show quick usage (don't load Nushell)
|
||
if [ -z "$1" ]; then
|
||
echo "Usage: provisioning [command] [options]"
|
||
echo ""
|
||
echo "Use 'provisioning help' for available commands"
|
||
exit 0
|
||
fi
|
||
|
||
# Job help detection (before expensive parsing) — "job" is the orchestrator job command
|
||
case "$1" in
|
||
job|j)
|
||
case "$2" in
|
||
help|-h|--help|-help)
|
||
_workflow_help
|
||
exit 0
|
||
;;
|
||
esac
|
||
;;
|
||
esac
|
||
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
# FLOW-AWARE TTY COMMAND FILTER
|
||
# Manages three execution flows: exit (standalone), pipe (inter-command), continue (Nushell)
|
||
# Registry: provisioning/core/cli/tty-commands.conf
|
||
# Filter: provisioning/core/cli/tty-filter.sh
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
if [ -f "$PROVISIONING/core/cli/tty-filter.sh" ]; then
|
||
# Source filter function
|
||
# shellcheck source=/dev/null
|
||
source "$PROVISIONING/core/cli/tty-filter.sh"
|
||
|
||
# Try to filter TTY command (full command line as single string)
|
||
# Return codes:
|
||
# - filter_tty_command returns 0: flow=continue case handled, continue to Nushell with $TTY_OUTPUT
|
||
# - filter_tty_command exits: flow=exit/pipe case completed (already exited)
|
||
# - filter returns 1: not a TTY command, continue to normal processing
|
||
if filter_tty_command "$@"; then
|
||
# Flow=continue: TTY wrapper executed, output in $TTY_OUTPUT, bypass daemon
|
||
# $env.PROVISIONING_BYPASS_DAEMON and $env.TTY_OUTPUT available to Nushell
|
||
: # Continue to Nushell dispatcher below
|
||
fi
|
||
fi
|
||
|
||
CMD_ARGS="$*"
|
||
|
||
# Note: Flag ordering is handled by Nushell's reorder_args function
|
||
# which automatically reorders flags before positional arguments.
|
||
# Flags can be placed anywhere on the command line.
|
||
case "${1:-}" in
|
||
# Note: "setup" is now handled by the main provisioning CLI dispatcher
|
||
# No special module handling needed
|
||
-mod)
|
||
PROVISIONING_MODULE=$(echo "$2" | sed 's/ //g' | cut -f1 -d"|")
|
||
PROVISIONING_MODULE_TASK=$(echo "$2" | sed 's/ //g' | cut -f2 -d"|")
|
||
[ "$PROVISIONING_MODULE" == "$PROVISIONING_MODULE_TASK" ] && PROVISIONING_MODULE_TASK=""
|
||
shift 2
|
||
CMD_ARGS="$*"
|
||
[ "${PROVISIONING_DEBUG_STARTUP:-false}" = "true" ] && echo "[DEBUG] -mod detected: MODULE=$PROVISIONING_MODULE, TASK=$PROVISIONING_MODULE_TASK, CMD_ARGS=$CMD_ARGS" >&2
|
||
;;
|
||
esac
|
||
NU_ARGS=""
|
||
|
||
DEFAULT_CONTEXT_TEMPLATE="default_context.yaml"
|
||
case "$(uname | tr '[:upper:]' '[:lower:]')" in
|
||
linux)
|
||
PROVISIONING_USER_CONFIG="$HOME/.config/provisioning/nushell"
|
||
PROVISIONING_CONTEXT_PATH="$HOME/.config/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
|
||
PROVISIONING_USER_PLATFORM="$HOME/.config/provisioning/platform"
|
||
;;
|
||
darwin)
|
||
PROVISIONING_USER_CONFIG="$HOME/Library/Application Support/provisioning/nushell"
|
||
PROVISIONING_CONTEXT_PATH="$HOME/Library/Application Support/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
|
||
PROVISIONING_USER_PLATFORM="$HOME/Library/Application Support/provisioning/platform"
|
||
;;
|
||
*)
|
||
PROVISIONING_USER_CONFIG="$HOME/.config/provisioning/nushell"
|
||
PROVISIONING_CONTEXT_PATH="$HOME/.config/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
|
||
PROVISIONING_USER_PLATFORM="$HOME/.config/provisioning/platform"
|
||
;;
|
||
esac
|
||
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
# Workflow help function (DRY) - defined early for use in global help handler
|
||
_workflow_help() {
|
||
echo "Workflow Management Commands"
|
||
echo ""
|
||
echo "Available commands:"
|
||
echo " l | list - List workflows"
|
||
echo " s | status - Show workflow status"
|
||
echo " m | monitor - Monitor workflow progress"
|
||
echo " st | stats - Show workflow statistics"
|
||
echo " c | cleanup - Clean up old workflows"
|
||
echo " b | browse - Browse workflows"
|
||
echo " o | orchestrator - Show orchestrator health"
|
||
echo ""
|
||
echo "Usage:"
|
||
echo " provisioning workflow [command] [arguments]"
|
||
echo " provisioning workflow <number> - List with limit"
|
||
echo ""
|
||
echo "Examples:"
|
||
echo " provisioning wf l - List workflows"
|
||
echo " provisioning wf 5 - List last 5 workflows"
|
||
echo " provisioning wf st - Show statistics"
|
||
echo " provisioning wf s <id> - Show status of specific task"
|
||
}
|
||
|
||
# DAEMON ROUTING - Try daemon for all commands (except setup/help/interactive)
|
||
# Falls back to traditional handlers if daemon unavailable
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
|
||
# NOTE: DAEMON_ENDPOINT is already defined above as http://127.0.0.1:9095
|
||
# Do NOT redefine it here
|
||
|
||
# Function to execute command via daemon
|
||
execute_via_daemon() {
|
||
local cmd="$1"
|
||
shift
|
||
local cwd_json
|
||
local response
|
||
|
||
# Build JSON array of arguments (simple bash)
|
||
local args_json="["
|
||
local first=1
|
||
for arg in "$@"; do
|
||
[ $first -eq 0 ] && args_json="$args_json,"
|
||
args_json="$args_json\"$(echo "$arg" | sed 's/"/\\"/g')\""
|
||
first=0
|
||
done
|
||
args_json="$args_json]"
|
||
cwd_json=$(printf '%s' "$PWD" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||
|
||
# Determine timeout based on command type
|
||
# Heavy commands (create, delete, update) get longer timeout
|
||
local timeout=0.5
|
||
case "$cmd" in
|
||
create | delete | update | setup | init) timeout=5 ;;
|
||
*) timeout=0.2 ;;
|
||
esac
|
||
|
||
# Make request and extract stdout
|
||
response=$(curl -s -m $timeout -X POST "$DAEMON_EXECUTE_ENDPOINT" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"command\":\"$cmd\",\"args\":$args_json,\"cwd\":\"$cwd_json\",\"timeout_ms\":30000}" 2>/dev/null)
|
||
|
||
if [ -z "$response" ] || [ "$response" = "null" ] || [ "$response" = "{}" ]; then
|
||
return 1
|
||
fi
|
||
|
||
if command -v jq >/dev/null 2>&1; then
|
||
printf '%s' "$response" | jq -r '.stdout // empty'
|
||
else
|
||
printf '%s' "$response" |
|
||
sed -n 's/.*"stdout":"\(.*\)","execution.*/\1/p' |
|
||
sed 's/\\n/\n/g'
|
||
fi
|
||
}
|
||
|
||
# Intercept: server volume → volume (avoids loading full server module)
|
||
if [ "${1:-}" = "server" ] || [ "${1:-}" = "s" ]; then
|
||
if [ "${2:-}" = "volume" ] || [ "${2:-}" = "vol" ]; then
|
||
shift 2
|
||
exec "$0" volume "$@"
|
||
fi
|
||
fi
|
||
|
||
# Try daemon ONLY for lightweight commands (list, show, status)
|
||
# Skip daemon for heavy commands (create, delete, update) because bash wrapper is slow
|
||
# ALSO skip daemon for flow=continue commands (need stdin for TTY interaction)
|
||
if [ "${PROVISIONING_BYPASS_DAEMON:-}" != "true" ] && [ "${PROVISIONING_NO_DAEMON:-}" != "true" ] && ([ "${1:-}" = "server" ] || [ "${1:-}" = "s" ]); then
|
||
if [ "${2:-}" = "list" ] || [ "${2:-}" = "ls" ] || [ "${2:-}" = "l" ] || [ -z "${2:-}" ]; then
|
||
# Light command - try daemon
|
||
[ -n "${PROVISIONING_DEBUG:-}" ] && [ "${PROVISIONING_DEBUG:-}" = "true" ] && echo "⚡ Attempting daemon execution..." >&2
|
||
DAEMON_OUTPUT=$(execute_via_daemon "$@" 2>/dev/null)
|
||
if [ -n "$DAEMON_OUTPUT" ]; then
|
||
echo "$DAEMON_OUTPUT"
|
||
exit 0
|
||
fi
|
||
[ -n "${PROVISIONING_DEBUG:-}" ] && [ "${PROVISIONING_DEBUG:-}" = "true" ] && echo "⚠️ Daemon unavailable, using traditional handlers..." >&2
|
||
fi
|
||
# NOTE: Command reordering (server create -> create server) has been removed.
|
||
# The Nushell dispatcher in provisioning/core/nulib/main_provisioning/dispatcher.nu
|
||
# handles command routing correctly and expects "server create" format.
|
||
# The reorder_args function in provisioning script handles any flag reordering needed.
|
||
fi
|
||
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
# FAST-PATH: Commands that don't need full config loading or platform bootstrap
|
||
# These commands use lib_minimal.nu for <100ms execution
|
||
# (ONLY REACHED if daemon is not available)
|
||
# ═══<E29590><E29590><EFBFBD>════════════════════════════════════════════════════════════════════════════
|
||
|
||
# Help commands fast-path (uses help_minimal.nu)
|
||
# Detects "help" in ANY argument position, not just first
|
||
|
||
# Normalize help category aliases to canonical names
|
||
_normalize_help_category() {
|
||
local category="$1"
|
||
case "$category" in
|
||
# Infrastructure aliases
|
||
s | server | infra | i) echo "infrastructure" ;;
|
||
|
||
# Orchestration aliases
|
||
wf | flow | workflow | orch | orchestrator | bat | batch) echo "orchestration" ;;
|
||
|
||
# Development aliases
|
||
mod | module | lyr | layer | pack | dev) echo "development" ;;
|
||
|
||
# Workspace aliases
|
||
ws | workspace | tpl | tmpl | template) echo "workspace" ;;
|
||
|
||
# Platform aliases
|
||
p | plat | platform) echo "platform" ;;
|
||
|
||
# Setup aliases
|
||
st | setup | config) echo "setup" ;;
|
||
|
||
# Authentication aliases
|
||
auth | authentication) echo "authentication" ;;
|
||
|
||
# Plugin aliases
|
||
plugin | plugins) echo "plugins" ;;
|
||
|
||
# Utilities aliases
|
||
utils | utilities | cache) echo "utilities" ;;
|
||
|
||
# Diagnostics aliases
|
||
diag | diagnostics | status | health) echo "diagnostics" ;;
|
||
|
||
# Other categories
|
||
orchestration | development | workspace | authentication | mfa | plugins | utilities | tools | vm | diagnostics | concepts | guides | integrations | build | infrastructure | setup)
|
||
echo "$category"
|
||
;;
|
||
|
||
# Unknown - return as-is
|
||
*) echo "$category" ;;
|
||
esac
|
||
}
|
||
|
||
help_category=""
|
||
help_found=false
|
||
help_subcmd="" # subcommand after the main command (e.g. "delete" in "server delete --help")
|
||
_pos_count=0 # count of positional (non-flag, non-help) args
|
||
|
||
# Check if first arg is empty (no args provided) - treat as help request
|
||
if [ -z "${1:-}" ]; then
|
||
help_found=true
|
||
else
|
||
# Loop through all arguments to find help variant and extract category
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
help | h | -h | --help | --helpinfo)
|
||
help_found=true
|
||
;;
|
||
-*)
|
||
# Skip flags (like -x, -xm, -i, -v, etc.)
|
||
;;
|
||
*)
|
||
_pos_count=$((_pos_count + 1))
|
||
if [ "$help_category" = "" ]; then
|
||
help_category="$(_normalize_help_category "$arg")"
|
||
elif [ "$help_subcmd" = "" ]; then
|
||
help_subcmd="$arg" # second positional = subcommand
|
||
fi
|
||
;;
|
||
esac
|
||
done
|
||
fi
|
||
|
||
# If help was requested for a SUBCOMMAND (e.g. "server delete --help"),
|
||
# clear help_found so the fast-path is skipped and the Nu module handles --help.
|
||
if [ "$help_found" = true ] && [ -n "$help_subcmd" ]; then
|
||
help_found=false
|
||
fi
|
||
|
||
# Execute help fast-path if help was requested
|
||
if [ "$help_found" = true ]; then
|
||
# List of known help categories - if not in this list, let command handle --help
|
||
case "$help_category" in
|
||
infrastructure | orchestration | development | workspace | setup | platform | authentication | mfa | plugins | utilities | tools | vm | diagnostics | concepts | guides | integrations | build)
|
||
# TIER 1: Try local cache first (instant! <1ms)
|
||
if _get_help_cached "$help_category"; then
|
||
exit 0
|
||
fi
|
||
|
||
# TIER 2: Try daemon next - DISABLED (daemon not critical for help)
|
||
# The daemon is optional - help can be generated directly via Nushell
|
||
|
||
# TIER 3: Fall back to Nushell (slower ~2-3s)
|
||
export LANG
|
||
|
||
# Execute Nushell help and capture output
|
||
HELP_OUTPUT=$($NU -n -c "source '$PROVISIONING/core/nulib/help_minimal.nu'; provisioning-help '$help_category' | print")
|
||
|
||
# Cache the output for next time (if not empty)
|
||
if [ -n "$HELP_OUTPUT" ]; then
|
||
_cache_help "$help_category" "$HELP_OUTPUT"
|
||
echo "$HELP_OUTPUT"
|
||
exit 0
|
||
else
|
||
# If output is empty, exit gracefully
|
||
exit 1
|
||
fi
|
||
;;
|
||
"")
|
||
# No category specified - show main help with all categories
|
||
# TIER 1: Try local cache for main help
|
||
if _get_help_cached "main"; then
|
||
exit 0
|
||
fi
|
||
|
||
# TIER 2: Try daemon next
|
||
if command -v timeout &>/dev/null && command -v curl &>/dev/null; then
|
||
DAEMON_OUTPUT=$(timeout 0.5 curl -s -m 0.5 -X POST "$DAEMON_ENDPOINT" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"command\": \"help\", \"args\": []}" 2>/dev/null)
|
||
if [ -n "$DAEMON_OUTPUT" ] && [ "$DAEMON_OUTPUT" != "null" ] && [ "$DAEMON_OUTPUT" != "{}" ]; then
|
||
# Store in cache for next time
|
||
_cache_help "main" "$DAEMON_OUTPUT"
|
||
echo "$DAEMON_OUTPUT"
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
# TIER 3: Fall back to Nushell
|
||
export LANG
|
||
HELP_OUTPUT=$($NU -n -c "source '$PROVISIONING/core/nulib/help_minimal.nu'; provisioning-help | print")
|
||
|
||
if [ -n "$HELP_OUTPUT" ]; then
|
||
_cache_help "main" "$HELP_OUTPUT"
|
||
echo "$HELP_OUTPUT"
|
||
exit 0
|
||
else
|
||
exit 1
|
||
fi
|
||
;;
|
||
*)
|
||
# Unknown category/command - let the main dispatcher handle it
|
||
# Don't process help here, just continue to normal flow
|
||
# The dispatcher will pass --help to the command for handling
|
||
unset help_found
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
# Commands requiring arguments - Fast-path: serve cached help when run without args
|
||
# ════════════════════════════════════════════════════════════════════════════════
|
||
|
||
# Map command to help category (for commands that require arguments)
|
||
# Get help category from Nickel schema registry
|
||
_get_help_category_for_command() {
|
||
local cmd="$1"
|
||
local schema_file="$PROVISIONING/core/nulib/commands-registry.ncl"
|
||
|
||
if [ ! -f "$schema_file" ]; then
|
||
return 1
|
||
fi
|
||
|
||
# Use external Nushell script for better maintainability
|
||
$NU "$PROVISIONING/core/nulib/scripts/get-help-category.nu" "$schema_file" "$cmd" 2>/dev/null
|
||
}
|
||
|
||
# Execute Nushell command with minimal lib (fast-path commands)
|
||
_nu_minimal() {
|
||
local nu_command="$1"
|
||
$NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; $nu_command" 2>/dev/null
|
||
}
|
||
|
||
# Execute Nushell command with full user config (workflow commands)
|
||
_nu_with_config() {
|
||
local nu_command="$1"
|
||
$NU --config "$PROVISIONING_USER_CONFIG/config.nu" --env-config "$PROVISIONING_USER_CONFIG/env.nu" -c "$nu_command"
|
||
}
|
||
|
||
# Check if first arg is a command that requires arguments and has no second arg
|
||
if [ -n "${1:-}" ] && [ -z "${2:-}" ]; then
|
||
help_cat=$(_get_help_category_for_command "${1}")
|
||
if [ -n "$help_cat" ]; then
|
||
# Command requires arguments but none provided - serve cached help
|
||
if _get_help_cached "$help_cat"; then
|
||
exit 0
|
||
fi
|
||
# Fallback to normal help system if cache miss
|
||
PROVISIONING_HELP_CATEGORY="$help_cat"
|
||
export PROVISIONING_HELP_CATEGORY
|
||
fi
|
||
fi
|
||
|
||
# workspace fast-path removed (ADR-025 Phase 4 — single-route principle).
|
||
# All workspace subcommands now route to main_provisioning/workspace.nu via
|
||
# the main dispatch case. --help still intercepts before full load.
|
||
if [ "${1:-}" = "workspace" ] || [ "${1:-}" = "ws" ]; then
|
||
case "${2:-}" in
|
||
"-help" | "h" | "help")
|
||
exec "$0" "${1}" --help
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
# Status/Health check (fast-path) - DISABLED to fix dispatcher loop
|
||
# Use normal dispatcher path instead of fast-path with lib_minimal.nu
|
||
# if [ "$1" = "status" ] || [ "$1" = "health" ]; then
|
||
# $NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; status-quick | table" 2>/dev/null
|
||
# exit $?
|
||
# fi
|
||
|
||
# env fast-path removed (ADR-025 Phase 4 — single-route principle).
|
||
# env/allenv now route to the full dispatcher via the *) default case.
|
||
|
||
# Alias list fast-path — reads JSON cache directly in bash, no Nu process
|
||
if [ "${1:-}" = "alias" ] || [ "${1:-}" = "a" ] || [ "${1:-}" = "al" ]; then
|
||
_ALIAS_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/provisioning/commands-registry.json"
|
||
echo ""
|
||
echo "ALIASES"
|
||
echo "════════════════════════════════════════════════════"
|
||
if [ -f "$_ALIAS_CACHE" ]; then
|
||
# Single awk pass: extract all command→aliases pairs, then filter by category
|
||
_alias_table=$(awk '
|
||
BEGIN { cmd=""; als=""; in_al=0 }
|
||
/"command": *"[^"]*"/ {
|
||
match($0, /"command": *"[^"]*"/)
|
||
s = substr($0, RSTART, RLENGTH)
|
||
gsub(/"command": *"|"$/, "", s); gsub(/"/, "", s)
|
||
cmd = s
|
||
}
|
||
/"aliases": *\[/ { in_al=1; als=""; next }
|
||
in_al && /^ *"[^"]*"/ {
|
||
match($0, /"[^"]*"/)
|
||
a = substr($0, RSTART+1, RLENGTH-2)
|
||
if (a != "") als = als (als==""?"":" ") a
|
||
}
|
||
/^ *\]/ && in_al { in_al=0 }
|
||
/^ *\}/ && cmd != "" && als != "" { print cmd "|" als; cmd=""; als="" }
|
||
' "$_ALIAS_CACHE")
|
||
|
||
echo ""
|
||
echo "INFRASTRUCTURE"
|
||
echo "$_alias_table" | grep -E "^(server|taskserv|component|extension)\|" | \
|
||
awk -F'|' '{ printf " %-14s → %s\n", $2, $1 }'
|
||
|
||
echo ""
|
||
echo "ORCHESTRATION"
|
||
echo "$_alias_table" | grep -E "^(job|workflow|batch|orchestrator)\|" | \
|
||
awk -F'|' '{ printf " %-14s → %s\n", $2, $1 }'
|
||
|
||
echo ""
|
||
echo "OTHER"
|
||
echo "$_alias_table" | grep -E "^(alias|workspace|platform|build|validate|help)\|" | \
|
||
awk -F'|' '{ printf " %-14s → %s\n", $2, $1 }'
|
||
unset _alias_table
|
||
else
|
||
echo ""
|
||
echo " s → server"
|
||
echo " t task → taskserv"
|
||
echo " c comp → component"
|
||
echo " e ext → extension"
|
||
echo " w wflow → workflow"
|
||
echo " j → job"
|
||
echo " b bat → batch"
|
||
echo " o orch → orchestrator"
|
||
echo " a al → alias"
|
||
fi
|
||
echo ""
|
||
echo "════════════════════════════════════════════════════"
|
||
echo "Tip: prvng <alias> help → subcommand details"
|
||
echo ""
|
||
exit 0
|
||
fi
|
||
|
||
# Job commands fast-path (orchestrator jobs — was "workflow")
|
||
if [ "${1:-}" = "job" ] || [ "${1:-}" = "j" ]; then
|
||
WORKFLOW_CMD="${2:-list}"
|
||
ARG="${3:-}"
|
||
|
||
# Handle help commands (matches -h, -help, h, ?)
|
||
case "$WORKFLOW_CMD" in
|
||
-h|-help|h|\?)
|
||
_workflow_help
|
||
exit 0
|
||
;;
|
||
esac
|
||
|
||
# Expand short command aliases
|
||
case "$WORKFLOW_CMD" in
|
||
l) WORKFLOW_CMD="list" ;;
|
||
s) WORKFLOW_CMD="status" ;;
|
||
m) WORKFLOW_CMD="monitor" ;;
|
||
st) WORKFLOW_CMD="stats" ;;
|
||
b) WORKFLOW_CMD="browse" ;;
|
||
c) WORKFLOW_CMD="cleanup" ;;
|
||
o) WORKFLOW_CMD="orchestrator" ;;
|
||
help) WORKFLOW_CMD="h" ;;
|
||
-help) WORKFLOW_CMD="h" ;;
|
||
--help) WORKFLOW_CMD="h" ;;
|
||
esac
|
||
|
||
# If WORKFLOW_CMD is a number, treat it as 'list <number>'
|
||
if [ -n "$WORKFLOW_CMD" ] && [ "$WORKFLOW_CMD" -ge 0 ] 2>/dev/null; then
|
||
ARG="$WORKFLOW_CMD"
|
||
WORKFLOW_CMD="list"
|
||
fi
|
||
|
||
# Use minimal config for quick execution
|
||
case "$WORKFLOW_CMD" in
|
||
list)
|
||
# Note: No < /dev/null here to allow interactive typedialog
|
||
if [ -z "$ARG" ]; then
|
||
_nu_with_config "use workflows/management.nu *; workflow list"
|
||
else
|
||
_nu_with_config "use workflows/management.nu *; workflow list $ARG"
|
||
fi
|
||
exit $?
|
||
;;
|
||
status)
|
||
if [ -z "$ARG" ]; then
|
||
echo "❌ Error: workflow status requires a task ID"
|
||
exit 1
|
||
fi
|
||
_nu_with_config "use workflows/management.nu *; workflow status '$ARG'"
|
||
exit $?
|
||
;;
|
||
monitor)
|
||
if [ -z "$ARG" ]; then
|
||
echo "❌ Error: workflow monitor requires a task ID"
|
||
exit 1
|
||
fi
|
||
_nu_with_config "use workflows/management.nu *; workflow monitor '$ARG'"
|
||
exit $?
|
||
;;
|
||
stats)
|
||
_nu_with_config "use workflows/management.nu *; workflow stats"
|
||
exit $?
|
||
;;
|
||
*)
|
||
echo "❌ Error: unknown workflow command '$WORKFLOW_CMD'"
|
||
echo ""
|
||
_workflow_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
# provider fast-path removed (ADR-025 Phase 4 — single-route principle).
|
||
# Falls through to main dispatch case.
|
||
|
||
# Fast-paths removed (ADR-025 Phase 4 — single-route principle).
|
||
# taskserv/server/cluster `list` now route to their thin handlers which invoke
|
||
# the full semantic path (middleware + live provider state). Daemon routing
|
||
# (for server list/ls/l) is preserved further down in the dispatch case.
|
||
|
||
# infra fast-path removed (ADR-025 Phase 4 — single-route principle).
|
||
# Falls through to main dispatch case. Help with no args still shows help menu.
|
||
if [ "${1:-}" = "infra" ] || [ "${1:-}" = "inf" ]; then
|
||
if [ -z "${2:-}" ]; then
|
||
provisioning help infrastructure
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
# validate fast-path removed (ADR-025 Phase 4 — single-route principle).
|
||
# Falls through to main dispatch case.
|
||
|
||
if [ ! -d "$PROVISIONING_USER_CONFIG" ] || [ ! -r "$PROVISIONING_CONTEXT_PATH" ]; then
|
||
[ ! -x "$PROVISIONING/core/nulib/provisioning setup" ] && echo "$PROVISIONING/core/nulib/provisioning setup not found" && exit 1
|
||
cd "$PROVISIONING/core/nulib"
|
||
./"provisioning setup"
|
||
echo ""
|
||
read -p "Use [enter] to continue or [ctrl-c] to cancel"
|
||
fi
|
||
[ ! -r "$PROVISIONING_USER_CONFIG/config.nu" ] && echo "$PROVISIONING_USER_CONFIG/config.nu not found" && exit 1
|
||
[ ! -r "$PROVISIONING_USER_CONFIG/env.nu" ] && echo "$PROVISIONING_USER_CONFIG/env.nu not found" && exit 1
|
||
|
||
NU_ARGS=(--config "$PROVISIONING_USER_CONFIG/config.nu" --env-config "$PROVISIONING_USER_CONFIG/env.nu")
|
||
export PROVISIONING_ARGS="$CMD_ARGS" NU_ARGS="$NU_ARGS"
|
||
#export NU_ARGS=${NU_ARGS//Application Support/Application\\ Support}
|
||
|
||
# Suppress repetitive config export output during initialization
|
||
export PROVISIONING_QUIET_EXPORT="true"
|
||
|
||
# Export NU_LIB_DIRS so Nushell can find modules during parsing
|
||
export NU_LIB_DIRS="$PROVISIONING/core/nulib:/opt/provisioning/core/nulib:/usr/local/provisioning/core/nulib"
|
||
|
||
# Export NICKEL_IMPORT_PATH so all nickel invocations resolve schemas/ and extensions/ without --import-path per call
|
||
export NICKEL_IMPORT_PATH="$PROVISIONING"
|
||
|
||
# ============================================================================
|
||
# COMMAND VALIDATION - Fast-fail for invalid commands + daemon check
|
||
# ============================================================================
|
||
# Read command-registry.txt and validate commands BEFORE invoking Nushell.
|
||
# This prevents hanging on invalid commands (like "prvng ps").
|
||
#
|
||
# Registry format: command|aliases|requires_daemon|requires_services|uses_cache|description
|
||
# Validation checks:
|
||
# 1. Command exists in registry (command or alias)
|
||
# 2. If requires_daemon=true, verify daemon is listening on port
|
||
# Fail-fast: Exit immediately with clear error if validation fails
|
||
#
|
||
_validate_command() {
|
||
local cmd="$1"
|
||
local registry_file="$PROVISIONING/core/nulib/commands-registry.ncl"
|
||
|
||
# Skip validation for empty command or help flags
|
||
if [ -z "$cmd" ] || [[ "$cmd" =~ ^(--help|--info|-i|-v|--version|-h|-V)$ ]]; then
|
||
return 0
|
||
fi
|
||
|
||
# Check if Nickel registry exists
|
||
if [ ! -f "$registry_file" ]; then
|
||
echo "ERROR: commands-registry.ncl not found at $registry_file" >&2
|
||
return 1
|
||
fi
|
||
|
||
# Cache: ~/.cache/provisioning/commands-registry.json
|
||
# Rebuilt via nickel export only when registry source changes (mtime check).
|
||
# Validated in pure bash using grep — no Nu process launched for validation.
|
||
local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/provisioning"
|
||
local cache_file="$cache_dir/commands-registry.json"
|
||
|
||
# Rebuild cache if stale or missing
|
||
if [ ! -f "$cache_file" ] || [ "$registry_file" -nt "$cache_file" ]; then
|
||
mkdir -p "$cache_dir"
|
||
nickel export --format json --import-path "$PROVISIONING" "$registry_file" \
|
||
> "$cache_file" 2>/dev/null || rm -f "$cache_file"
|
||
fi
|
||
|
||
local found=false
|
||
local requires_daemon=false
|
||
|
||
if [ -f "$cache_file" ]; then
|
||
# Pure bash grep: find the entry whose "command" or "aliases" contains $cmd.
|
||
# Extract all command names and alias values as a line-per-name list, then check.
|
||
local all_names
|
||
all_names=$(grep -o '"[a-zA-Z0-9_\-\+\.]*"' "$cache_file" | tr -d '"')
|
||
|
||
if echo "$all_names" | grep -qx "$cmd"; then
|
||
found=true
|
||
# Check requires_daemon for this specific command block.
|
||
# Strategy: find the block containing our cmd, check its requires_daemon value.
|
||
# Simple grep: look for "requires_daemon": true in the same JSON object as $cmd.
|
||
# We extract the 30-line window around the match and check for requires_daemon true.
|
||
local window
|
||
window=$(grep -n "\"$cmd\"" "$cache_file" | head -1 | cut -d: -f1)
|
||
if [ -n "$window" ]; then
|
||
local block
|
||
block=$(sed -n "$((window > 10 ? window - 10 : 1)),$((window + 15))p" "$cache_file")
|
||
if echo "$block" | grep -q '"requires_daemon": *true'; then
|
||
requires_daemon=true
|
||
fi
|
||
fi
|
||
else
|
||
found=false
|
||
fi
|
||
else
|
||
# No cache and nickel failed — fall back to Nu script (slow, one-time)
|
||
local validate_script="$PROVISIONING/core/nulib/scripts/validate-command.nu"
|
||
local query_result
|
||
query_result=$($NU -n "$validate_script" "$cmd" 2>&1)
|
||
if [[ "$query_result" == "NOT_FOUND" ]]; then
|
||
found=false
|
||
elif [[ "$query_result" =~ ^FOUND\|(true|false)$ ]]; then
|
||
found=true
|
||
requires_daemon="${BASH_REMATCH[1]}"
|
||
fi
|
||
fi
|
||
|
||
# ERROR 1: Command not found in registry
|
||
if [ "$found" = "false" ]; then
|
||
echo "" >&2
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
|
||
echo "❌ Unknown command: $cmd" >&2
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
|
||
echo "" >&2
|
||
echo "This command is not recognized by the provisioning system." >&2
|
||
echo "" >&2
|
||
echo "To see available commands:" >&2
|
||
echo " provisioning help" >&2
|
||
echo " prvng help # short alias" >&2
|
||
echo "" >&2
|
||
echo "Common commands:" >&2
|
||
echo " provisioning help - Show help" >&2
|
||
echo " provisioning platform - Manage platform services" >&2
|
||
echo " provisioning workspace - Workspace management" >&2
|
||
echo " provisioning create - Create resources" >&2
|
||
echo "" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# ERROR 2: Command requires daemon but daemon is not available
|
||
if [ "$requires_daemon" = "true" ]; then
|
||
# Check if daemon is listening on port (using lsof)
|
||
if ! lsof -i :"$DAEMON_PORT" -P -n 2>/dev/null | grep -q LISTEN; then
|
||
echo "" >&2
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
|
||
echo "❌ CRITICAL: provisioning_daemon not available" >&2
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
|
||
echo "" >&2
|
||
echo "The provisioning daemon is required for operation: $cmd" >&2
|
||
echo "Daemon is not listening on port $DAEMON_PORT" >&2
|
||
echo "" >&2
|
||
echo "The daemon is a CRITICAL component - all operations require it." >&2
|
||
echo "" >&2
|
||
echo "To check daemon status:" >&2
|
||
echo " provisioning platform status" >&2
|
||
echo " prvng plat st # short alias" >&2
|
||
echo "" >&2
|
||
echo "To start the daemon:" >&2
|
||
echo " provisioning platform start provisioning_daemon" >&2
|
||
echo " prvng plat start provisioning_daemon # short alias" >&2
|
||
echo "" >&2
|
||
echo "Allowed operations without daemon:" >&2
|
||
echo " • help / -h / --help - View help" >&2
|
||
echo " • platform <cmd> - Manage platform services" >&2
|
||
echo " • setup - Initial setup" >&2
|
||
echo "" >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# DAEMON ROUTING - ENABLED (Phase 3.7: CLI Daemon Integration)
|
||
# ============================================================================
|
||
# Redesigned daemon with pre-loaded Nushell environment (no CLI callback).
|
||
# Routes eligible commands to HTTP daemon for <100ms execution.
|
||
# Gracefully falls back to full load if daemon unavailable.
|
||
#
|
||
# ARCHITECTURE:
|
||
# 1. Check daemon health (curl with 5ms timeout)
|
||
# 2. Route eligible commands to daemon via HTTP POST
|
||
# 3. Fall back to full load if daemon unavailable
|
||
# 4. Zero breaking changes (graceful degradation)
|
||
#
|
||
# PERFORMANCE:
|
||
# - With daemon: <100ms for ALL commands
|
||
# - Without daemon: ~430ms (normal behavior)
|
||
# - Daemon fallback: Automatic, user sees no difference
|
||
|
||
if [ -n "$PROVISIONING_MODULE" ]; then
|
||
# -mod <module> mode: provisioning-cli.nu reads PROVISIONING_MODULE from env
|
||
# and dispatches to the module's main function directly.
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS </dev/null
|
||
else
|
||
# Only redirect stdin for non-interactive commands (nu command needs interactive stdin)
|
||
if [ "${1:-}" = "nu" ]; then
|
||
# For interactive mode, start nu with provisioning environment
|
||
export PROVISIONING_CONFIG="$PROVISIONING_USER_CONFIG"
|
||
# Start nu interactively - it will use the config and env from NU_ARGS
|
||
$NU "${NU_ARGS[@]}"
|
||
else
|
||
FIRST_ARG="${1:-}"
|
||
|
||
# CRITICAL: Handle help/version FIRST (avoid Nushell module loading hang)
|
||
case "$FIRST_ARG" in
|
||
help | h | --help | -h)
|
||
_show_help "${2:-}"
|
||
exit 0
|
||
;;
|
||
version | v | --version | -v | -V)
|
||
echo "$PROVISIONING_VERS"
|
||
exit 0
|
||
;;
|
||
about | --info | -i)
|
||
echo "Provisioning System v$PROVISIONING_VERS"
|
||
exit 0
|
||
;;
|
||
esac
|
||
|
||
# Expand single-char and short top-level aliases before validation.
|
||
# These map directly to canonical command names so the dispatcher and
|
||
# _validate_command see the canonical form.
|
||
case "$FIRST_ARG" in
|
||
s) FIRST_ARG="server" ;;
|
||
t) FIRST_ARG="taskserv" ;;
|
||
c) FIRST_ARG="component" ;;
|
||
e) FIRST_ARG="extension" ;;
|
||
w) FIRST_ARG="workflow" ;;
|
||
j) FIRST_ARG="job" ;;
|
||
b) FIRST_ARG="batch" ;;
|
||
o) FIRST_ARG="orchestrator" ;;
|
||
a|al) FIRST_ARG="alias" ;;
|
||
esac
|
||
|
||
# Validate command to prevent hanging on invalid commands
|
||
# Uses commands-registry.json cache (pure bash grep, no Nu process).
|
||
# This will exit immediately with clear error if:
|
||
# 1. Command not found in registry
|
||
# 2. Command requires daemon but daemon is not available
|
||
_validate_command "$FIRST_ARG"
|
||
|
||
# Don't redirect stdin for infrastructure commands - they may need interactive input
|
||
# Only redirect for commands we know are safe
|
||
case "$FIRST_ARG" in
|
||
status | health | diagnostics)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-status.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
workspace | ws)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/main_provisioning/workspace.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
env | allenv | list | ls | l | provider | providers | validate | plugin | plugins | nuinfo)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
platform | plat | p)
|
||
# logs needs interactive stdin for typedialog — keep stdin open.
|
||
# All other platform subcommands use the thin entry (~50ms vs ~3s).
|
||
case "${2:-}" in
|
||
logs | log)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS
|
||
;;
|
||
*)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-platform.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
esac
|
||
;;
|
||
batch | bat)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-batch.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
bootstrap)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-bootstrap.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
taskserv | task)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-taskserv.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
component | comp)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-component.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
extension | ext)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-extension.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
job)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-job.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
workflow | wflow)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-workflow.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
alias)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
create | new)
|
||
# "prvng create server ..." → "prvng server create ..."
|
||
shift
|
||
_resource="${1:-}"
|
||
[ -n "$_resource" ] && shift
|
||
case "$_resource" in
|
||
server|s)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server.nu" server create "$@"
|
||
exit $? ;;
|
||
taskserv|task|t)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-taskserv.nu" taskserv create "$@" </dev/null
|
||
exit $? ;;
|
||
cluster|cl)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cluster.nu" cluster create "$@" </dev/null
|
||
exit $? ;;
|
||
*)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" create "$_resource" "$@"
|
||
exit $? ;;
|
||
esac
|
||
;;
|
||
server | s)
|
||
# Route list/sync to the lightweight handler (loads only list.nu, ~255ms).
|
||
# All other subcommands go to the full handler (~1.15s).
|
||
_srv_sub="${2:-}"
|
||
case "$_srv_sub" in
|
||
list|ls|l|sync)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server-list.nu" $CMD_ARGS </dev/null
|
||
exit $? ;;
|
||
esac
|
||
# Intercept subcommand --help before Nu absorbs it at the top-level main
|
||
_has_help=false
|
||
for _a in "$@"; do [ "$_a" = "--help" ] || [ "$_a" = "-h" ] && _has_help=true && break; done
|
||
if [ "$_has_help" = "true" ]; then
|
||
case "$_srv_sub" in
|
||
delete|d|del)
|
||
$NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/delete.nu' *; main delete --help"
|
||
exit $? ;;
|
||
create|c)
|
||
$NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/create.nu' *; main create --help"
|
||
exit $? ;;
|
||
list|l)
|
||
$NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/list.nu' *; main list --help"
|
||
exit $? ;;
|
||
ssh)
|
||
$NU "${NU_ARGS[@]}" -c "use '$PROVISIONING/core/nulib/servers/ssh.nu' *; main ssh --help"
|
||
exit $? ;;
|
||
esac
|
||
fi
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server.nu" $CMD_ARGS
|
||
;;
|
||
ssh)
|
||
# Shortcut: provisioning ssh <hostname> → provisioning server ssh <hostname> --run
|
||
shift
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-server.nu" server ssh "$@" --run
|
||
;;
|
||
state | st)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-state.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
cluster | cl)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cluster.nu" $CMD_ARGS </dev/null
|
||
;;
|
||
volume | vol)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-volume.nu" "${@:2}" </dev/null
|
||
;;
|
||
fip | floating-ip)
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/main_provisioning/fip.nu" "${@:2}"
|
||
;;
|
||
*)
|
||
# All other commands — provisioning-cli.nu is the single fallback entry.
|
||
# stdin kept open for interactive commands (delete, update, etc.).
|
||
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/provisioning-cli.nu" $CMD_ARGS
|
||
;;
|
||
esac
|
||
fi
|
||
fi
|