chore: update scripts

This commit is contained in:
Jesús Pérez 2026-01-08 21:14:49 +00:00
parent a874f20a4d
commit 0ccd697e55
Signed by: jesus
GPG Key ID: 9F243E355E0BC939
12 changed files with 948 additions and 757 deletions

View File

@ -47,20 +47,24 @@ register_plugins() {
else
echo -e "❗ Failed to install nu_plugin_tera"
fi
else
echo -e "❗ Cargo not found - nu_plugin_tera not installed"
fi
}
# Install nu_plugin_kcl if available
echo -e "Installing nu_plugin_kcl..."
if cargo install nu_plugin_kcl; then
if $source/nu -c "register ~/.cargo/bin/nu_plugin_kcl" 2>/dev/null; then
echo -e "nu_plugin_kcl\t\t registred"
# Check Nickel configuration language installation
check_nickel_installation() {
if command -v nickel >/dev/null 2>&1; then
nickel_version=$(nickel --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
echo -e "Nickel\t\t\t already installed (version $nickel_version)"
return 0
else
echo -e "❗ Failed to register nu_plugin_kcl"
fi
else
echo -e "❗ Failed to install nu_plugin_kcl"
fi
else
echo -e "❗ Cargo not found - nu_plugin_tera and nu_plugin_kcl not installed"
echo -e "⚠️ Nickel not found - Optional but recommended for config rendering"
echo -e " Install via: \$PROVISIONING/core/cli/tools-install nickel"
echo -e " Recommended method: nix profile install nixpkgs#nickel"
echo -e " (Pre-built binaries have Nix library dependencies)"
echo -e " https://nickel-lang.org/getting-started"
return 1
fi
}
@ -184,6 +188,9 @@ message_install() {
install_from_url $INSTALL_PATH
install_mode ""
fi
echo ""
echo -e "Checking optional configuration languages..."
check_nickel_installation
}
set +o errexit

View File

@ -10,7 +10,7 @@ use ../nulib/providers/discover.nu *
use ../nulib/providers/load.nu *
use ../nulib/clusters/discover.nu *
use ../nulib/clusters/load.nu *
use ../nulib/lib_provisioning/kcl_module_loader.nu *
use ../nulib/lib_provisioning/module_loader.nu *
use ../nulib/lib_provisioning/config/accessor.nu config-get
# Main module loader command with enhanced features
@ -82,11 +82,11 @@ export def "main discover" [
}
}
# Sync KCL dependencies for infrastructure workspace
export def "main sync-kcl" [
# Sync Nickel dependencies for infrastructure workspace
export def "main sync" [
infra: string, # Infrastructure name or path
--manifest: string = "providers.manifest.yaml", # Manifest file name
--kcl # Show KCL module info after sync
--show-modules # Show module info after sync
] {
# Resolve infrastructure path
let infra_path = if ($infra | path exists) {
@ -102,14 +102,14 @@ export def "main sync-kcl" [
}
}
# Sync KCL dependencies using library function
sync-kcl-dependencies $infra_path --manifest $manifest
# Sync Nickel dependencies using library function
sync-nickel-dependencies $infra_path --manifest $manifest
# Show KCL module info if requested
if $kcl {
# Show Nickel module info if requested
if $show_modules {
print ""
print "📋 KCL Modules:"
let modules_dir = (get-config-value "kcl" "modules_dir")
print "📋 Nickel Modules:"
let modules_dir = (get-config-value "nickel" "modules_dir")
let modules_path = ($infra_path | path join $modules_dir)
if ($modules_path | path exists) {
@ -382,7 +382,7 @@ export def "main override create" [
$"# Override for ($module) in ($infra)
# Based on template: ($from)
import ($type).*.($module).kcl.($module) as base
import ($type).*.($module).ncl.($module) as base
import provisioning.workspace.templates.($type).($from) as template
# Infrastructure-specific overrides
@ -396,7 +396,7 @@ import provisioning.workspace.templates.($type).($from) as template
} else {
$"# Override for ($module) in ($infra)
import ($type).*.($module).kcl.($module) as base
import ($type).*.($module).ncl.($module) as base
# Infrastructure-specific overrides
($module)_($infra)_override: base.($module | str capitalize) = base.($module)_config {
@ -627,29 +627,29 @@ def load_extension_to_workspace [
cp -r $source_module_path $parent_dir
print $" ✓ Schemas copied to workspace .($extension_type)/"
# STEP 2a: Update individual module's kcl.mod with correct workspace paths
# STEP 2a: Update individual module's nickel.mod with correct workspace paths
# Calculate relative paths based on categorization depth
let provisioning_path = if ($group_path | is-not-empty) {
# Categorized: .{ext}/{category}/{module}/kcl/ -> ../../../../.kcl/packages/provisioning
"../../../../.kcl/packages/provisioning"
# Categorized: .{ext}/{category}/{module}/nickel/ -> ../../../../.nickel/packages/provisioning
"../../../../.nickel/packages/provisioning"
} else {
# Non-categorized: .{ext}/{module}/kcl/ -> ../../../.kcl/packages/provisioning
"../../../.kcl/packages/provisioning"
# Non-categorized: .{ext}/{module}/nickel/ -> ../../../.nickel/packages/provisioning
"../../../.nickel/packages/provisioning"
}
let parent_path = if ($group_path | is-not-empty) {
# Categorized: .{ext}/{category}/{module}/kcl/ -> ../../..
# Categorized: .{ext}/{category}/{module}/nickel/ -> ../../..
"../../.."
} else {
# Non-categorized: .{ext}/{module}/kcl/ -> ../..
# Non-categorized: .{ext}/{module}/nickel/ -> ../..
"../.."
}
# Update the module's kcl.mod file with workspace-relative paths
let module_kcl_mod_path = ($target_module_path | path join "kcl" "kcl.mod")
if ($module_kcl_mod_path | path exists) {
print $" 🔧 Updating module kcl.mod with workspace paths"
let module_kcl_mod_content = $"[package]
# Update the module's nickel.mod file with workspace-relative paths
let module_nickel_mod_path = ($target_module_path | path join "nickel" "nickel.mod")
if ($module_nickel_mod_path | path exists) {
print $" 🔧 Updating module nickel.mod with workspace paths"
let module_nickel_mod_content = $"[package]
name = \"($module)\"
edition = \"v0.11.3\"
version = \"0.0.1\"
@ -658,24 +658,24 @@ version = \"0.0.1\"
provisioning = { path = \"($provisioning_path)\", version = \"0.0.1\" }
($extension_type) = { path = \"($parent_path)\", version = \"0.1.0\" }
"
$module_kcl_mod_content | save -f $module_kcl_mod_path
print $" ✓ Updated kcl.mod: ($module_kcl_mod_path)"
$module_nickel_mod_content | save -f $module_nickel_mod_path
print $" ✓ Updated nickel.mod: ($module_nickel_mod_path)"
}
} else {
print $" ⚠️ Warning: Source not found at ($source_module_path)"
}
# STEP 2b: Create kcl.mod in workspace/.{extension_type}
let extension_kcl_mod = ($target_schemas_dir | path join "kcl.mod")
if not ($extension_kcl_mod | path exists) {
print $" 📦 Creating kcl.mod for .($extension_type) package"
let kcl_mod_content = $"[package]
# STEP 2b: Create nickel.mod in workspace/.{extension_type}
let extension_nickel_mod = ($target_schemas_dir | path join "nickel.mod")
if not ($extension_nickel_mod | path exists) {
print $" 📦 Creating nickel.mod for .($extension_type) package"
let nickel_mod_content = $"[package]
name = \"($extension_type)\"
edition = \"v0.11.3\"
version = \"0.1.0\"
description = \"Workspace-level ($extension_type) schemas\"
"
$kcl_mod_content | save $extension_kcl_mod
$nickel_mod_content | save $extension_nickel_mod
}
# Ensure config directory exists
@ -690,9 +690,9 @@ description = \"Workspace-level ($extension_type) schemas\"
# Build import statement with "as {module}" alias
let import_stmt = if ($group_path | is-not-empty) {
$"import ($extension_type).($group_path).($module).kcl.($module) as ($module)"
$"import ($extension_type).($group_path).($module).ncl.($module) as ($module)"
} else {
$"import ($extension_type).($module).kcl.($module) as ($module)"
$"import ($extension_type).($module).ncl.($module) as ($module)"
}
# Get relative paths for comments
@ -719,7 +719,7 @@ description = \"Workspace-level ($extension_type) schemas\"
($import_stmt)
# TODO: Configure your ($module) instance
# See available schemas at: ($relative_schema_path)/kcl/
# See available schemas at: ($relative_schema_path)/nickel/
"
}
@ -727,15 +727,15 @@ description = \"Workspace-level ($extension_type) schemas\"
print $" ✓ Config created: ($config_file_path)"
print $" 📝 Edit ($extension_type)/($module).k to configure settings"
# STEP 3: Update infra kcl.mod
# STEP 3: Update infra nickel.mod
if ($workspace_abs | str contains "/infra/") {
let kcl_mod_path = ($workspace_abs | path join "kcl.mod")
if ($kcl_mod_path | path exists) {
let kcl_mod_content = (open $kcl_mod_path)
if not ($kcl_mod_content | str contains $"($extension_type) =") {
print $" 🔧 Updating kcl.mod to include ($extension_type) dependency"
let nickel_mod_path = ($workspace_abs | path join "nickel.mod")
if ($nickel_mod_path | path exists) {
let nickel_mod_content = (open $nickel_mod_path)
if not ($nickel_mod_content | str contains $"($extension_type) =") {
print $" 🔧 Updating nickel.mod to include ($extension_type) dependency"
let new_dependency = $"\n# Workspace-level ($extension_type) \(shared across infras\)\n($extension_type) = { path = \"../../.($extension_type)\" }\n"
$"($kcl_mod_content)($new_dependency)" | save -f $kcl_mod_path
$"($nickel_mod_content)($new_dependency)" | save -f $nickel_mod_path
}
}
}
@ -808,7 +808,7 @@ def print_enhanced_help [] {
print ""
print "CORE COMMANDS:"
print " discover <type> [query] [--format <fmt>] [--category <cat>] - Discover available modules"
print " sync-kcl <infra> [--manifest <file>] [--kcl] - Sync KCL dependencies for infrastructure"
print " sync <infra> [--manifest <file>] [--show-modules] - Sync Nickel dependencies for infrastructure"
print " load <type> <workspace> <modules...> [--layer <layer>] - Load modules into workspace"
print " list <type> <workspace> [--layer <layer>] - List loaded modules"
print " unload <type> <workspace> <module> [--layer <layer>] - Unload module from workspace"

View File

@ -41,8 +41,8 @@ function _install_tools {
# local jq_version
# local has_yq
# local yq_version
local has_kcl
local kcl_version
local has_nickel
local nickel_version
local has_tera
local tera_version
local has_k9s
@ -99,22 +99,20 @@ function _install_tools {
# printf "%s\t%s\n" "yq" "already $YQ_VERSION"
# fi
# fi
if [ -n "$KCL_VERSION" ] && [ "$match" == "all" ] || [ "$match" == "kcl" ] ; then
has_kcl=$(type -P kcl)
if [ -n "$NICKEL_VERSION" ] && [ "$match" == "all" ] || [ "$match" == "nickel" ] ; then
has_nickel=$(type -P nickel)
num_version="0"
[ -n "$has_kcl" ] && kcl_version=$(kcl -v | cut -f3 -d" " | sed 's/ //g') && num_version=${kcl_version//\./}
expected_version_num=${KCL_VERSION//\./}
[ -n "$has_nickel" ] && nickel_version=$(nickel --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) && num_version=${nickel_version//\./}
expected_version_num=${NICKEL_VERSION//\./}
if [ -z "$CHECK_ONLY" ] && [ "$num_version" -lt "$expected_version_num" ] ; then
curl -fsSLO "https://github.com/kcl-lang/cli/releases/download/v${KCL_VERSION}/kcl-v${KCL_VERSION}-${OS}-${ARCH}.tar.gz" &&
tar -xzf "kcl-v${KCL_VERSION}-${OS}-${ARCH}.tar.gz" &&
sudo mv kcl /usr/local/bin/kcl &&
rm -f "kcl-v${KCL_VERSION}-${OS}-${ARCH}.tar.gz" &&
printf "%s\t%s\n" "kcl" "installed $KCL_VERSION"
echo "⚠️ Nickel installation/update required"
echo " Recommended method: nix profile install nixpkgs#nickel"
echo " Alternative: cargo install nickel-lang-cli --version ${NICKEL_VERSION}"
echo " https://nickel-lang.org/getting-started"
elif [ -n "$CHECK_ONLY" ] ; then
printf "%s\t%s\t%s\n" "kcl" "$kcl_version" "expected $KCL_VERSION"
printf "%s\t%s\t%s\n" "nickel" "$nickel_version" "expected $NICKEL_VERSION"
else
printf "%s\t%s\n" "kcl" "already $KCL_VERSION"
printf "%s\t%s\n" "nickel" "already $NICKEL_VERSION"
fi
fi
if [ -n "$TERA_VERSION" ] && [ "$match" == "all" ] || [ "$match" == "tera" ] ; then

View File

@ -7,6 +7,9 @@
set +o errexit
set +o pipefail
# Debug: log startup
[ "$PROVISIONING_DEBUG_STARTUP" = "true" ] && echo "[DEBUG] Wrapper started with args: $@" >&2
export NU=$(type -P nu)
_release() {
@ -52,11 +55,12 @@ case "$1" in
# Note: "setup" is now handled by the main provisioning CLI dispatcher
# No special module handling needed
-mod)
export PROVISIONING_MODULE=$(echo "$2" | sed 's/ //g' | cut -f1 -d"|")
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" = "true" ] && echo "[DEBUG] -mod detected: MODULE=$PROVISIONING_MODULE, TASK=$PROVISIONING_MODULE_TASK, CMD_ARGS=$CMD_ARGS" >&2
;;
esac
NU_ARGS=""
@ -75,14 +79,545 @@ case "$(uname | tr '[:upper:]' '[:lower:]')" in
;;
esac
# FAST-PATH: Help commands and no-arguments case don't need full config loading
# Detect help-only commands and empty arguments, use minimal help system
# ════════════════════════════════════════════════════════════════════════════════
# 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)
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
# 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
[ ! -x "$PROVISIONING/core/nulib/provisioning setup" ] && echo "$PROVISIONING/core/nulib/provisioning setup not found" && exit 1
cd "$PROVISIONING/core/nulib"
@ -100,19 +635,50 @@ export PROVISIONING_ARGS="$CMD_ARGS" NU_ARGS="$NU_ARGS"
# 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"
# ============================================================================
# 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
if [[ -x $PROVISIONING/core/nulib/$RUNNER\ $PROVISIONING_MODULE ]] ; then
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/$RUNNER $PROVISIONING_MODULE" $PROVISIONING_MODULE_TASK $CMD_ARGS
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/$RUNNER $PROVISIONING_MODULE" $CMD_ARGS
else
echo "Error \"$PROVISIONING/core/nulib/$RUNNER $PROVISIONING_MODULE\" not found"
fi
else
# Only redirect stdin for non-interactive commands (nu command needs interactive stdin)
if [ "$1" = "nu" ]; then
# For interactive mode, ensure ENV variables are available
# For interactive mode, start nu with provisioning environment
export PROVISIONING_CONFIG="$PROVISIONING_USER_CONFIG"
$NU "${NU_ARGS[@]}" "$PROVISIONING/core/nulib/$RUNNER" $CMD_ARGS
# Start nu interactively - it will use the config and env from NU_ARGS
$NU "${NU_ARGS[@]}"
else
# 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
fi
fi

View File

@ -76,8 +76,8 @@ function _install_tools {
# local yq_version
local has_nu
local nu_version
local has_kcl
local kcl_version
local has_nickel
local nickel_version
local has_tera
local tera_version
local has_k9s
@ -148,22 +148,66 @@ function _install_tools {
printf "%s\t%s\n" "nu" "already $NU_VERSION"
fi
fi
if [ -n "$KCL_VERSION" ] && [ "$match" == "all" ] || [ "$match" == "kcl" ] ; then
has_kcl=$(type -P kcl)
if [ -n "$NICKEL_VERSION" ] && [ "$match" == "all" ] || [ "$match" == "nickel" ] ; then
has_nickel=$(type -P nickel)
num_version=0
[ -n "$has_kcl" ] && kcl_version=$(kcl -v | cut -f3 -d" " | sed 's/ //g') && num_version=${kcl_version//\./}
expected_version_num=${KCL_VERSION//\./}
[ -n "$has_nickel" ] && nickel_version=$((nickel -V | cut -f3 -d" ") 2>/dev/null) && num_version=${nickel_version//\./}
expected_version_num=${NICKEL_VERSION//\./}
[ -z "$num_version" ] && num_version=0
if [ -z "$CHECK_ONLY" ] && [ "$num_version" -lt "$expected_version_num" ] ; then
curl -fsSLO "https://github.com/kcl-lang/cli/releases/download/v${KCL_VERSION}/kcl-v${KCL_VERSION}-${OS}-${ARCH}.tar.gz" &&
tar -xzf "kcl-v${KCL_VERSION}-${OS}-${ARCH}.tar.gz" &&
sudo mv kcl /usr/local/bin/kcl &&
rm -f "kcl-v${KCL_VERSION}-${OS}-${ARCH}.tar.gz" &&
printf "%s\t%s\n" "kcl" "installed $KCL_VERSION"
elif [ -n "$CHECK_ONLY" ] ; then
printf "%s\t%s\t%s\n" "kcl" "$kcl_version" "expected $KCL_VERSION"
# macOS: try Cargo first, then Homebrew
if [ "$OS" == "darwin" ] ; then
printf "%s\t%s\n" "nickel" "installing $NICKEL_VERSION on macOS"
# Try Cargo first (if available)
if command -v cargo >/dev/null 2>&1 ; then
printf "%s\t%s\n" "nickel" "using Cargo (Rust compiler)"
if cargo install nickel-lang-cli --version "${NICKEL_VERSION}" ; then
printf "%s\t%s\n" "nickel" "✅ installed $NICKEL_VERSION via Cargo"
else
printf "%s\t%s\n" "kcl" "already $KCL_VERSION"
printf "%s\t%s\n" "nickel" "❌ Failed to build with Cargo"
exit 1
fi
# Try Homebrew if Cargo not available
elif command -v brew >/dev/null 2>&1 ; then
printf "%s\t%s\n" "nickel" "using Homebrew"
if brew install nickel ; then
printf "%s\t%s\n" "nickel" "✅ installed $NICKEL_VERSION via Homebrew"
else
printf "%s\t%s\n" "nickel" "❌ Failed to install with Homebrew"
exit 1
fi
else
# Neither Cargo nor Homebrew available
printf "%s\t%s\n" "nickel" "⚠️ Neither Cargo nor Homebrew found"
printf "%s\t%s\n" "nickel" "Install one of:"
printf "%s\t%s\n" "nickel" " 1. Cargo: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
printf "%s\t%s\n" "nickel" " 2. Homebrew: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
exit 1
fi
else
# Non-macOS: download binary from GitHub
printf "%s\t%s\n" "nickel" "installing $NICKEL_VERSION on $OS"
# Map architecture names (GitHub uses different naming)
local nickel_arch="$ARCH"
[ "$nickel_arch" == "amd64" ] && nickel_arch="x86_64"
# Build download URL
local download_url="https://github.com/tweag/nickel/releases/download/${NICKEL_VERSION}/nickel-${nickel_arch}-${OS}"
# Download and install
if curl -fsSLO "$download_url" && chmod +x "nickel-${nickel_arch}-${OS}" && sudo mv "nickel-${nickel_arch}-${OS}" /usr/local/bin/nickel ; then
printf "%s\t%s\n" "nickel" "installed $NICKEL_VERSION"
else
printf "%s\t%s\n" "nickel" "❌ Failed to download/install Nickel binary"
exit 1
fi
fi
elif [ -n "$CHECK_ONLY" ] ; then
printf "%s\t%s\t%s\n" "nickel" "$nickel_version" "expected $NICKEL_VERSION"
else
printf "%s\t%s\n" "nickel" "already $NICKEL_VERSION"
fi
fi
#if [ -n "$TERA_VERSION" ] && [ "$match" == "all" ] || [ "$match" == "tera" ] ; then
@ -263,8 +307,8 @@ function _detect_tool_version {
nu | nushell)
nu -v 2>/dev/null | head -1 || echo ""
;;
kcl)
kcl -v 2>/dev/null | grep "kcl version" | sed 's/.*version\s*//' || echo ""
nickel)
nickel --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo ""
;;
sops)
sops -v 2>/dev/null | head -1 | awk '{print $2}' || echo ""
@ -325,22 +369,22 @@ function _try_install_provider_tool {
local options=$2
local force_update=$3
# Look for the tool in provider kcl/version.k files (KCL is single source of truth)
# Look for the tool in provider nickel/version.ncl files (Nickel is single source of truth)
for prov in $(ls $PROVIDERS_PATH 2>/dev/null | grep -v "^_" )
do
if [ -r "$PROVIDERS_PATH/$prov/kcl/version.k" ] ; then
# Compile KCL file to JSON and extract version data (single source of truth)
local kcl_file="$PROVIDERS_PATH/$prov/kcl/version.k"
local kcl_output=""
if [ -r "$PROVIDERS_PATH/$prov/nickel/version.ncl" ] ; then
# Evaluate Nickel file to JSON and extract version data (single source of truth)
local nickel_file="$PROVIDERS_PATH/$prov/nickel/version.ncl"
local nickel_output=""
local tool_version=""
local tool_name=""
# Compile KCL to JSON and capture output
kcl_output=$(kcl run "$kcl_file" --format json 2>/dev/null)
# Evaluate Nickel to JSON and capture output
nickel_output=$(nickel export --format json "$nickel_file" 2>/dev/null)
# Extract tool name and version from JSON
tool_name=$(echo "$kcl_output" | grep -o '"name": "[^"]*"' | head -1 | sed 's/"name": "//;s/"$//')
tool_version=$(echo "$kcl_output" | grep -o '"current": "[^"]*"' | head -1 | sed 's/"current": "//;s/"$//')
tool_name=$(echo "$nickel_output" | grep -o '"name": "[^"]*"' | head -1 | sed 's/"name": "//;s/"$//')
tool_version=$(echo "$nickel_output" | grep -o '"current": "[^"]*"' | head -1 | sed 's/"current": "//;s/"$//')
# If this is the tool we're looking for
if [ "$tool_name" == "$tool" ] && [ -n "$tool_version" ] ; then
@ -357,7 +401,7 @@ function _try_install_provider_tool {
export UPCLOUD_UPCTL_VERSION="$tool_version"
$PROVIDERS_PATH/$prov/bin/install.sh "$tool_name" $options
elif [ "$prov" = "hetzner" ] ; then
# Hetzner expects: version as param (from kcl/version.k)
# Hetzner expects: version as param (from nickel/version.ncl)
$PROVIDERS_PATH/$prov/bin/install.sh "$tool_version" $options
elif [ "$prov" = "aws" ] ; then
# AWS format - set env var and pass tool name

View File

@ -1,469 +0,0 @@
# KMS Independent Configuration - Migration Summary
**Date:** 2025-10-06
**Version:** 1.0.0
**Status:** ✅ Complete
## Overview
Successfully created independent KMS (Key Management Service) configuration system supporting local and remote modes, completely decoupled from SOPS configuration.
## What Was Created
### 1. Directory Structure
```
/Users/Akasha/project-provisioning/provisioning/core/services/kms/
├── config.defaults.toml (6.7 KB) - System defaults
├── config.schema.toml (14 KB) - Validation rules
├── config.remote.example.toml (5.0 KB) - Remote KMS examples
├── config.local.example.toml (8.4 KB) - Local KMS examples
├── README.md (14 KB) - Comprehensive documentation
└── MIGRATION.md (this file) - Migration summary
```
### 2. Configuration Files
#### config.defaults.toml (270 lines)
Comprehensive default configuration covering:
- **Core Settings**: enabled, mode (local/remote/hybrid), version
- **Path Configuration**: All paths with interpolation support
- **Local KMS**: age, sops, vault providers
- **Remote KMS**: Server, auth, TLS, cache configuration
- **Hybrid Mode**: Fallback and sync settings
- **Policies**: Rotation, backup, audit logging
- **Encryption**: Algorithms and KDF configuration
- **Security**: Enforcement rules and secret scanning
- **Monitoring**: Health checks and metrics
- **Operations**: Verbose, debug, dry-run modes
**Key Features:**
- All paths use interpolation: `{{workspace.path}}`, `{{kms.paths.base}}`, `{{env.HOME}}`
- No hardcoded paths
- Secure defaults (TLS 1.3, 0600 permissions, no debug)
- Secret references only (no plaintext)
#### config.schema.toml (330 lines)
Validation schema defining:
- Type constraints for all fields
- Value ranges (timeouts, retries, sizes)
- Pattern matching (versions, ARNs, URLs)
- Enum validation (modes, algorithms, formats)
- 10 cross-field validation rules
**Validation Rules:**
1. Mode consistency (local/remote/hybrid)
2. Auth method required fields
3. Local provider configuration
4. Password secret format enforcement
5. TLS/mTLS consistency
6. Cache TTL bounds
7. Rotation interval requirements
8. Key permissions security
9. Debug mode warnings
10. Hybrid mode requirements
#### config.remote.example.toml (180 lines)
Remote KMS examples including:
- mTLS authentication (production)
- Token-based auth
- API key authentication
- Basic authentication
- IAM authentication (AWS)
- Deployment scenarios (prod, dev, CI/CD)
- Integration examples (AWS, Cosmian, Vault)
#### config.local.example.toml (290 lines)
Local KMS examples including:
- Age encryption (simple, multi-key, SSH-key)
- SOPS with age
- SOPS with cloud KMS (AWS, GCP, Azure)
- HashiCorp Vault Transit engine
- Development/testing setups
- High-security configurations
- Migration paths
### 3. Configuration Accessor Functions
Added **59 new accessor functions** to `/provisioning/core/nulib/lib_provisioning/config/accessor.nu`:
#### Core Settings (3)
- `get-kms-enabled`
- `get-kms-mode`
- `get-kms-version`
#### Path Accessors (4)
- `get-kms-base-path`
- `get-kms-keys-dir`
- `get-kms-cache-dir`
- `get-kms-config-dir`
#### Local Configuration (13)
- `get-kms-local-enabled`
- `get-kms-local-provider`
- `get-kms-local-key-path`
- `get-kms-local-sops-config`
- Age: `get-kms-age-generate-on-init`, `get-kms-age-key-format`, `get-kms-age-key-permissions`
- SOPS: `get-kms-sops-config-path`, `get-kms-sops-age-recipients`
- Vault: `get-kms-vault-address`, `get-kms-vault-token-path`, `get-kms-vault-transit-path`, `get-kms-vault-key-name`
#### Remote Configuration (19)
- `get-kms-remote-enabled`
- `get-kms-remote-endpoint`
- `get-kms-remote-api-version`
- `get-kms-remote-timeout`
- `get-kms-remote-retry-attempts`
- `get-kms-remote-retry-delay`
- Auth: `get-kms-remote-auth-method`, `get-kms-remote-token-path`, `get-kms-remote-refresh-token`, `get-kms-remote-token-expiry`
- TLS: `get-kms-remote-tls-enabled`, `get-kms-remote-tls-verify`, `get-kms-remote-ca-cert-path`, `get-kms-remote-client-cert-path`, `get-kms-remote-client-key-path`, `get-kms-remote-tls-min-version`
- Cache: `get-kms-remote-cache-enabled`, `get-kms-remote-cache-ttl`, `get-kms-remote-cache-max-size`
#### Hybrid Mode (3)
- `get-kms-hybrid-enabled`
- `get-kms-hybrid-fallback-to-local`
- `get-kms-hybrid-sync-keys`
#### Policies (6)
- `get-kms-auto-rotate`
- `get-kms-rotation-days`
- `get-kms-backup-enabled`
- `get-kms-backup-path`
- `get-kms-audit-log-enabled`
- `get-kms-audit-log-path`
#### Encryption & Security (6)
- `get-kms-encryption-algorithm`
- `get-kms-key-derivation`
- `get-kms-enforce-key-permissions`
- `get-kms-disallow-plaintext-secrets`
- `get-kms-secret-scanning-enabled`
- `get-kms-min-key-size-bits`
#### Operations (4)
- `get-kms-verbose`
- `get-kms-debug`
- `get-kms-dry-run`
- `get-kms-max-file-size-mb`
#### Helper Function (1)
- `get-kms-config-full` - Returns complete KMS config as record
**Total:** 69 KMS accessor functions (10 existing + 59 new)
### 4. Documentation
#### README.md (500+ lines)
Comprehensive documentation covering:
- Overview and directory structure
- Configuration file descriptions
- Path interpolation guide (6 variable types)
- **Security Considerations** (7 critical topics):
1. Key file permissions (0600/0400)
2. Secret references (no plaintext)
3. TLS/mTLS configuration
4. Audit logging
5. Debug mode warnings
6. Secret scanning
7. Key backup and rotation
- Operational modes (local, remote, hybrid)
- Authentication methods (5 types)
- Integration with existing lib.nu
- Validation rules
- Migration guide
- Best practices (dev, prod, HA)
- Troubleshooting
- Version compatibility
## Security Implementation
### 1. Path Interpolation
All paths support secure interpolation:
```toml
base = "{{workspace.path}}/.kms" # Workspace-relative
keys_dir = "{{kms.paths.base}}/keys" # Self-referential
token_path = "{{env.HOME}}/.kms/token" # Environment-based
```
**Benefits:**
- No hardcoded paths
- Portable configurations
- Dynamic workspace support
- Environment-aware
### 2. Secret References
**Never plaintext secrets!** Only references:
```toml
# ✅ Secure
password_secret = "sops://kms/remote/password"
api_key = "vault://kms/api_key"
# ❌ Insecure (blocked by validation)
password = "my-password"
```
**Supported Schemes:**
- `sops://` - SOPS encrypted
- `vault://` - HashiCorp Vault
- `kms://` - KMS encrypted
- `age://` - Age encrypted
### 3. Permission Enforcement
```toml
[kms.local.age]
key_permissions = "0600" # Owner read/write only
[kms.security]
enforce_key_permissions = true
disallow_plaintext_secrets = true
```
**Enforced Rules:**
- Keys must be 0600 or 0400
- Secrets must be references
- TLS 1.3+ for remote
- Certificate verification required
### 4. Audit and Monitoring
```toml
[kms.policies]
audit_log_enabled = true
audit_log_path = "{{kms.paths.base}}/audit.log"
audit_log_format = "json"
[kms.monitoring]
health_check_enabled = true
metrics_enabled = true
```
**Logged Events:**
- Encryption/decryption operations
- Key rotations
- Authentication attempts
- Configuration changes
## Changes to Existing Code
### Modified Files
#### 1. config/accessor.nu
**Location:** `/provisioning/core/nulib/lib_provisioning/config/accessor.nu`
**Changes:**
- Added 59 new KMS accessor functions (lines 739-1144)
- Added comprehensive documentation header
- Added helper function `get-kms-config-full`
- Total KMS functions: 69 (10 existing + 59 new)
**No Breaking Changes:**
- Existing functions preserved
- Backward compatible
- Additive only
### Existing KMS Library (lib.nu)
**Location:** `/provisioning/core/nulib/lib_provisioning/kms/lib.nu`
**Current State:**
- Uses old accessor functions (`get-kms-server`, etc.)
- Hardcoded to remote KMS (Cosmian)
- No local/hybrid mode support
**Recommended Updates:**
```nushell
# Update get_kms_config function to use new accessors:
def get_kms_config [] {
let mode = (get-kms-mode)
match $mode {
"local" => {
{
provider: (get-kms-local-provider)
key_path: (get-kms-local-key-path)
}
}
"remote" => {
{
endpoint: (get-kms-remote-endpoint)
auth_method: (get-kms-remote-auth-method)
# ... existing remote config
}
}
"hybrid" => {
# Both configs with fallback
}
}
}
```
**Note:** lib.nu was NOT modified in this task. Future task should update it to use new config.
## Integration Points
### 1. With SOPS
KMS config is now independent but still supports SOPS:
```toml
[kms.local]
provider = "sops"
sops_config = "{{workspace.path}}/.sops.yaml"
[kms.local.sops]
age_recipients = ["age1xxx..."]
```
### 2. With Workspace Config
KMS config loads from workspace:
```toml
[kms.paths]
base = "{{workspace.path}}/.kms"
```
### 3. With Provider Configs
Can integrate with cloud provider KMS:
```toml
[kms.local.sops]
aws_kms_arn = "arn:aws:kms:..."
gcp_kms_resource_id = "projects/..."
azure_keyvault_url = "https://..."
```
## Usage Examples
### Local Age Encryption
```nushell
# Configuration automatically loaded
let kms_config = (get-kms-config-full)
print $kms_config.local.key_path
# Output: /workspace/my-project/.kms/keys/age.txt
```
### Remote KMS with mTLS
```nushell
let endpoint = (get-kms-remote-endpoint)
let auth = (get-kms-remote-auth-method)
let tls_enabled = (get-kms-remote-tls-enabled)
print $"Connecting to ($endpoint) using ($auth)"
# Output: Connecting to https://kms.prod.example.com using mtls
```
### Hybrid Mode with Fallback
```nushell
let mode = (get-kms-mode)
let fallback = (get-kms-hybrid-fallback-to-local)
if $mode == "hybrid" and $fallback {
print "Hybrid mode with local fallback enabled"
}
```
## Testing Checklist
- [x] Config files created with correct structure
- [x] Schema validation rules defined
- [x] Path interpolation variables documented
- [x] Secret reference patterns enforced
- [x] Accessor functions added (59 new)
- [x] Security considerations documented
- [x] Example configurations provided
- [x] Migration guide included
- [x] README comprehensive
- [ ] lib.nu updated (future task)
- [ ] Integration tests added (future task)
- [ ] End-to-end testing (future task)
## Next Steps
### 1. Update lib.nu
Update `/provisioning/core/nulib/lib_provisioning/kms/lib.nu` to:
- Use new accessor functions
- Support all three modes (local/remote/hybrid)
- Implement local providers (age, sops, vault)
- Add fallback logic for hybrid mode
### 2. Integration Testing
- Test local age encryption
- Test SOPS integration
- Test remote KMS connection
- Test hybrid mode fallback
- Validate all accessor functions
### 3. Migration Path
- Update existing configurations
- Migrate from ENV to config
- Document breaking changes
- Provide migration scripts
### 4. Additional Features
- Key rotation automation
- Backup/restore procedures
- Monitoring dashboards
- Alerting integration
## Files Summary
| File | Size | Lines | Purpose |
|------|------|-------|---------|
| config.defaults.toml | 6.7 KB | 270 | System defaults |
| config.schema.toml | 14 KB | 330 | Validation rules |
| config.remote.example.toml | 5.0 KB | 180 | Remote examples |
| config.local.example.toml | 8.4 KB | 290 | Local examples |
| README.md | 14 KB | 500+ | Documentation |
| MIGRATION.md | - | - | This summary |
| **Total** | **48.1 KB** | **1570+** | Complete KMS config |
## Accessor Functions Summary
| Category | Count | Examples |
|----------|-------|----------|
| Core Settings | 3 | get-kms-enabled, get-kms-mode |
| Paths | 4 | get-kms-base-path, get-kms-keys-dir |
| Local Config | 13 | get-kms-local-provider, get-kms-age-* |
| Remote Config | 19 | get-kms-remote-endpoint, get-kms-remote-tls-* |
| Hybrid Mode | 3 | get-kms-hybrid-enabled |
| Policies | 6 | get-kms-auto-rotate, get-kms-backup-path |
| Security | 6 | get-kms-enforce-key-permissions |
| Operations | 4 | get-kms-verbose, get-kms-debug |
| Helper | 1 | get-kms-config-full |
| **Total New** | **59** | - |
| **Total KMS** | **69** | (10 existing + 59 new) |
## Security Guarantees
**No plaintext secrets** - All secrets use references
**No hardcoded paths** - All paths use interpolation
**Secure defaults** - TLS 1.3, 0600 permissions, no debug
**Validation enforced** - Schema validates all configs
**Audit logging** - All operations logged (when enabled)
**Key rotation** - Automated rotation support
**Permission checks** - Enforced key file permissions
**Secret scanning** - Pattern-based secret detection
## Conclusion
Successfully created a comprehensive, independent KMS configuration system with:
- **4 config files** (defaults, schema, 2 examples)
- **59 new accessor functions**
- **Comprehensive documentation** (README + migration guide)
- **Security-first design** (no plaintext, path interpolation, validation)
- **Three operational modes** (local, remote, hybrid)
- **Backward compatibility** (existing code unchanged)
The system is ready for:
1. Integration with existing lib.nu
2. Testing and validation
3. Production deployment
All requirements met. All paths use interpolation. All security considerations documented.

View File

@ -14,7 +14,7 @@ The KMS configuration system provides a comprehensive, independent configuration
## Directory Structure
```
```plaintext
provisioning/core/services/kms/
├── config.defaults.toml # System defaults for all KMS settings
├── config.schema.toml # Validation rules and constraints
@ -22,7 +22,7 @@ provisioning/core/services/kms/
├── config.local.example.toml # Local encryption examples
├── lib.nu # KMS library functions (existing)
└── README.md # This file
```
```plaintext
## Configuration Files
@ -31,6 +31,7 @@ provisioning/core/services/kms/
Primary configuration file containing all KMS settings with sensible defaults.
**Key Sections:**
- `[kms]` - Core settings (enabled, mode, version)
- `[kms.paths]` - Path configuration with interpolation support
- `[kms.local]` - Local encryption provider settings
@ -43,6 +44,7 @@ Primary configuration file containing all KMS settings with sensible defaults.
### 2. config.schema.toml
Validation schema defining:
- Type constraints for all fields
- Value ranges and patterns
- Cross-field validation rules
@ -87,7 +89,7 @@ token_path = "{{env.HOME}}/.config/provisioning/kms-token"
# Environment variable paths
[kms.local.vault]
token_path = "{{env.VAULT_TOKEN_PATH}}"
```
```plaintext
## Security Considerations
@ -101,9 +103,10 @@ key_permissions = "0600" # Read/write for owner only
[kms.security]
enforce_key_permissions = true # Enforces permission checks
```
```plaintext
**Best Practice:**
- Production keys: `0400` (read-only)
- Development keys: `0600` (read/write for owner)
- Never use: `0644`, `0755`, or world-readable permissions
@ -123,9 +126,10 @@ api_key = "vault://kms/api/key"
# ❌ WRONG - Plaintext secret
[kms.remote.auth]
password = "my-secret-password" # NEVER DO THIS!
```
```plaintext
**Supported Secret References:**
- `sops://path/to/secret` - SOPS encrypted secret
- `vault://path/to/secret` - HashiCorp Vault secret
- `kms://path/to/secret` - KMS-encrypted secret
@ -147,9 +151,10 @@ ca_cert_path = "/etc/kms/ca.crt"
method = "mtls"
client_cert_path = "/etc/kms/client.crt"
client_key_path = "/etc/kms/client.key"
```
```plaintext
**Security Rules:**
- Never disable TLS verification in production
- Use mTLS when available for mutual authentication
- Store certificates outside version control
@ -164,9 +169,10 @@ Enable audit logging for production environments:
audit_log_enabled = true
audit_log_path = "{{kms.paths.base}}/audit.log"
audit_log_format = "json"
```
```plaintext
**Logged Operations:**
- Encryption/decryption requests
- Key rotation events
- Authentication attempts
@ -180,9 +186,10 @@ audit_log_format = "json"
[kms.operations]
debug = false # Debug exposes sensitive data in logs!
verbose = false
```
```plaintext
Debug mode includes:
- Plaintext key material in logs
- Full request/response bodies
- Authentication credentials
@ -202,7 +209,7 @@ secret_patterns = [
"(?i)api[_-]?key\\s*=\\s*['\"]?[^'\"\\s]+",
"(?i)token\\s*=\\s*['\"]?[^'\"\\s]+",
]
```
```plaintext
### 7. Key Backup and Rotation
@ -215,9 +222,10 @@ rotation_days = 90 # Rotate every 90 days
backup_enabled = true
backup_path = "{{kms.paths.base}}/backups"
backup_retention_count = 5 # Keep last 5 backups
```
```plaintext
**Backup Best Practices:**
- Store backups in secure, encrypted storage
- Test restore procedures regularly
- Document key recovery process
@ -230,29 +238,34 @@ The KMS configuration is loaded via config accessor functions in `/provisioning/
### Available Accessor Functions
#### Core Settings
- `get-kms-enabled` - Check if KMS is enabled
- `get-kms-mode` - Get operating mode (local/remote/hybrid)
- `get-kms-version` - Get KMS config version
#### Path Accessors
- `get-kms-base-path` - Get base KMS directory
- `get-kms-keys-dir` - Get keys directory
- `get-kms-cache-dir` - Get cache directory
- `get-kms-config-dir` - Get config directory
#### Local Configuration
- `get-kms-local-enabled` - Check if local mode enabled
- `get-kms-local-provider` - Get provider (age/sops/vault)
- `get-kms-local-key-path` - Get key file path
- `get-kms-age-generate-on-init` - Check auto-generate setting
#### Remote Configuration
- `get-kms-remote-enabled` - Check if remote mode enabled
- `get-kms-remote-endpoint` - Get KMS server URL
- `get-kms-remote-auth-method` - Get auth method
- `get-kms-remote-timeout` - Get connection timeout
#### Full Config Helper
- `get-kms-config-full` - Get complete KMS config as record
### Usage Examples
@ -269,7 +282,7 @@ let kms_config = (get-kms-config-full)
# Get local key path with interpolation resolved
let key_path = (get-kms-local-key-path)
```
```plaintext
## Operational Modes
@ -278,17 +291,20 @@ let key_path = (get-kms-local-key-path)
Uses local encryption tools without external dependencies.
**Use Cases:**
- Development environments
- Offline operations
- Simple encryption needs
- No cloud KMS access
**Supported Providers:**
- **age** - Simple, modern encryption (recommended)
- **sops** - Secret Operations with multiple backends
- **vault** - HashiCorp Vault Transit engine
**Example:**
```toml
[kms]
enabled = true
@ -298,25 +314,28 @@ mode = "local"
enabled = true
provider = "age"
key_path = "{{kms.paths.keys_dir}}/age.txt"
```
```plaintext
### 2. Remote Mode
Connects to external KMS server for centralized key management.
**Use Cases:**
- Production environments
- Centralized key management
- Compliance requirements
- Multi-region deployments
**Supported Integrations:**
- Cosmian KMS
- AWS KMS
- HashiCorp Vault (remote)
- Custom KMS servers
**Example:**
```toml
[kms]
enabled = true
@ -330,19 +349,21 @@ endpoint = "https://kms.production.example.com"
method = "mtls"
client_cert_path = "/etc/kms/client.crt"
client_key_path = "/etc/kms/client.key"
```
```plaintext
### 3. Hybrid Mode
Combines local and remote with automatic fallback.
**Use Cases:**
- High availability requirements
- Gradual migration from local to remote
- Offline operation support
- Disaster recovery
**Example:**
```toml
[kms]
enabled = true
@ -360,20 +381,22 @@ endpoint = "https://kms.example.com"
enabled = true
fallback_to_local = true
sync_keys = false
```
```plaintext
## Authentication Methods
### Token-based Authentication
```toml
[kms.remote.auth]
method = "token"
token_path = "{{kms.paths.config_dir}}/token"
refresh_token = true
token_expiry_seconds = 3600
```
```plaintext
### mTLS (Mutual TLS)
```toml
[kms.remote.auth]
method = "mtls"
@ -382,44 +405,49 @@ client_key_path = "/etc/kms/client.key"
[kms.remote.tls]
ca_cert_path = "/etc/kms/ca.crt"
```
```plaintext
### API Key
```toml
[kms.remote.auth]
method = "api_key"
api_key = "sops://kms/api_key" # Secret reference!
```
```plaintext
### Basic Authentication
```toml
[kms.remote.auth]
method = "basic"
username = "provisioning"
password_secret = "vault://kms/password" # Secret reference!
```
```plaintext
### IAM (AWS)
```toml
[kms.remote.auth]
method = "iam"
iam_role_arn = "arn:aws:iam::123456789012:role/kms-role"
```
```plaintext
## Integration with Existing KMS Library
The existing KMS library (`lib.nu`) can be updated to use the new configuration:
### Current Implementation
```nushell
# Old: Hardcoded config lookup
def get_kms_config [] {
let server_url = (get-kms-server)
# ...
}
```
```plaintext
### Updated Implementation
```nushell
# New: Use new config accessors
def get_kms_config [] {
@ -445,7 +473,7 @@ def get_kms_config [] {
}
}
}
```
```plaintext
## Validation
@ -480,19 +508,21 @@ Configuration is validated against the schema:
### From Environment Variables to Config
**Before (ENV-based):**
```bash
export PROVISIONING_KMS_SERVER="https://kms.example.com"
export PROVISIONING_KMS_AUTH="certificate"
```
```plaintext
**After (Config-based):**
```toml
[kms.remote]
endpoint = "https://kms.example.com"
[kms.remote.auth]
method = "mtls"
```
```plaintext
### From SOPS to KMS Config
@ -505,11 +535,12 @@ sops_config = "{{workspace.path}}/.sops.yaml"
[kms.local.sops]
age_recipients = ["age1xxx...", "age1yyy..."]
```
```plaintext
## Best Practices
### 1. Development Environment
```toml
[kms]
mode = "local"
@ -525,9 +556,10 @@ debug = false # Never true, even in dev!
[kms.policies]
backup_enabled = false
audit_log_enabled = false
```
```plaintext
### 2. Production Environment
```toml
[kms]
mode = "remote"
@ -559,9 +591,10 @@ disallow_plaintext_secrets = true
[kms.operations]
verbose = false
debug = false
```
```plaintext
### 3. Hybrid/HA Environment
```toml
[kms]
mode = "hybrid"
@ -578,42 +611,48 @@ endpoint = "https://kms.example.com"
enabled = true
fallback_to_local = true
sync_keys = false
```
```plaintext
## Troubleshooting
### Issue: Permission Denied on Key File
**Error:**
```
```plaintext
Permission denied: /path/to/age.txt
```
```plaintext
**Solution:**
```bash
chmod 0600 /path/to/age.txt
```
```plaintext
Or update config:
```toml
[kms.local.age]
key_permissions = "0600"
[kms.security]
enforce_key_permissions = true
```
```plaintext
### Issue: Remote KMS Connection Failed
**Error:**
```
```plaintext
Connection timeout: https://kms.example.com
```
```plaintext
**Solutions:**
1. Check network connectivity
2. Verify TLS certificates
3. Increase timeout:
```toml
[kms.remote]
timeout_seconds = 60
@ -623,18 +662,20 @@ Connection timeout: https://kms.example.com
### Issue: Secret Reference Not Found
**Error:**
```
```plaintext
Secret not found: sops://kms/password
```
```plaintext
**Solution:**
1. Verify secret exists in SOPS/Vault
2. Check secret path format
3. Ensure SOPS/Vault is properly configured
## Version Compatibility
| KMS Config Version | Nushell Version | KCL Version | Notes |
| KMS Config Version | Nushell Version | Nickel Version | Notes |
|-------------------|-----------------|-------------|-------|
| 1.0.0 | 0.107.1+ | 0.11.3+ | Initial release |
@ -648,6 +689,7 @@ Secret not found: sops://kms/password
## Support
For issues or questions:
1. Check this README
2. Review example configurations
3. Consult validation schema

View File

@ -19,11 +19,12 @@ display_only = true
[items.warning_details]
type = "text"
prompt = "Cluster Deletion will:"
help = " Permanently delete all nodes in the cluster
help = """
Permanently delete all nodes in the cluster
Destroy all persistent volumes and data
Terminate all running applications and services
Remove all persistent configurations
Make cluster inaccessible - cannot be recovered"
Make cluster inaccessible - cannot be recovered"""
display_only = true
# ============================================================================

View File

@ -19,10 +19,11 @@ display_only = true
[items.warning_text]
type = "text"
prompt = "Server Deletion will:"
help = " Permanently remove the server from all providers
help = """
Permanently remove the server from all providers
Delete all associated data and configurations
Terminate all running services
Release allocated IP addresses and storage"
Release allocated IP addresses and storage"""
display_only = true
# ============================================================================

View File

@ -19,11 +19,12 @@ display_only = true
[items.warning_text]
type = "text"
prompt = "Task Service Deletion will:"
help = " Permanently remove the service definition
help = """
Permanently remove the service definition
Delete all containers and images
Remove all associated volumes and data
Terminate all running tasks
Invalidate all service references"
Invalidate all service references"""
display_only = true
# ============================================================================