2025-10-07 10:32:04 +01:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Info: Script to run Provisioning
|
2026-01-08 21:14:49 +00:00
|
|
|
# Author: JesusPerezLorenzo
|
2025-12-11 21:57:05 +00:00
|
|
|
# Release: 1.0.10
|
2025-10-07 10:32:04 +01:00
|
|
|
# Date: 2025-10-02
|
|
|
|
|
|
|
|
|
|
set +o errexit
|
|
|
|
|
set +o pipefail
|
|
|
|
|
|
2026-01-08 21:14:49 +00:00
|
|
|
# Debug: log startup
|
|
|
|
|
[ "$PROVISIONING_DEBUG_STARTUP" = "true" ] && echo "[DEBUG] Wrapper started with args: $@" >&2
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
export NU=$(type -P nu)
|
|
|
|
|
|
2026-01-08 21:14:49 +00:00
|
|
|
_release() {
|
2025-10-07 10:32:04 +01:00
|
|
|
grep "^# Release:" "$0" | sed "s/# Release: //g"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export PROVISIONING_VERS=$(_release)
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
set -o allexport
|
2025-10-07 10:32:04 +01:00
|
|
|
## shellcheck disable=SC1090
|
|
|
|
|
[ -n "$PROVISIONING_ENV" ] && [ -r "$PROVISIONING_ENV" ] && source "$PROVISIONING_ENV"
|
|
|
|
|
[ -r "../env-provisioning" ] && source ../env-provisioning
|
|
|
|
|
[ -r "env-provisioning" ] && source ./env-provisioning
|
2025-12-11 21:57:05 +00:00
|
|
|
#[ -r ".env" ] && source .env set
|
|
|
|
|
|
|
|
|
|
# Disable provisioning logo/banner output
|
|
|
|
|
export PROVISIONING_NO_TITLES=true
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
set +o allexport
|
|
|
|
|
|
|
|
|
|
export PROVISIONING=${PROVISIONING:-/usr/local/provisioning}
|
|
|
|
|
PROVIISONING_WKPATH=${PROVIISONING_WKPATH:-/tmp/tmp.}
|
|
|
|
|
|
|
|
|
|
RUNNER="provisioning"
|
|
|
|
|
|
|
|
|
|
[ "$1" == "" ] && shift
|
|
|
|
|
|
|
|
|
|
[ -z "$NU" ] || [ "$1" == "install" ] || [ "$1" == "reinstall" ] || [ "$1" == "mode" ] && exec bash $PROVISIONING/core/bin/install_nu.sh $PROVISIONING $1 $2
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
[ "$1" == "rmwk" ] && rm -rf "$PROVIISONING_WKPATH"* && echo "$PROVIISONING_WKPATH deleted" && exit
|
2025-10-07 10:32:04 +01:00
|
|
|
[ "$1" == "-x" ] && debug=-x && export PROVISIONING_DEBUG=true && shift
|
|
|
|
|
[ "$1" == "-xm" ] && export PROVISIONING_METADATA=true && shift
|
2025-12-11 21:57:05 +00:00
|
|
|
[ "$1" == "nu" ] && export PROVISIONING_DEBUG=true
|
2025-10-07 10:32:04 +01:00
|
|
|
[ "$1" == "--x" ] && set -x && debug=-x && export PROVISIONING_DEBUG=true && shift
|
2025-12-11 21:57:05 +00:00
|
|
|
[ "$1" == "-i" ] || [ "$2" == "-i" ] && echo "$(basename "$0") $(grep "^# Info:" "$0" | sed "s/# Info: //g") " && exit
|
|
|
|
|
[ "$1" == "-v" ] || [ "$1" == "--version" ] || [ "$2" == "-v" ] && _release && exit
|
2025-10-07 10:32:04 +01:00
|
|
|
CMD_ARGS=$@
|
2025-12-11 21:57:05 +00:00
|
|
|
|
|
|
|
|
# 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
|
2025-10-07 10:32:04 +01:00
|
|
|
-mod)
|
2026-01-08 21:14:49 +00:00
|
|
|
PROVISIONING_MODULE=$(echo "$2" | sed 's/ //g' | cut -f1 -d"|")
|
|
|
|
|
PROVISIONING_MODULE_TASK=$(echo "$2" | sed 's/ //g' | cut -f2 -d"|")
|
2025-10-07 10:32:04 +01:00
|
|
|
[ "$PROVISIONING_MODULE" == "$PROVISIONING_MODULE_TASK" ] && PROVISIONING_MODULE_TASK=""
|
|
|
|
|
shift 2
|
|
|
|
|
CMD_ARGS=$@
|
2026-01-08 21:14:49 +00:00
|
|
|
[ "$PROVISIONING_DEBUG_STARTUP" = "true" ] && echo "[DEBUG] -mod detected: MODULE=$PROVISIONING_MODULE, TASK=$PROVISIONING_MODULE_TASK, CMD_ARGS=$CMD_ARGS" >&2
|
2025-10-07 10:32:04 +01:00
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
NU_ARGS=""
|
|
|
|
|
|
|
|
|
|
DEFAULT_CONTEXT_TEMPLATE="default_context.yaml"
|
|
|
|
|
case "$(uname | tr '[:upper:]' '[:lower:]')" in
|
2025-12-11 21:57:05 +00:00
|
|
|
linux) PROVISIONING_USER_CONFIG="$HOME/.config/provisioning/nushell"
|
2025-10-07 10:32:04 +01:00
|
|
|
PROVISIONING_CONTEXT_PATH="$HOME/.config/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
|
|
|
|
|
|
|
|
|
|
;;
|
2025-12-11 21:57:05 +00:00
|
|
|
darwin) PROVISIONING_USER_CONFIG="$HOME/Library/Application Support/provisioning/nushell"
|
|
|
|
|
PROVISIONING_CONTEXT_PATH="$HOME/Library/Application Support/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
|
2025-10-07 10:32:04 +01:00
|
|
|
;;
|
2025-12-11 21:57:05 +00:00
|
|
|
*) PROVISIONING_USER_CONFIG="$HOME/.config/provisioning/nushell"
|
2025-10-07 10:32:04 +01:00
|
|
|
PROVISIONING_CONTEXT_PATH="$HOME/.config/provisioning/$DEFAULT_CONTEXT_TEMPLATE"
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
|
2026-01-08 21:14:49 +00:00
|
|
|
# ════════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
# DAEMON ROUTING - Try daemon for all commands (except setup/help/interactive)
|
|
|
|
|
# Falls back to traditional handlers if daemon unavailable
|
|
|
|
|
# ════════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
DAEMON_ENDPOINT="http://127.0.0.1:9091/execute"
|
|
|
|
|
|
|
|
|
|
# Function to execute command via daemon
|
|
|
|
|
execute_via_daemon() {
|
|
|
|
|
local cmd="$1"
|
|
|
|
|
shift
|
|
|
|
|
|
|
|
|
|
# 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]"
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
curl -s -m $timeout -X POST "$DAEMON_ENDPOINT" \
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
-d "{\"command\":\"$cmd\",\"args\":$args_json,\"timeout_ms\":30000}" 2>/dev/null | \
|
|
|
|
|
sed -n 's/.*"stdout":"\(.*\)","execution.*/\1/p' | \
|
|
|
|
|
sed 's/\\n/\n/g'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Try daemon ONLY for lightweight commands (list, show, status)
|
|
|
|
|
# Skip daemon for heavy commands (create, delete, update) because bash wrapper is slow
|
|
|
|
|
if [ "$1" = "server" ] || [ "$1" = "s" ]; then
|
|
|
|
|
if [ "$2" = "list" ] || [ -z "$2" ]; then
|
|
|
|
|
# Light command - try daemon
|
|
|
|
|
[ "$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
|
|
|
|
|
[ "$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)
|
|
|
|
|
# ════════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
# Help commands (uses help_minimal.nu)
|
2025-12-11 21:57:05 +00:00
|
|
|
if [ -z "$1" ] || [ "$1" = "help" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ] || [ "$1" = "--helpinfo" ]; then
|
|
|
|
|
category="${2:-}"
|
|
|
|
|
$NU -n -c "source '$PROVISIONING/core/nulib/help_minimal.nu'; provisioning-help '$category' | print" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
|
2026-01-08 21:14:49 +00:00
|
|
|
# Workspace operations (fast-path)
|
|
|
|
|
if [ "$1" = "workspace" ] || [ "$1" = "ws" ]; then
|
|
|
|
|
case "$2" in
|
|
|
|
|
"list"|"")
|
|
|
|
|
$NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; workspace-list | table" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
;;
|
|
|
|
|
"active")
|
|
|
|
|
$NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; workspace-active" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
;;
|
|
|
|
|
"info")
|
|
|
|
|
if [ -n "$3" ]; then
|
|
|
|
|
$NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; workspace-info '$3'" 2>/dev/null
|
|
|
|
|
else
|
|
|
|
|
$NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; workspace-active | workspace-info \$in" 2>/dev/null
|
|
|
|
|
fi
|
|
|
|
|
exit $?
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
# Other workspace commands (switch, register, etc.) fall through to full loading
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Status/Health check (fast-path)
|
|
|
|
|
if [ "$1" = "status" ] || [ "$1" = "health" ]; then
|
|
|
|
|
$NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; status-quick | table" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Environment display (fast-path)
|
|
|
|
|
if [ "$1" = "env" ] || [ "$1" = "allenv" ]; then
|
|
|
|
|
$NU -n -c "source '$PROVISIONING/core/nulib/lib_minimal.nu'; env-quick | table" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Provider list (lightweight - reads filesystem only, no module loading)
|
|
|
|
|
if [ "$1" = "provider" ] || [ "$1" = "providers" ]; then
|
|
|
|
|
if [ "$2" = "list" ] || [ -z "$2" ]; then
|
|
|
|
|
$NU -n -c "
|
|
|
|
|
source '$PROVISIONING/core/nulib/lib_minimal.nu'
|
|
|
|
|
|
|
|
|
|
let provisioning = (\$env.PROVISIONING | default '/usr/local/provisioning')
|
|
|
|
|
let providers_base = (\$provisioning | path join 'extensions' | path join 'providers')
|
|
|
|
|
|
|
|
|
|
if not (\$providers_base | path exists) {
|
|
|
|
|
print 'PROVIDERS list: (none found)'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Discover all providers from directories
|
|
|
|
|
let all_providers = (
|
|
|
|
|
ls \$providers_base | where type == 'dir' | each {|prov_dir|
|
|
|
|
|
let prov_name = (\$prov_dir.name | path basename)
|
|
|
|
|
if \$prov_name != 'prov_lib' {
|
|
|
|
|
{name: \$prov_name, type: 'providers', version: '0.0.1'}
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
} | compact
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (\$all_providers | length) == 0 {
|
|
|
|
|
print 'PROVIDERS list: (none found)'
|
|
|
|
|
} else {
|
|
|
|
|
print 'PROVIDERS list: '
|
|
|
|
|
print ''
|
|
|
|
|
\$all_providers | table
|
|
|
|
|
}
|
|
|
|
|
" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Taskserv list (fast-path) - avoid full system load
|
|
|
|
|
if [ "$1" = "taskserv" ] || [ "$1" = "task" ]; then
|
|
|
|
|
if [ "$2" = "list" ] || [ -z "$2" ]; then
|
|
|
|
|
$NU -n -c "
|
|
|
|
|
# Direct implementation of taskserv discovery (no dependency loading)
|
|
|
|
|
# Taskservs are nested: extensions/taskservs/{category}/{name}/kcl/
|
|
|
|
|
let provisioning = (\$env.PROVISIONING | default '/usr/local/provisioning')
|
|
|
|
|
let taskservs_base = (\$provisioning | path join 'extensions' | path join 'taskservs')
|
|
|
|
|
|
|
|
|
|
if not (\$taskservs_base | path exists) {
|
|
|
|
|
print '📦 Available Taskservs: (none found)'
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Discover all taskservs from nested categories
|
|
|
|
|
let all_taskservs = (
|
|
|
|
|
ls \$taskservs_base | where type == 'dir' | each {|cat_dir|
|
|
|
|
|
let category = (\$cat_dir.name | path basename)
|
|
|
|
|
let cat_path = (\$taskservs_base | path join \$category)
|
|
|
|
|
if (\$cat_path | path exists) {
|
|
|
|
|
ls \$cat_path | where type == 'dir' | each {|ts|
|
|
|
|
|
let ts_name = (\$ts.name | path basename)
|
|
|
|
|
{task: \$ts_name, mode: \$category, info: ''}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
[]
|
|
|
|
|
}
|
|
|
|
|
} | flatten
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (\$all_taskservs | length) == 0 {
|
|
|
|
|
print '📦 Available Taskservs: (none found)'
|
|
|
|
|
} else {
|
|
|
|
|
print '📦 Available Taskservs:'
|
|
|
|
|
print ''
|
|
|
|
|
\$all_taskservs | each {|ts|
|
|
|
|
|
print \$\" • (\$ts.task) [(\$ts.mode)]\"
|
|
|
|
|
} | ignore
|
|
|
|
|
}
|
|
|
|
|
" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Server list (lightweight - reads filesystem only, no config loading)
|
|
|
|
|
if [ "$1" = "server" ] || [ "$1" = "s" ]; then
|
|
|
|
|
if [ "$2" = "list" ] || [ -z "$2" ]; then
|
|
|
|
|
# Extract --infra flag from remaining args
|
|
|
|
|
INFRA_FILTER=""
|
|
|
|
|
shift
|
|
|
|
|
[ "$1" = "list" ] && shift
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
--infra|-i) INFRA_FILTER="$2"; shift 2 ;;
|
|
|
|
|
*) shift ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
$NU -n -c "
|
|
|
|
|
source '$PROVISIONING/core/nulib/lib_minimal.nu'
|
|
|
|
|
|
|
|
|
|
# Get active workspace
|
|
|
|
|
let active_ws = (workspace-active)
|
|
|
|
|
if (\$active_ws | is-empty) {
|
|
|
|
|
print 'No active workspace'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get workspace path from config
|
|
|
|
|
let user_config_path = if (\$env.HOME | path exists) {
|
|
|
|
|
(
|
|
|
|
|
\$env.HOME | path join 'Library' | path join 'Application Support' |
|
|
|
|
|
path join 'provisioning' | path join 'user_config.yaml'
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if not (\$user_config_path | path exists) {
|
|
|
|
|
print 'Config not found'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let config = (open \$user_config_path)
|
|
|
|
|
let workspaces = (\$config | get --optional workspaces | default [])
|
|
|
|
|
let ws = (\$workspaces | where { \$in.name == \$active_ws } | first)
|
|
|
|
|
|
|
|
|
|
if (\$ws | is-empty) {
|
|
|
|
|
print 'Workspace not found'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ws_path = \$ws.path
|
|
|
|
|
let infra_path = (\$ws_path | path join 'infra')
|
|
|
|
|
|
|
|
|
|
if not (\$infra_path | path exists) {
|
|
|
|
|
print 'No infrastructures found'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Filter by infrastructure if specified
|
|
|
|
|
let infra_filter = \"$INFRA_FILTER\"
|
|
|
|
|
|
|
|
|
|
# List server definitions from infrastructure (filtered if --infra specified)
|
|
|
|
|
let servers = (
|
|
|
|
|
ls \$infra_path | where type == 'dir' | each {|infra|
|
|
|
|
|
let infra_name = (\$infra.name | path basename)
|
|
|
|
|
|
|
|
|
|
# Skip if filter is specified and doesn't match
|
|
|
|
|
if ((\$infra_filter | is-not-empty) and (\$infra_name != \$infra_filter)) {
|
|
|
|
|
[]
|
|
|
|
|
} else {
|
|
|
|
|
let servers_file = (\$infra_path | path join \$infra_name | path join 'defs' | path join 'servers.k')
|
|
|
|
|
|
|
|
|
|
if (\$servers_file | path exists) {
|
|
|
|
|
# Parse the KCL servers.k file to extract server names
|
|
|
|
|
let content = (open \$servers_file --raw)
|
|
|
|
|
# Extract hostnames from hostname = "..." patterns by splitting on quotes
|
|
|
|
|
let hostnames = (
|
|
|
|
|
\$content
|
|
|
|
|
| split row \"\\n\"
|
|
|
|
|
| where {|line| \$line | str contains \"hostname = \\\"\" }
|
|
|
|
|
| each {|line|
|
|
|
|
|
# Split by quotes to extract hostname value
|
|
|
|
|
let parts = (\$line | split row \"\\\"\")
|
|
|
|
|
if (\$parts | length) >= 2 {
|
|
|
|
|
\$parts | get 1
|
|
|
|
|
} else {
|
|
|
|
|
\"\"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
| where {|h| (\$h | is-not-empty) }
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
\$hostnames | each {|srv_name|
|
|
|
|
|
{
|
|
|
|
|
name: \$srv_name
|
|
|
|
|
infrastructure: \$infra_name
|
|
|
|
|
path: \$servers_file
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
[]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} | flatten
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (\$servers | length) == 0 {
|
|
|
|
|
print '📦 Available Servers: (none configured)'
|
|
|
|
|
} else {
|
|
|
|
|
print '📦 Available Servers:'
|
|
|
|
|
print ''
|
|
|
|
|
\$servers | each {|srv|
|
|
|
|
|
print \$\" • (\$srv.name) [(\$srv.infrastructure)]\"
|
|
|
|
|
} | ignore
|
|
|
|
|
}
|
|
|
|
|
" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Cluster list (lightweight - reads filesystem only)
|
|
|
|
|
if [ "$1" = "cluster" ] || [ "$1" = "cl" ]; then
|
|
|
|
|
if [ "$2" = "list" ] || [ -z "$2" ]; then
|
|
|
|
|
$NU -n -c "
|
|
|
|
|
source '$PROVISIONING/core/nulib/lib_minimal.nu'
|
|
|
|
|
|
|
|
|
|
# Get active workspace
|
|
|
|
|
let active_ws = (workspace-active)
|
|
|
|
|
if (\$active_ws | is-empty) {
|
|
|
|
|
print 'No active workspace'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get workspace path from config
|
|
|
|
|
let user_config_path = (
|
|
|
|
|
\$env.HOME | path join 'Library' | path join 'Application Support' |
|
|
|
|
|
path join 'provisioning' | path join 'user_config.yaml'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not (\$user_config_path | path exists) {
|
|
|
|
|
print 'Config not found'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let config = (open \$user_config_path)
|
|
|
|
|
let workspaces = (\$config | get --optional workspaces | default [])
|
|
|
|
|
let ws = (\$workspaces | where { \$in.name == \$active_ws } | first)
|
|
|
|
|
|
|
|
|
|
if (\$ws | is-empty) {
|
|
|
|
|
print 'Workspace not found'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ws_path = \$ws.path
|
|
|
|
|
|
|
|
|
|
# List all clusters from workspace
|
|
|
|
|
let clusters = (
|
|
|
|
|
if ((\$ws_path | path join '.clusters') | path exists) {
|
|
|
|
|
let clusters_path = (\$ws_path | path join '.clusters')
|
|
|
|
|
ls \$clusters_path | where type == 'dir' | each {|cl|
|
|
|
|
|
let cl_name = (\$cl.name | path basename)
|
|
|
|
|
{
|
|
|
|
|
name: \$cl_name
|
|
|
|
|
path: \$cl.name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
[]
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (\$clusters | length) == 0 {
|
|
|
|
|
print '🗂️ Available Clusters: (none found)'
|
|
|
|
|
} else {
|
|
|
|
|
print '🗂️ Available Clusters:'
|
|
|
|
|
print ''
|
|
|
|
|
\$clusters | each {|cl|
|
|
|
|
|
print \$\" • (\$cl.name)\"
|
|
|
|
|
} | ignore
|
|
|
|
|
}
|
|
|
|
|
" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Infra list (lightweight - reads filesystem only)
|
|
|
|
|
if [ "$1" = "infra" ] || [ "$1" = "inf" ]; then
|
|
|
|
|
if [ "$2" = "list" ] || [ -z "$2" ]; then
|
|
|
|
|
$NU -n -c "
|
|
|
|
|
source '$PROVISIONING/core/nulib/lib_minimal.nu'
|
|
|
|
|
|
|
|
|
|
# Get active workspace
|
|
|
|
|
let active_ws = (workspace-active)
|
|
|
|
|
if (\$active_ws | is-empty) {
|
|
|
|
|
print 'No active workspace'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get workspace path from config
|
|
|
|
|
let user_config_path = (
|
|
|
|
|
\$env.HOME | path join 'Library' | path join 'Application Support' |
|
|
|
|
|
path join 'provisioning' | path join 'user_config.yaml'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not (\$user_config_path | path exists) {
|
|
|
|
|
print 'Config not found'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let config = (open \$user_config_path)
|
|
|
|
|
let workspaces = (\$config | get --optional workspaces | default [])
|
|
|
|
|
let ws = (\$workspaces | where { \$in.name == \$active_ws } | first)
|
|
|
|
|
|
|
|
|
|
if (\$ws | is-empty) {
|
|
|
|
|
print 'Workspace not found'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ws_path = \$ws.path
|
|
|
|
|
let infra_path = (\$ws_path | path join 'infra')
|
|
|
|
|
|
|
|
|
|
if not (\$infra_path | path exists) {
|
|
|
|
|
print '📁 Available Infrastructures: (none configured)'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# List all infrastructures
|
|
|
|
|
let infras = (
|
|
|
|
|
ls \$infra_path | where type == 'dir' | each {|inf|
|
|
|
|
|
let inf_name = (\$inf.name | path basename)
|
|
|
|
|
let inf_full_path = (\$infra_path | path join \$inf_name)
|
|
|
|
|
let has_config = ((\$inf_full_path | path join 'settings.k') | path exists)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
name: \$inf_name
|
|
|
|
|
configured: \$has_config
|
|
|
|
|
modified: \$inf.modified
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (\$infras | length) == 0 {
|
|
|
|
|
print '📁 Available Infrastructures: (none found)'
|
|
|
|
|
} else {
|
|
|
|
|
print '📁 Available Infrastructures:'
|
|
|
|
|
print ''
|
|
|
|
|
\$infras | each {|inf|
|
|
|
|
|
let status = if \$inf.configured { '✓' } else { '○' }
|
|
|
|
|
let output = \" [\" + \$status + \"] \" + \$inf.name
|
|
|
|
|
print \$output
|
|
|
|
|
} | ignore
|
|
|
|
|
}
|
|
|
|
|
" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Config validation (lightweight - validates config structure without full load)
|
|
|
|
|
if [ "$1" = "validate" ]; then
|
|
|
|
|
if [ "$2" = "config" ] || [ -z "$2" ]; then
|
|
|
|
|
$NU -n -c "
|
|
|
|
|
source '$PROVISIONING/core/nulib/lib_minimal.nu'
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
# Get active workspace
|
|
|
|
|
let active_ws = (workspace-active)
|
|
|
|
|
if (\$active_ws | is-empty) {
|
|
|
|
|
print '❌ Error: No active workspace'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get workspace path from config
|
|
|
|
|
let user_config_path = (
|
|
|
|
|
\$env.HOME | path join 'Library' | path join 'Application Support' |
|
|
|
|
|
path join 'provisioning' | path join 'user_config.yaml'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not (\$user_config_path | path exists) {
|
|
|
|
|
print '❌ Error: User config not found at' \$user_config_path
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let config = (open \$user_config_path)
|
|
|
|
|
let workspaces = (\$config | get --optional workspaces | default [])
|
|
|
|
|
let ws = (\$workspaces | where { \$in.name == \$active_ws } | first)
|
|
|
|
|
|
|
|
|
|
if (\$ws | is-empty) {
|
|
|
|
|
print '❌ Error: Workspace' \$active_ws 'not found in config'
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ws_path = \$ws.path
|
|
|
|
|
|
|
|
|
|
# Validate workspace structure
|
|
|
|
|
let required_dirs = ['infra', 'config', '.clusters']
|
|
|
|
|
let infra_path = (\$ws_path | path join 'infra')
|
|
|
|
|
let config_path = (\$ws_path | path join 'config')
|
|
|
|
|
|
|
|
|
|
let missing_dirs = \$required_dirs | where { not ((\$ws_path | path join \$in) | path exists) }
|
|
|
|
|
|
|
|
|
|
if (\$missing_dirs | length) > 0 {
|
|
|
|
|
print '⚠️ Warning: Missing directories:' (\$missing_dirs | str join ', ')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Validate infrastructures have required files
|
|
|
|
|
if (\$infra_path | path exists) {
|
|
|
|
|
let infras = (ls \$infra_path | where type == 'dir')
|
|
|
|
|
let invalid_infras = (
|
|
|
|
|
\$infras | each {|inf|
|
|
|
|
|
let inf_name = (\$inf.name | path basename)
|
|
|
|
|
let inf_full_path = (\$infra_path | path join \$inf_name)
|
|
|
|
|
if not ((\$inf_full_path | path join 'settings.k') | path exists) {
|
|
|
|
|
\$inf_name
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
} | compact
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (\$invalid_infras | length) > 0 {
|
|
|
|
|
print '⚠️ Warning: Infrastructures missing settings.k:' (\$invalid_infras | str join ', ')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Validate user config structure
|
|
|
|
|
let has_active = ((\$config | get --optional active_workspace) != null)
|
|
|
|
|
let has_workspaces = ((\$config | get --optional workspaces) != null)
|
|
|
|
|
let has_preferences = ((\$config | get --optional preferences) != null)
|
|
|
|
|
|
|
|
|
|
if not \$has_active {
|
|
|
|
|
print '⚠️ Warning: Missing active_workspace in user config'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if not \$has_workspaces {
|
|
|
|
|
print '⚠️ Warning: Missing workspaces list in user config'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if not \$has_preferences {
|
|
|
|
|
print '⚠️ Warning: Missing preferences in user config'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Summary
|
|
|
|
|
print ''
|
|
|
|
|
print '✓ Configuration validation complete for workspace:' \$active_ws
|
|
|
|
|
print ' Path:' \$ws_path
|
|
|
|
|
print ' Status: Valid (with warnings, if any listed above)'
|
|
|
|
|
} catch {|err|
|
|
|
|
|
print '❌ Validation error:' \$err
|
|
|
|
|
}
|
|
|
|
|
" 2>/dev/null
|
|
|
|
|
exit $?
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ ! -d "$PROVISIONING_USER_CONFIG" ] || [ ! -r "$PROVISIONING_CONTEXT_PATH" ] ; then
|
2025-10-07 10:32:04 +01:00
|
|
|
[ ! -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}
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
# 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"
|
|
|
|
|
|
2026-01-08 21:14:49 +00:00
|
|
|
# ============================================================================
|
|
|
|
|
# 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
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
if [ -n "$PROVISIONING_MODULE" ] ; then
|
2025-10-07 10:32:04 +01:00
|
|
|
if [[ -x $PROVISIONING/core/nulib/$RUNNER\ $PROVISIONING_MODULE ]] ; then
|
2026-01-08 21:14:49 +00:00
|
|
|
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/$RUNNER $PROVISIONING_MODULE" $CMD_ARGS
|
2025-12-11 21:57:05 +00:00
|
|
|
else
|
2025-10-07 10:32:04 +01:00
|
|
|
echo "Error \"$PROVISIONING/core/nulib/$RUNNER $PROVISIONING_MODULE\" not found"
|
|
|
|
|
fi
|
|
|
|
|
else
|
2025-12-11 21:57:05 +00:00
|
|
|
# Only redirect stdin for non-interactive commands (nu command needs interactive stdin)
|
|
|
|
|
if [ "$1" = "nu" ]; then
|
2026-01-08 21:14:49 +00:00
|
|
|
# For interactive mode, start nu with provisioning environment
|
2025-12-11 21:57:05 +00:00
|
|
|
export PROVISIONING_CONFIG="$PROVISIONING_USER_CONFIG"
|
2026-01-08 21:14:49 +00:00
|
|
|
# Start nu interactively - it will use the config and env from NU_ARGS
|
|
|
|
|
$NU "${NU_ARGS[@]}"
|
2025-12-11 21:57:05 +00:00
|
|
|
else
|
2026-01-08 21:14:49 +00:00
|
|
|
# Don't redirect stdin for infrastructure commands - they may need interactive input
|
|
|
|
|
# Only redirect for commands we know are safe
|
|
|
|
|
case "$1" in
|
|
|
|
|
help|h|--help|--info|-i|-v|--version|env|allenv|status|health|list|ls|l|workspace|ws|provider|providers|validate|plugin|plugins|nuinfo)
|
|
|
|
|
# Safe commands - can use /dev/null
|
|
|
|
|
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/$RUNNER" $CMD_ARGS < /dev/null
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
# All other commands (create, delete, server, taskserv, etc.) - keep stdin open
|
|
|
|
|
# NOTE: PROVISIONING_MODULE is automatically inherited by Nushell from bash environment
|
|
|
|
|
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/$RUNNER" $CMD_ARGS
|
|
|
|
|
;;
|
|
|
|
|
esac
|
2025-12-11 21:57:05 +00:00
|
|
|
fi
|
2025-10-07 10:32:04 +01:00
|
|
|
fi
|