890 lines
34 KiB
Text
890 lines
34 KiB
Text
# Platform Command Handlers
|
||
# Handles: platform status, config, health, start, connections, etc.
|
||
|
||
use cli/flags.nu *
|
||
use platform/mod.nu *
|
||
use platform/service_manager.nu [ncl-sync-start ncl-sync-stop ncl-sync-status]
|
||
use platform/user/config.nu [get-platform-config-dir write-provisioning-root]
|
||
|
||
# Main platform command dispatcher
|
||
export def handle_platform_command [
|
||
command: string
|
||
ops: string
|
||
flags: record
|
||
] {
|
||
# Parse subcommand from ops if present
|
||
let parts = ($ops | split row " " | where { |x| ($x | is-not-empty) })
|
||
let actual_command = if ($parts | length) > 0 { ($parts | get 0) } else { "" }
|
||
let remaining_args = if ($parts | length) > 1 { ($parts | skip 1) } else { [] }
|
||
|
||
match $actual_command {
|
||
"start" => { platform-start $remaining_args $flags }
|
||
"stop" => { platform-stop $remaining_args }
|
||
"restart" => { platform-restart $remaining_args $flags }
|
||
"status" | "st" => { platform-status $remaining_args }
|
||
"external" | "ext" => { platform-external }
|
||
"health" => { platform-health }
|
||
"check" => { platform-check }
|
||
"list" => { platform-list }
|
||
"config" => { platform-config }
|
||
"connections" => { platform-connections }
|
||
"init" => { platform-init }
|
||
"logs" | "log" => { platform-logs $remaining_args }
|
||
"help" | "" => { show-platform-help }
|
||
_ => {
|
||
print $"❌ Unknown platform command: ($actual_command)"
|
||
print ""
|
||
show-platform-help
|
||
exit 1
|
||
}
|
||
}
|
||
}
|
||
|
||
# ============================================================================
|
||
# Platform Command Implementations
|
||
# ============================================================================
|
||
|
||
def platform-start [args: list<string>, flags: record] {
|
||
# Persist provisioning root so Rust binaries can resolve schema imports
|
||
# without requiring $PROVISIONING in their environment.
|
||
write-provisioning-root
|
||
|
||
# Known deployment modes
|
||
let known_modes = ["local" "docker" "kubernetes"]
|
||
|
||
# Determine if first arg is a mode or service name
|
||
let is_mode_spec = (
|
||
if ($args | length) > 0 {
|
||
let first_arg = $args | get 0
|
||
$known_modes | any { |m| $m == $first_arg }
|
||
} else {
|
||
false
|
||
}
|
||
)
|
||
|
||
# If first arg is NOT a mode, treat all args as service names
|
||
if (not $is_mode_spec) and ($args | length) > 0 {
|
||
# ncl-sync doesn't follow the provisioning-* binary convention — handle separately.
|
||
let has_ncl_sync = ($args | any {|s| $s == "ncl-sync" or $s == "ncl_sync"})
|
||
if $has_ncl_sync {
|
||
ncl-sync-start
|
||
let rest = ($args | where {|s| $s != "ncl-sync" and $s != "ncl_sync"})
|
||
if ($rest | is-not-empty) { start-services $rest }
|
||
} else {
|
||
start-services $args
|
||
}
|
||
print ""
|
||
platform-status
|
||
return
|
||
}
|
||
|
||
# Otherwise, determine mode: from argument or from deployment-mode.ncl
|
||
let mode = (
|
||
if $is_mode_spec {
|
||
$args | get 0
|
||
} else {
|
||
# Read mode from deployment-mode.ncl
|
||
let deployment = (load-deployment-mode)
|
||
$deployment.mode
|
||
}
|
||
)
|
||
match $mode {
|
||
"local" => {
|
||
# Use configuration from deployment-mode.ncl to determine which services to start
|
||
start-required-services
|
||
|
||
# Show status table after start
|
||
sleep 3sec
|
||
print ""
|
||
platform-status
|
||
return
|
||
# Define service registry with correct binary names and startup args
|
||
# Note: Some services have known issues and are marked as experimental
|
||
let services_registry = {
|
||
"vault-service": {port: 8081, binary: "provisioning-vault-service", protocol: "gRPC", args: "--port", status: "stable"}
|
||
"catalog-registry": {port: 8082, binary: "provisioning-catalog-registry", protocol: "HTTP", args: "--port --host 127.0.0.1", status: "experimental"}
|
||
"control-center": {port: 8000, binary: "provisioning-control-center", protocol: "HTTP/WebSocket", args: "--port", status: "stable"}
|
||
"provisioning-rag": {port: 8300, binary: "provisioning-rag", protocol: "REST", args: "--mode solo", status: "stable"}
|
||
"ai-service": {port: 8083, binary: "provisioning-ai-service", protocol: "HTTP", args: "--port --host 127.0.0.1 --mode solo", status: "stable"}
|
||
"mcp-server": {port: 8400, binary: "provisioning-mcp-server", protocol: "Binary", args: "", status: "experimental"}
|
||
"provisioning-nu-daemon": {port: 8100, binary: "provisioning-nu-daemon", protocol: "gRPC", args: "", status: "stable"}
|
||
"orchestrator": {port: 9090, binary: "provisioning-orchestrator", protocol: "HTTP", args: "--port", status: "stable"}
|
||
"detector": {port: 8600, binary: "provisioning-detector", protocol: "HTTP", args: "", status: "experimental"}
|
||
"control-center-ui": {port: 3000, binary: "provisioning-control-center-ui", protocol: "HTTP (WASM)", args: "", status: "missing"}
|
||
}
|
||
|
||
let service_groups = {
|
||
"core": ["orchestrator"]
|
||
"essential": ["vault-service", "provisioning-nu-daemon", "orchestrator"]
|
||
"stable": ["vault-service", "provisioning-rag", "ai-service", "provisioning-nu-daemon", "orchestrator", "control-center"]
|
||
"experimental": ["catalog-registry", "mcp-server", "detector"]
|
||
"all": ["vault-service", "provisioning-rag", "ai-service", "catalog-registry", "mcp-server", "provisioning-nu-daemon", "orchestrator", "control-center", "detector"]
|
||
}
|
||
|
||
# Determine which services to start
|
||
let services_set = ($flags | get --optional services | default "core")
|
||
let services_to_start = (
|
||
if $services_set == "all" {
|
||
$service_groups.all
|
||
} else if $services_set == "essential" {
|
||
$service_groups.essential
|
||
} else if $services_set == "stable" {
|
||
$service_groups.stable
|
||
} else if $services_set == "experimental" {
|
||
$service_groups.experimental
|
||
} else if $services_set == "custom" {
|
||
# TODO: Handle custom services from args
|
||
$service_groups.core
|
||
} else {
|
||
$service_groups.core
|
||
}
|
||
)
|
||
|
||
# Display startup information
|
||
print ""
|
||
print "🚀 Starting Platform Services (Local Binary Mode)"
|
||
print "═══════════════════════════════════════════════════"
|
||
print ""
|
||
print $"📋 Service Set: ($services_set)"
|
||
print $"🔄 Services to start: ($services_to_start | length)"
|
||
print ""
|
||
print "Service Sets Available:"
|
||
print " • core (default): Minimal working setup - orchestrator only"
|
||
print " • essential: Recommended minimum - vault-service, daemon, orchestrator"
|
||
print " • stable: All production-ready services"
|
||
print " • experimental: Experimental/testing services"
|
||
print " • all: All platform services (including experimental)"
|
||
print ""
|
||
|
||
# Create logs directory
|
||
let log_dir = $"($env.HOME? | default "~")/.provisioning/logs"
|
||
(do { mkdir ($log_dir | path expand) } | ignore)
|
||
|
||
# Start each service
|
||
let started_count = ($services_to_start | length)
|
||
let max_index = (($services_to_start | length) - 1)
|
||
let started_indices = (0..$max_index)
|
||
|
||
for i in $started_indices {
|
||
let service_name = $services_to_start | get $i
|
||
let service_info = $services_registry | get $service_name
|
||
let port = $service_info.port
|
||
let binary = $service_info.binary
|
||
let protocol = $service_info.protocol
|
||
let args_template = $service_info.args
|
||
let log_file = $"($log_dir | path expand)/($service_name).log"
|
||
|
||
let index_num = ($i + 1)
|
||
print $" [($index_num)] Starting ($service_name) on port ($port) — ($protocol)"
|
||
|
||
# Initialize vault service if needed
|
||
if $service_name == "vault-service" {
|
||
let local_bin_dir = ($env.HOME? | default "~" | path expand | path join ".local/bin")
|
||
let init_script = ($local_bin_dir | path join "provisioning-init-vault")
|
||
if ($init_script | path exists) {
|
||
(^bash $init_script out+err> /dev/null | ignore)
|
||
} else {
|
||
print $" ⚠️ Vault initialization script not found"
|
||
}
|
||
}
|
||
|
||
# Check if binary exists in $HOME/.local/bin
|
||
let local_bin_dir = ($env.HOME? | default "~" | path expand | path join ".local/bin")
|
||
let binary_path = ($local_bin_dir | path join $binary)
|
||
|
||
if not ($binary_path | path exists) {
|
||
print $" ✗ Binary not found: ($binary_path)"
|
||
print $" ℹ Install with: just install"
|
||
} else {
|
||
# Build command with appropriate arguments
|
||
let cmd_args = (
|
||
if ($args_template | str contains "--port") {
|
||
# Replace --port placeholder with actual port
|
||
$args_template | str replace "--port" $"--port ($port)"
|
||
} else if ($args_template | is-not-empty) {
|
||
$args_template
|
||
} else {
|
||
""
|
||
}
|
||
)
|
||
|
||
# Add config if available
|
||
let config_args = ($service_info | get --optional config | default "")
|
||
let full_args = if ($config_args | is-not-empty) {
|
||
$"($cmd_args) ($config_args)"
|
||
} else {
|
||
$cmd_args
|
||
} | str trim
|
||
|
||
# Start the service in background using nohup
|
||
# This properly detaches the process from the terminal
|
||
# Set up environment variables for specific services
|
||
let home_expanded = ($env.HOME? | default "~" | path expand)
|
||
let env_vars = if $service_name == "vault-service" {
|
||
# Use development mode with Age KMS (can switch to secretumvault in production)
|
||
$"PROVISIONING_ENV=dev AGE_PUBLIC_KEY_PATH=($home_expanded)/.config/provisioning/age/public_key.txt AGE_PRIVATE_KEY_PATH=($home_expanded)/.config/provisioning/age/private_key.txt"
|
||
} else if $service_name == "control-center" {
|
||
$"PROVISIONING_USER_PLATFORM=($home_expanded)/.config/provisioning/platform"
|
||
} else if $service_name == "mcp-server" {
|
||
# MCP server needs provisioning path set
|
||
$"PROVISIONING_PATH=($home_expanded)/Development/provisioning"
|
||
} else {
|
||
""
|
||
}
|
||
|
||
let start_cmd = if ($env_vars | is-not-empty) {
|
||
$"nohup env ($env_vars) ($binary_path) ($full_args) >>($log_file) 2>&1 &"
|
||
} else {
|
||
$"nohup ($binary_path) ($full_args) >>($log_file) 2>&1 &"
|
||
}
|
||
(^sh -c $start_cmd | ignore)
|
||
sleep 800ms
|
||
let log_msg = $"logs: ($log_file)"
|
||
print $" ✓ Started on port ($port) ($log_msg)"
|
||
print $" Process may be initializing..."
|
||
}
|
||
}
|
||
|
||
print ""
|
||
print "Service Status:"
|
||
print $" • Requested to start: ($started_count)"
|
||
print $" • Logs directory: ($log_dir)"
|
||
print ""
|
||
|
||
# Show status table after start
|
||
sleep 1sec
|
||
print ""
|
||
platform-status
|
||
|
||
print "Next steps:"
|
||
print " tail -f $($log_dir)/*.log # Monitor logs in real-time"
|
||
print " provisioning platform health # Health checks"
|
||
print " provisioning platform stop # Stop services"
|
||
print ""
|
||
print "Note: Services may take time to initialize. Check logs for startup details."
|
||
}
|
||
"docker" => {
|
||
print "🐳 Docker Compose Mode"
|
||
print " • Start services via docker-compose"
|
||
print " • Uses docker-compose.yml from deployment config"
|
||
print ""
|
||
|
||
let provisioning_path = ($env.PROVISIONING? | default "/usr/local/provisioning")
|
||
let docker_compose_file = $"($provisioning_path)/platform/docker-compose.yml"
|
||
|
||
if ($docker_compose_file | path exists) {
|
||
print "Starting with docker-compose..."
|
||
(^docker-compose -f $docker_compose_file up -d out+err> /dev/null | ignore)
|
||
print "✓ Services started"
|
||
} else {
|
||
print $"⚠ docker-compose.yml not found at ($docker_compose_file)"
|
||
print " Create it with: provisioning generate docker-compose"
|
||
}
|
||
}
|
||
"kubernetes" => {
|
||
print "☸️ Kubernetes Mode"
|
||
print " • Deploy to Kubernetes cluster"
|
||
print " • Uses kubectl and manifests"
|
||
print ""
|
||
print "TODO: Implement Kubernetes deployment"
|
||
}
|
||
_ => {
|
||
print $"❌ Unknown mode: ($mode)"
|
||
print ""
|
||
print "Available modes: local, docker, kubernetes"
|
||
}
|
||
}
|
||
}
|
||
|
||
def platform-stop [args: list<string>] {
|
||
print ""
|
||
|
||
# If service names are provided, stop only those services
|
||
if ($args | length) > 0 {
|
||
# ncl-sync doesn't follow the provisioning-* binary convention — handle separately.
|
||
let has_ncl_sync = ($args | any {|s| $s == "ncl-sync" or $s == "ncl_sync"})
|
||
if $has_ncl_sync {
|
||
ncl-sync-stop
|
||
print "✓ ncl-sync stopped"
|
||
let rest = ($args | where {|s| $s != "ncl-sync" and $s != "ncl_sync"})
|
||
if ($rest | is-not-empty) { stop-services $rest }
|
||
} else {
|
||
stop-services $args
|
||
}
|
||
platform-status
|
||
} else {
|
||
# No service specified - stop all
|
||
print "🛑 Stopping All Platform Services"
|
||
print "═════════════════════════════════"
|
||
print ""
|
||
|
||
# Kill all provisioning service binaries (excluding this CLI)
|
||
(^sh -c "pkill -f 'provisioning-[a-z]' 2>/dev/null || true") | ignore
|
||
ncl-sync-stop
|
||
sleep 500ms
|
||
print "✓ All services stopped"
|
||
print ""
|
||
|
||
# Show updated status
|
||
platform-status
|
||
}
|
||
}
|
||
|
||
def platform-restart [args: list<string>, flags: record] {
|
||
print ""
|
||
|
||
# If a service name is provided, restart only that service
|
||
if ($args | length) > 0 {
|
||
let service_name = $args | get 0
|
||
|
||
# ncl-sync doesn't follow the provisioning-* binary convention — handle separately.
|
||
if $service_name == "ncl-sync" or $service_name == "ncl_sync" {
|
||
ncl-sync-stop
|
||
sleep 500ms
|
||
ncl-sync-start
|
||
print ""
|
||
platform-status
|
||
return
|
||
}
|
||
|
||
# Normalize: strip "provisioning-" prefix so both "daemon" and "provisioning-daemon" work
|
||
let svc_short = ($service_name | str replace "_" "-"
|
||
| if ($in | str starts-with "provisioning-") { str replace "provisioning-" "" } else { $in })
|
||
let binary_name = $"provisioning-($svc_short)"
|
||
let port = (get-service-port $svc_short)
|
||
|
||
# Stop the service
|
||
(^sh -c $"pkill -f '($binary_name)' 2>/dev/null || true") | ignore
|
||
sleep 1sec
|
||
|
||
# Start the service
|
||
print $"→ Starting ($service_name)..."
|
||
|
||
let home = ($env.HOME? | default "~" | path expand)
|
||
let canonical_path = ($home | path join ".local/share/provisioning/bin" $binary_name)
|
||
let binary_path = if ($canonical_path | path exists) { $canonical_path } else { $home | path join ".local/bin" $binary_name }
|
||
|
||
if not ($binary_path | path exists) {
|
||
print $"✗ Binary not found: ($binary_path)"
|
||
return
|
||
}
|
||
|
||
let log_dir = ($home | path join ".provisioning/logs")
|
||
(do { mkdir ($log_dir) } | ignore)
|
||
let log_file = ($log_dir | path join $"($service_name).log")
|
||
|
||
# Set environment variables for service
|
||
let platform_path = (get-platform-config-dir)
|
||
|
||
# Properly quote environment variables for shell execution
|
||
let start_cmd = $"nohup env PROVISIONING_USER_PLATFORM=\"($platform_path)\" PROVISIONING_CONFIG_DIR=\"($platform_path)\" ($binary_path) >>\"($log_file)\" 2>&1 &"
|
||
(^sh -c $start_cmd | ignore)
|
||
sleep 2sec
|
||
|
||
let started_msg = $"((ansi green))started((ansi reset))"
|
||
print $"✓ ($service_name) on port ($port) — ($started_msg)"
|
||
print ""
|
||
|
||
# Show updated status
|
||
platform-status
|
||
} else {
|
||
# No service specified - restart all
|
||
print "🔄 Restarting All Platform Services"
|
||
print "═════════════════════════════════"
|
||
print ""
|
||
|
||
# Stop all
|
||
(^sh -c "pkill -f 'provisioning-[a-z]' 2>/dev/null || true") | ignore
|
||
print "✓ All services stopped"
|
||
|
||
# Wait
|
||
sleep 2sec
|
||
|
||
# Start all
|
||
start-required-services
|
||
|
||
# Wait for services to initialize
|
||
sleep 1sec
|
||
|
||
# Show updated status
|
||
platform-status
|
||
}
|
||
}
|
||
|
||
def platform-status [args: list<string> = []] {
|
||
let json_out = ("--json" in $args)
|
||
let clean_args = ($args | where { |a| $a != "--json" })
|
||
let filter = ($clean_args | get 0? | default null)
|
||
let filter_key = if $filter != null { $filter | str replace --all "-" "_" } else { null }
|
||
|
||
let deployment = (load-deployment-mode)
|
||
let all_services = (
|
||
if ($deployment | get --optional "services") != null { $deployment.services | columns } else { [] }
|
||
)
|
||
|
||
if ($all_services | length) == 0 and not $json_out {
|
||
print "⚠ No services found in deployment configuration"
|
||
return
|
||
}
|
||
|
||
# ── Platform services ────────────────────────────────────────────────────────
|
||
let plat_data = $all_services | each { |service_name|
|
||
let config = $deployment.services | get $service_name
|
||
let enabled = ($config | get "enabled" | default false)
|
||
let port = (get-service-port $service_name)
|
||
let port_num = (if $port != "?" { $port | into int } else { 0 })
|
||
let running = (
|
||
if $port_num > 0 { is-port-listening $port_num }
|
||
else {
|
||
let bin = $"provisioning-(normalize-service-name $service_name | str replace '_' '-')"
|
||
(do { ^pgrep -f $bin } | complete).exit_code == 0
|
||
}
|
||
)
|
||
{ kind: "platform", name: $service_name, port: $port, enabled: $enabled, running: $running, required: $enabled, url: "" }
|
||
}
|
||
|
||
let ncs = (ncl-sync-status)
|
||
let plat_with_ncs = ($plat_data | append {
|
||
kind: "platform", name: "ncl_sync", port: "—", enabled: true, running: $ncs.running, required: false, url: ""
|
||
})
|
||
|
||
# ── External services ────────────────────────────────────────────────────────
|
||
let ext_data = (get-external-services) | each { |svc|
|
||
let running = (is-port-listening $svc.port)
|
||
{ kind: "external", name: $svc.name, port: ($svc.port | into string), enabled: true, running: $running, required: ($svc.required? | default false), url: $svc.url }
|
||
}
|
||
|
||
let all_data = ($plat_with_ncs | append $ext_data)
|
||
|
||
# ── Filter by service name ───────────────────────────────────────────────────
|
||
let display_data = if $filter_key != null {
|
||
let matched = ($all_data | where { |r| ($r.name | str replace --all "-" "_") == $filter_key })
|
||
if ($matched | is-empty) {
|
||
if $json_out { print ({error: $"unknown service: ($filter)"} | to json); return }
|
||
print $"❌ Unknown service: ($filter)"; return
|
||
}
|
||
$matched
|
||
} else {
|
||
$all_data
|
||
}
|
||
|
||
# ── JSON output ──────────────────────────────────────────────────────────────
|
||
if $json_out {
|
||
print ($display_data | to json)
|
||
return
|
||
}
|
||
|
||
# ── Human output ─────────────────────────────────────────────────────────────
|
||
let table_rows = $display_data | each { |r|
|
||
let status_str = if $r.running {
|
||
let label = if $r.enabled { "running" } else { "running*" }
|
||
$"((ansi green))($label)((ansi reset))"
|
||
} else {
|
||
let label = if $r.enabled { "stopped" } else { "disabled" }
|
||
if $r.enabled { $"((ansi red))($label)((ansi reset))" } else { $"((ansi dark_gray))($label)((ansi reset))" }
|
||
}
|
||
let req_str = if $r.required { $"((ansi yellow))req((ansi reset))" } else { "opt" }
|
||
{ Kind: $r.kind, Service: $r.name, Status: $status_str, Port: $r.port, Req: $req_str }
|
||
}
|
||
|
||
print ""
|
||
if $filter_key != null {
|
||
print $"📊 Status: ($filter)"
|
||
} else {
|
||
print "📊 Platform + External Services"
|
||
print "════════════════════════════════"
|
||
}
|
||
print ""
|
||
print ($table_rows | table -i false)
|
||
print ""
|
||
|
||
if $filter_key == null {
|
||
let running = ($display_data | where running | length)
|
||
let req_down = ($display_data | where { |r| $r.required and (not $r.running) } | length)
|
||
print $" ✓ Running: ($running) / ($display_data | length)"
|
||
if $req_down > 0 { print $" ❌ Required down: ($req_down) — run: prvng plat start" }
|
||
print ""
|
||
print "Legend: req=required opt=optional *=running but disabled in config"
|
||
}
|
||
}
|
||
|
||
def platform-external [] {
|
||
print ""
|
||
print "🔧 External Services (Infrastructure)"
|
||
print "════════════════════════════════════"
|
||
print ""
|
||
|
||
let external_services = (get-external-services)
|
||
|
||
if ($external_services | length) == 0 {
|
||
print "No external services configured"
|
||
return
|
||
}
|
||
|
||
# Build external services table
|
||
let external_data = [
|
||
...($external_services | each { |service|
|
||
let name = ($service | get "name")
|
||
let url = ($service | get "url")
|
||
let port = ($service | get "port")
|
||
let required = ($service | get "required" | default false)
|
||
let dependencies = ($service | get "dependencies" | default [] | str join ", ")
|
||
|
||
# Check if service is running by testing if port is listening
|
||
let is_running = (is-port-listening $port)
|
||
|
||
let status = (
|
||
if $is_running {
|
||
$"((ansi green))running((ansi reset))"
|
||
} else {
|
||
$"((ansi red))stopped((ansi reset))"
|
||
}
|
||
)
|
||
|
||
let required_display = (
|
||
if $required {
|
||
$"((ansi red))required((ansi reset))"
|
||
} else {
|
||
$"((ansi dark_gray))optional((ansi reset))"
|
||
}
|
||
)
|
||
|
||
{
|
||
Service: $name,
|
||
URL: $url,
|
||
Port: $port,
|
||
Status: $status,
|
||
Dependencies: $dependencies,
|
||
Required: $required_display
|
||
}
|
||
})
|
||
]
|
||
|
||
# Display external services table
|
||
print ($external_data | table -i false)
|
||
|
||
print ""
|
||
let external_running = (
|
||
$external_data
|
||
| where { |row| ($row.Status | str contains "running") }
|
||
| length
|
||
)
|
||
let external_total = ($external_data | length)
|
||
let external_stopped = ($external_total - $external_running)
|
||
|
||
print "Summary:"
|
||
print $" ✓ Running: ($external_running)"
|
||
print $" ✗ Stopped: ($external_stopped)"
|
||
print ""
|
||
print "Note: External services are monitored only. Use system commands to manage them."
|
||
}
|
||
|
||
def platform-health [] {
|
||
print ""
|
||
print "💚 Platform Services Health Check"
|
||
print "═════════════════════════════════"
|
||
print ""
|
||
|
||
let deployment = (load-deployment-mode)
|
||
let all_services = $deployment.services | columns
|
||
mut healthy_count = 0
|
||
mut critical_services = []
|
||
|
||
for service_name in $all_services {
|
||
let config = $deployment.services | get $service_name
|
||
let enabled = ($config | get "enabled" | default false)
|
||
|
||
# Load individual service config to get the actual port
|
||
let port = (get-service-port $service_name)
|
||
|
||
let binary_name = $"provisioning-($service_name | str replace "_" "-")"
|
||
|
||
let is_running = ((^pgrep -f $"[/]($binary_name)$" | complete).exit_code == 0)
|
||
|
||
if $is_running {
|
||
print $" ✓ ($service_name) — healthy on port ($port)"
|
||
$healthy_count = ($healthy_count + 1)
|
||
} else if $enabled {
|
||
print $" ✗ ($service_name) — CRITICAL \(enabled but not running\)"
|
||
$critical_services = ($critical_services | append $service_name)
|
||
} else {
|
||
print $" ⊘ ($service_name) — disabled"
|
||
}
|
||
}
|
||
|
||
print ""
|
||
print "Health Summary:"
|
||
let total = ($all_services | length)
|
||
let critical_count = ($critical_services | length)
|
||
print $" ✓ Running: ($healthy_count) / ($total)"
|
||
print $" ⚠️ Critical: ($critical_count)"
|
||
|
||
if ($critical_count == 0) {
|
||
print $" Status: ✅ All enabled services are running"
|
||
} else {
|
||
print $" Status: ⚠️ Missing critical services:"
|
||
for svc in $critical_services {
|
||
print $" - ($svc)"
|
||
}
|
||
}
|
||
|
||
print ""
|
||
}
|
||
|
||
def platform-list [] {
|
||
print ""
|
||
print "📋 Available Platform Services"
|
||
print "═════════════════════════════"
|
||
print ""
|
||
|
||
let services_info = [
|
||
{name: "vault-service", port: 8081, protocol: "gRPC", deps: "none"}
|
||
{name: "catalog-registry", port: 8082, protocol: "HTTP", deps: "none"}
|
||
{name: "control-center", port: 8000, protocol: "HTTP/WebSocket", deps: "vault-service"}
|
||
{name: "provisioning-rag", port: 8300, protocol: "REST", deps: "none"}
|
||
{name: "ai-service", port: 8083, protocol: "HTTP", deps: "provisioning-rag, vault-service"}
|
||
{name: "mcp-server", port: 8400, protocol: "Binary", deps: "vault-service"}
|
||
{name: "provisioning-nu-daemon", port: 8100, protocol: "gRPC", deps: "vault-service"}
|
||
{name: "orchestrator", port: 9090, protocol: "HTTP", deps: "catalog-registry, control-center, ai-service"}
|
||
{name: "detector", port: 8600, protocol: "HTTP", deps: "vault-service"}
|
||
{name: "control-center-ui", port: 3000, protocol: "HTTP (WASM)", deps: "control-center"}
|
||
]
|
||
|
||
for svc in $services_info {
|
||
print $" • ($svc.name)"
|
||
print $" Port: ($svc.port), Protocol: ($svc.protocol)"
|
||
print $" Dependencies: ($svc.deps)"
|
||
print ""
|
||
}
|
||
|
||
print "Total: 10 services"
|
||
print ""
|
||
}
|
||
|
||
def platform-config [] {
|
||
print ""
|
||
print "⚙️ Platform Configuration"
|
||
print "════════════════════════"
|
||
print ""
|
||
|
||
let platform_base = ($env.PROVISIONING_USER_PLATFORM? | default "~/.config/provisioning/platform")
|
||
print $"Platform Directory: ($platform_base)"
|
||
print ""
|
||
|
||
print "Configuration Files:"
|
||
print $" • ($platform_base)/deployment-mode.ncl"
|
||
print $" • ($platform_base)/config/control-center.ncl"
|
||
print $" • ($platform_base)/config/orchestrator.ncl"
|
||
print ""
|
||
}
|
||
|
||
def platform-connections [] {
|
||
print ""
|
||
print "🔗 Platform Service Connections"
|
||
print "════════════════════════════════"
|
||
print ""
|
||
|
||
print "Service Dependency Graph:"
|
||
print " vault-service"
|
||
print " ↓"
|
||
print " ├─ control-center"
|
||
print " │ ↓"
|
||
print " │ orchestrator"
|
||
print " │"
|
||
print " ├─ ai-service"
|
||
print " │ ↓"
|
||
print " │ orchestrator"
|
||
print " │"
|
||
print " ├─ mcp-server"
|
||
print " └─ provisioning-nu-daemon"
|
||
print ""
|
||
|
||
print "Service Network Endpoints:"
|
||
print " • vault-service: grpc://localhost:8081"
|
||
print " • catalog-registry: http://localhost:8082"
|
||
print " • control-center: http://localhost:8000"
|
||
print " • provisioning-rag: http://localhost:8300"
|
||
print " • ai-service: http://localhost:8083"
|
||
print " • mcp-server: grpc://localhost:8400"
|
||
print " • provisioning-nu-daemon: grpc://localhost:8100"
|
||
print " • orchestrator: http://localhost:9011"
|
||
print " • detector: http://localhost:8600"
|
||
print " • control-center-ui: http://localhost:3000"
|
||
print ""
|
||
}
|
||
|
||
def platform-init [] {
|
||
print ""
|
||
print "🔧 Platform Initialization"
|
||
print "═════════════════════════"
|
||
print ""
|
||
|
||
let platform_base = ($env.PROVISIONING_USER_PLATFORM? | default "~/.config/provisioning/platform")
|
||
print $"Platform Directory: ($platform_base)"
|
||
print ""
|
||
|
||
if ($"($platform_base)" | path exists) {
|
||
print "✓ Platform directory exists"
|
||
} else {
|
||
print "⚠ Platform directory not found"
|
||
print " Run: setup-platform-config.sh to initialize"
|
||
}
|
||
|
||
print ""
|
||
print "Platform is ready for:"
|
||
print " • provisioning platform start local - Start local services"
|
||
print " • provisioning platform status - Check service status"
|
||
print " • provisioning platform health - Health checks"
|
||
print " • provisioning platform list - List services"
|
||
print ""
|
||
}
|
||
|
||
def platform-logs [args: list<string>] {
|
||
let home = ($env.HOME? | default "~" | path expand)
|
||
let log_dir = ($home | path join ".provisioning" "logs")
|
||
|
||
# Parse args: first non-numeric token = service name, first numeric token = lines limit
|
||
let service_arg = ($args | where { |a| not ($a =~ '^[0-9]+$') } | get 0?)
|
||
let lines_raw = ($args | where { |a| $a =~ '^[0-9]+$' } | get 0?)
|
||
let lines_arg = if $lines_raw != null { $lines_raw | into int } else { null }
|
||
|
||
if not ($log_dir | path exists) {
|
||
print "❌ Log directory not found: ~/.provisioning/logs"
|
||
print " Start services first: provisioning platform start"
|
||
return
|
||
}
|
||
|
||
# Resolve initial log file when service name provided upfront
|
||
let resolved_initial = if $service_arg != null {
|
||
let exact = ($log_dir | path join $"($service_arg).log")
|
||
let under = ($log_dir | path join $"($service_arg | str replace --all '-' '_').log")
|
||
if ($exact | path exists) {
|
||
$exact
|
||
} else if ($under | path exists) {
|
||
$under
|
||
} else {
|
||
print ""
|
||
print $"❌ No log file for: ($service_arg)"
|
||
print $" Tried: ($exact)"
|
||
print $" Tried: ($under)"
|
||
print $" Start with: provisioning platform start ($service_arg)"
|
||
print ""
|
||
return
|
||
}
|
||
} else {
|
||
""
|
||
}
|
||
|
||
mut keep_going = true
|
||
mut current_log = $resolved_initial
|
||
|
||
while $keep_going {
|
||
# Resolve log file for this iteration: preselected or interactive selector
|
||
let log_file = if ($current_log | is-not-empty) {
|
||
$current_log
|
||
} else {
|
||
let entries = (
|
||
ls ($log_dir)
|
||
| where type == file
|
||
| where name =~ '\.log$'
|
||
| get name
|
||
| each { |f| $f | path basename | str replace --regex '\.log$' '' }
|
||
)
|
||
if ($entries | length) == 0 {
|
||
print "❌ No log files in ~/.provisioning/logs"
|
||
$keep_going = false
|
||
""
|
||
} else {
|
||
let selected = (typedialog select "Service logs:" $entries)
|
||
$log_dir | path join $"($selected).log"
|
||
}
|
||
}
|
||
|
||
if $keep_going and ($log_file | is-not-empty) {
|
||
let label = ($log_file | path basename | str replace --regex '\.log$' '')
|
||
print ""
|
||
print $"📋 ($label)"
|
||
print "─────────────────────────────────────────────────"
|
||
print ""
|
||
|
||
if $lines_arg != null {
|
||
^tail -n $lines_arg ($log_file)
|
||
} else {
|
||
^cat ($log_file)
|
||
}
|
||
|
||
print ""
|
||
print "─────────────────────────────────────────────────"
|
||
print $" ($log_file)"
|
||
print ""
|
||
|
||
let choice = (typedialog select "¿Qué deseas hacer?" ["Ver otro log" "Salir"])
|
||
$current_log = ""
|
||
if $choice == "Salir" {
|
||
$keep_going = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
def platform-check [] {
|
||
print ""
|
||
print "🔍 Checking External Services"
|
||
print "════════════════════════════"
|
||
print ""
|
||
|
||
# For now, provide template for checking external services
|
||
# TODO: Load actual config from external-services config file
|
||
|
||
print "External Services to Check:"
|
||
print " ✓ Database (SurrealDB/PostgreSQL/Filesystem)"
|
||
print " ✓ OCI Registry (Zot/Harbor) for extensions"
|
||
print " ✓ Git Source (Forgejo/Gitea/GitHub) for discovery"
|
||
print " ✓ Cache Service (Local directory or Redis)"
|
||
print ""
|
||
|
||
print "To implement full checks, ensure config is loaded from:"
|
||
print " • PLATFORM_MODE environment variable"
|
||
print " • Workspace config/platform/deployment-mode.ncl"
|
||
print " • System defaults"
|
||
print ""
|
||
|
||
print "Remediation:"
|
||
print " 1. Set deployment mode: export PLATFORM_MODE=solo|multiuser|enterprise"
|
||
print " 2. Configure external services in platform config"
|
||
print " 3. Run 'provisioning platform check' again"
|
||
print ""
|
||
}
|
||
|
||
def show-platform-help [] {
|
||
print ""
|
||
print "🖥️ Platform Commands"
|
||
print "===================="
|
||
print ""
|
||
print " platform start [mode|service] - Start services (mode from deployment-mode.ncl if omitted)"
|
||
print " platform stop [service] - Stop all services or specific service"
|
||
print " platform restart [service] - Restart all services or specific service"
|
||
print " platform status - Show service status"
|
||
print " platform health - Health checks"
|
||
print " platform external - Show external services status"
|
||
print " platform list - List available services"
|
||
print " platform config - Show configuration"
|
||
print " platform connections - Show service connections"
|
||
print " platform logs [service] - Stream service logs (interactive selector if no service given)"
|
||
print " platform init - Initialize platform"
|
||
print ""
|
||
print "Examples:"
|
||
print " provisioning platform start # Start using deployment-mode"
|
||
print " provisioning platform start local --services core # Override mode"
|
||
print " provisioning platform start vault_service # Start single service"
|
||
print " provisioning platform stop orchestrator # Stop single service"
|
||
print " provisioning platform restart vault_service # Restart single service"
|
||
print " provisioning platform status"
|
||
print " provisioning platform health"
|
||
print " provisioning platform external # Check external services"
|
||
print " provisioning platform logs # Interactive selector → show full log → loop"
|
||
print " provisioning platform logs orchestrator # Show full orchestrator log"
|
||
print " provisioning platform logs orchestrator 50 # Show last 50 lines"
|
||
print " prvng p logs 100 # Interactive selector, last 100 lines"
|
||
print ""
|
||
}
|