586 lines
15 KiB
Text
Executable file
586 lines
15 KiB
Text
Executable file
#!/usr/bin/env nu
|
||
# Start local provisioning platform services
|
||
# Usage: ./start-local-binaries.nu [FLAGS] [--services <set> [services...]]
|
||
#
|
||
# Services sets:
|
||
# core - vault-service, extension-registry, control-center
|
||
# all - All 10 services
|
||
# custom - Specified services (pass as arguments after --services custom)
|
||
#
|
||
# Examples:
|
||
# ./start-local-binaries.nu --services core
|
||
# ./start-local-binaries.nu --services all
|
||
# ./start-local-binaries.nu --services custom orchestrator control-center ai-service
|
||
|
||
# Color constants for terminal output
|
||
const COLOR_RESET = "\u{1b}[0m"
|
||
const COLOR_GREEN = "\u{1b}[32m"
|
||
const COLOR_YELLOW = "\u{1b}[33m"
|
||
const COLOR_RED = "\u{1b}[31m"
|
||
const COLOR_BLUE = "\u{1b}[34m"
|
||
const COLOR_CYAN = "\u{1b}[36m"
|
||
|
||
# Service registry with metadata
|
||
const SERVICES_REGISTRY = {
|
||
"vault-service": {
|
||
port: 8081,
|
||
protocol: "gRPC",
|
||
description: "Key management and encryption service",
|
||
depends_on: [],
|
||
binary: "vault-service"
|
||
},
|
||
"extension-registry": {
|
||
port: 8082,
|
||
protocol: "HTTP",
|
||
description: "OCI container registry for extensions",
|
||
depends_on: [],
|
||
binary: "extension-registry"
|
||
},
|
||
"control-center": {
|
||
port: 8000,
|
||
protocol: "HTTP/WebSocket",
|
||
description: "Core control plane with JWT auth",
|
||
depends_on: ["vault-service"],
|
||
binary: "control-center"
|
||
},
|
||
"provisioning-rag": {
|
||
port: 8300,
|
||
protocol: "REST",
|
||
description: "Vector search and RAG database",
|
||
depends_on: [],
|
||
binary: "provisioning-rag"
|
||
},
|
||
"ai-service": {
|
||
port: 8083,
|
||
protocol: "HTTP",
|
||
description: "AI service with RAG and MCP tools",
|
||
depends_on: ["provisioning-rag", "vault-service"],
|
||
binary: "ai-service"
|
||
},
|
||
"mcp-server": {
|
||
port: 8400,
|
||
protocol: "Binary",
|
||
description: "Infrastructure automation server",
|
||
depends_on: ["vault-service"],
|
||
binary: "mcp-server"
|
||
},
|
||
"provisioning-daemon": {
|
||
port: 8100,
|
||
protocol: "gRPC",
|
||
description: "Nushell script execution daemon",
|
||
depends_on: ["vault-service"],
|
||
binary: "provisioning-daemon"
|
||
},
|
||
"orchestrator": {
|
||
port: 9090,
|
||
protocol: "HTTP",
|
||
description: "Batch workflow orchestrator",
|
||
depends_on: ["extension-registry", "control-center", "ai-service"],
|
||
binary: "orchestrator"
|
||
},
|
||
"detector": {
|
||
port: 8600,
|
||
protocol: "HTTP",
|
||
description: "Infrastructure detection service",
|
||
depends_on: ["vault-service"],
|
||
binary: "detector"
|
||
},
|
||
"control-center-ui": {
|
||
port: 3000,
|
||
protocol: "HTTP (WASM)",
|
||
description: "Web UI dashboard (Leptos/WASM)",
|
||
depends_on: ["control-center"],
|
||
binary: "control-center-ui"
|
||
}
|
||
}
|
||
|
||
# Service group definitions
|
||
const SERVICE_GROUPS = {
|
||
"core": ["vault-service", "extension-registry", "control-center"],
|
||
"all": [
|
||
"vault-service",
|
||
"extension-registry",
|
||
"control-center",
|
||
"provisioning-rag",
|
||
"ai-service",
|
||
"mcp-server",
|
||
"provisioning-daemon",
|
||
"orchestrator",
|
||
"detector",
|
||
"control-center-ui"
|
||
]
|
||
}
|
||
|
||
# Utility functions
|
||
|
||
def log_info [message: string] {
|
||
print $"($COLOR_BLUE)ℹ($COLOR_RESET) ($message)"
|
||
}
|
||
|
||
def log_success [message: string] {
|
||
print $"($COLOR_GREEN)✓($COLOR_RESET) ($message)"
|
||
}
|
||
|
||
def log_warning [message: string] {
|
||
print $"($COLOR_YELLOW)⚠($COLOR_RESET) ($message)"
|
||
}
|
||
|
||
def log_error [message: string] {
|
||
print $"($COLOR_RED)✗($COLOR_RESET) ($message)"
|
||
}
|
||
|
||
def log_section [title: string] {
|
||
print $"($COLOR_CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━($COLOR_RESET)"
|
||
print $"($COLOR_CYAN)($title)($COLOR_RESET)"
|
||
print $"($COLOR_CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━($COLOR_RESET)"
|
||
}
|
||
|
||
# Check if a port is available
|
||
def is_port_available [port: int] {
|
||
try {
|
||
let result = (
|
||
if ($nu.os-info.name == "macos") {
|
||
lsof -i -P -n | grep LISTEN | grep -c $":($port) " | into int
|
||
} else {
|
||
netstat -an | grep LISTEN | grep -c ":($port) " | into int
|
||
}
|
||
)
|
||
$result == 0
|
||
} catch {
|
||
true # Assume available if check fails
|
||
}
|
||
}
|
||
|
||
# Resolve service startup order (respecting dependencies)
|
||
def resolve_startup_order [services: list<string>] {
|
||
mut ordered = []
|
||
mut remaining = $services
|
||
mut max_iterations = 100
|
||
mut iterations = 0
|
||
|
||
while (($remaining | length) > 0) and ($iterations < $max_iterations) {
|
||
mut found_any = false
|
||
|
||
for service in $remaining {
|
||
let deps = ($SERVICES_REGISTRY | get $service).depends_on
|
||
let all_deps_satisfied = (
|
||
$deps | all { |dep| $ordered | any { |s| $s == $dep } }
|
||
)
|
||
|
||
if $all_deps_satisfied {
|
||
$ordered = ($ordered | append $service)
|
||
$remaining = ($remaining | where { |s| $s != $service })
|
||
$found_any = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if not $found_any {
|
||
log_error $"Circular dependency detected or missing dependencies for: ($remaining | str join ', ')"
|
||
return []
|
||
}
|
||
|
||
$iterations = $iterations + 1
|
||
}
|
||
|
||
if (($remaining | length) > 0) {
|
||
log_error $"Failed to resolve startup order for: ($remaining | str join ', ')"
|
||
return []
|
||
}
|
||
|
||
$ordered
|
||
}
|
||
|
||
# Parse command line arguments
|
||
def parse_arguments [args: list<string>] {
|
||
let mut config = {
|
||
services_set: "core",
|
||
custom_services: [],
|
||
debug: false,
|
||
health_check: false,
|
||
logs: false,
|
||
stop: false,
|
||
config_file: null,
|
||
verbose: false
|
||
}
|
||
|
||
let mut i = 0
|
||
while $i < ($args | length) {
|
||
let arg = $args | get $i
|
||
|
||
if $arg == "--services" {
|
||
$i = $i + 1
|
||
if $i < ($args | length) {
|
||
let set = $args | get $i
|
||
$config.services_set = $set
|
||
|
||
# If custom, collect remaining service names
|
||
if $set == "custom" {
|
||
$i = $i + 1
|
||
while $i < ($args | length) and not ($args | get $i | str starts-with "-") {
|
||
$config.custom_services = ($config.custom_services | append ($args | get $i))
|
||
$i = $i + 1
|
||
}
|
||
$i = $i - 1
|
||
}
|
||
}
|
||
} else if $arg == "--debug" {
|
||
$config.debug = true
|
||
} else if $arg == "--health-check" {
|
||
$config.health_check = true
|
||
} else if $arg == "--logs" {
|
||
$config.logs = true
|
||
} else if $arg == "--stop" {
|
||
$config.stop = true
|
||
} else if $arg == "--config" {
|
||
$i = $i + 1
|
||
if $i < ($args | length) {
|
||
$config.config_file = ($args | get $i)
|
||
}
|
||
} else if $arg == "--verbose" {
|
||
$config.verbose = true
|
||
}
|
||
|
||
$i = $i + 1
|
||
}
|
||
|
||
$config
|
||
}
|
||
|
||
# Determine which services to start
|
||
def get_services_to_start [config: record] {
|
||
if $config.services_set == "custom" {
|
||
$config.custom_services
|
||
} else if $config.services_set == "all" {
|
||
$SERVICE_GROUPS.all
|
||
} else {
|
||
$SERVICE_GROUPS.core
|
||
}
|
||
}
|
||
|
||
# Check if service is already running
|
||
def is_service_running [service_name: string] {
|
||
let port = ($SERVICES_REGISTRY | get $service_name).port
|
||
|
||
try {
|
||
if ($nu.os-info.name == "macos") {
|
||
let result = (lsof -i -P -n | grep LISTEN | grep -c $":($port) " | into int)
|
||
$result > 0
|
||
} else {
|
||
let result = (netstat -an | grep LISTEN | grep -c ":($port) " | into int)
|
||
$result > 0
|
||
}
|
||
} catch {
|
||
false
|
||
}
|
||
}
|
||
|
||
# Start a single service
|
||
def start_service [service_name: string, config: record, index: int] {
|
||
let service_info = $SERVICES_REGISTRY | get $service_name
|
||
let port = $service_info.port
|
||
let binary = $service_info.binary
|
||
let protocol = $service_info.protocol
|
||
|
||
if (is_service_running $service_name) {
|
||
log_warning $"($service_name) is already running on port ($port)"
|
||
return true
|
||
}
|
||
|
||
if not (is_port_available $port) {
|
||
log_error $"Port ($port) is not available for ($service_name)"
|
||
return false
|
||
}
|
||
|
||
# Show progress
|
||
print ""
|
||
print $"[$COLOR_YELLOW($index)($COLOR_RESET)] Starting ($service_name) on port ($port) ($COLOR_GREEN)($protocol)($COLOR_RESET)"
|
||
print $" → $($service_info.description)"
|
||
|
||
# Prepare environment variables
|
||
let mut env_vars = {}
|
||
|
||
match $service_name {
|
||
"vault-service" => {
|
||
$env_vars = {
|
||
VAULT_PORT: ($port | into string),
|
||
RUST_LOG: (if $config.debug { "debug" } else { "info" })
|
||
}
|
||
},
|
||
"control-center" => {
|
||
let platform_base = ($env.PROVISIONING_USER_PLATFORM? | default "~/.config/provisioning/platform")
|
||
let config_file = ($config.config_file ?? $"($platform_base)/config/control-center.ncl")
|
||
$env_vars = {
|
||
CONTROL_CENTER_PORT: ($port | into string),
|
||
CONTROL_CENTER_CONFIG: ($config_file | path expand),
|
||
RUST_LOG: (if $config.debug { "debug" } else { "info" })
|
||
}
|
||
},
|
||
"orchestrator" => {
|
||
$env_vars = {
|
||
ORCHESTRATOR_PORT: ($port | into string),
|
||
REGISTRY_URL: "http://localhost:8082",
|
||
RUST_LOG: (if $config.debug { "debug" } else { "info" })
|
||
}
|
||
},
|
||
"ai-service" => {
|
||
$env_vars = {
|
||
AI_SERVICE_PORT: ($port | into string),
|
||
RAG_URL: "http://localhost:8300",
|
||
RUST_LOG: (if $config.debug { "debug" } else { "info" })
|
||
}
|
||
},
|
||
"control-center-ui" => {
|
||
$env_vars = {
|
||
UI_PORT: ($port | into string),
|
||
CONTROL_CENTER_URL: "http://localhost:8000",
|
||
RUST_LOG: (if $config.debug { "debug" } else { "info" })
|
||
}
|
||
},
|
||
_ => {
|
||
$env_vars = {
|
||
RUST_LOG: (if $config.debug { "debug" } else { "info" })
|
||
}
|
||
}
|
||
}
|
||
|
||
# Create logs directory
|
||
let log_dir = $"($env.HOME? | default "~")/.provisioning/logs"
|
||
try {
|
||
mkdir ($log_dir | path expand)
|
||
} catch { }
|
||
|
||
let log_file = $"($log_dir)/($service_name).log"
|
||
|
||
# Start service in background
|
||
try {
|
||
if ($nu.os-info.name == "macos") {
|
||
let cmd = if ($service_name == "control-center-ui") {
|
||
# UI service uses different build
|
||
$"cargo run --release -p control-center-ui 2>&1 | tee ($log_file)"
|
||
} else {
|
||
$"cargo run --release -p ($service_name) 2>&1 | tee ($log_file)"
|
||
}
|
||
|
||
# Set environment and run in background
|
||
with-env $env_vars {
|
||
nohup bash -c $cmd > /dev/null 2>&1 &
|
||
}
|
||
} else {
|
||
let cmd = if ($service_name == "control-center-ui") {
|
||
$"cargo run --release -p control-center-ui 2>&1 | tee ($log_file)"
|
||
} else {
|
||
$"cargo run --release -p ($service_name) 2>&1 | tee ($log_file)"
|
||
}
|
||
|
||
with-env $env_vars {
|
||
nohup sh -c $cmd > /dev/null 2>&1 &
|
||
}
|
||
}
|
||
|
||
# Wait for service to be ready
|
||
sleep 2s
|
||
|
||
# Check if service is running
|
||
if (is_service_running $service_name) {
|
||
log_success $"($service_name) started successfully"
|
||
return true
|
||
} else {
|
||
log_error $"($service_name) failed to start (port not responding)"
|
||
return false
|
||
}
|
||
} catch {
|
||
log_error $"Failed to start ($service_name): ($in)"
|
||
return false
|
||
}
|
||
}
|
||
|
||
# Health check for a service
|
||
def health_check [service_name: string] {
|
||
let service_info = $SERVICES_REGISTRY | get $service_name
|
||
let port = $service_info.port
|
||
|
||
try {
|
||
let response = (
|
||
curl -s -f $"http://localhost:($port)/health" 2>/dev/null ||
|
||
curl -s -f $"http://localhost:($port)/ping" 2>/dev/null ||
|
||
curl -s -f $"http://localhost:($port)/" 2>/dev/null
|
||
)
|
||
true
|
||
} catch {
|
||
false
|
||
}
|
||
}
|
||
|
||
# Stop all services
|
||
def stop_all_services [] {
|
||
log_section "Stopping all services"
|
||
|
||
# Kill all cargo processes
|
||
try {
|
||
let result = (
|
||
if ($nu.os-info.name == "macos") {
|
||
pkill -f "cargo run" 2>/dev/null
|
||
} else {
|
||
pkill -f "cargo run" 2>/dev/null
|
||
}
|
||
)
|
||
log_success "Stopped all cargo processes"
|
||
} catch {
|
||
log_warning "No cargo processes found"
|
||
}
|
||
|
||
# Stop Docker containers if running
|
||
try {
|
||
for container in ["provisioning-postgres", "provisioning-redis"] {
|
||
docker stop $container 2>/dev/null
|
||
log_success $"Stopped Docker container: ($container)"
|
||
}
|
||
} catch { }
|
||
|
||
print ""
|
||
log_success "All services stopped"
|
||
}
|
||
|
||
# Show service status
|
||
def show_status [services: list<string>] {
|
||
log_section "Service Status"
|
||
|
||
for service in $services {
|
||
let service_info = $SERVICES_REGISTRY | get $service
|
||
let is_running = (is_service_running $service)
|
||
let status = (if $is_running { $"($COLOR_GREEN)✓ RUNNING($COLOR_RESET)" } else { $"($COLOR_RED)✗ STOPPED($COLOR_RESET)" })
|
||
|
||
print $"($service): $status (port $($service_info.port))"
|
||
}
|
||
|
||
print ""
|
||
}
|
||
|
||
# Main execution
|
||
def main [args: list<string>] {
|
||
let config = (parse_arguments $args)
|
||
|
||
# Handle stop action
|
||
if $config.stop {
|
||
(stop_all_services)
|
||
return
|
||
}
|
||
|
||
# Validate services
|
||
let services_to_start = (get_services_to_start $config)
|
||
|
||
if ($services_to_start | length == 0) {
|
||
log_error "No services to start"
|
||
return
|
||
}
|
||
|
||
# Validate all services exist
|
||
for service in $services_to_start {
|
||
if not ($SERVICES_REGISTRY | has $service) {
|
||
log_error $"Unknown service: ($service)"
|
||
log_info "Available services: $([$SERVICES_REGISTRY | keys | join ', '])"
|
||
return
|
||
}
|
||
}
|
||
|
||
# Show startup configuration
|
||
log_section "Provisioning Platform - Local Services"
|
||
log_info $"Starting ($($services_to_start | length)) services"
|
||
log_info $"Service set: ($config.services_set)"
|
||
|
||
if $config.debug {
|
||
log_info "Debug mode: ENABLED"
|
||
}
|
||
|
||
if $config.health_check {
|
||
log_info "Health checks: ENABLED"
|
||
}
|
||
|
||
print ""
|
||
|
||
# Resolve startup order
|
||
let startup_order = (resolve_startup_order $services_to_start)
|
||
|
||
if ($startup_order | length == 0) {
|
||
return
|
||
}
|
||
|
||
# Change to provisioning/platform directory
|
||
let original_dir = (pwd)
|
||
let platform_dir = $"($original_dir)/provisioning/platform"
|
||
|
||
if not (test -d $platform_dir) {
|
||
log_error "provisioning/platform directory not found"
|
||
log_info "Make sure you're running this script from the project root"
|
||
return
|
||
}
|
||
|
||
cd $platform_dir
|
||
|
||
# Start services
|
||
mut failed_services = []
|
||
|
||
for idx in 0..($startup_order | length) {
|
||
let service = $startup_order | get $idx
|
||
let success = (start_service $service $config ($idx + 1))
|
||
|
||
if not $success {
|
||
$failed_services = ($failed_services | append $service)
|
||
}
|
||
|
||
# Wait between services (dependencies need time to start)
|
||
if $idx < (($startup_order | length) - 1) {
|
||
sleep 2
|
||
}
|
||
}
|
||
|
||
print ""
|
||
log_section "Startup Summary"
|
||
|
||
# Show status
|
||
(show_status $startup_order)
|
||
|
||
# Health checks if enabled
|
||
if $config.health_check {
|
||
print ""
|
||
log_info "Running health checks..."
|
||
|
||
for service in $startup_order {
|
||
let healthy = (health_check $service)
|
||
let status = (if $healthy { $"($COLOR_GREEN)✓ HEALTHY($COLOR_RESET)" } else { $"($COLOR_YELLOW)⚠ NOT RESPONDING($COLOR_RESET)" })
|
||
print $" ($service): $status"
|
||
}
|
||
}
|
||
|
||
# Summary
|
||
print ""
|
||
if (($failed_services | length) > 0) {
|
||
log_warning $"(($failed_services | length)) services failed to start"
|
||
for service in $failed_services {
|
||
print $" - ($service)"
|
||
}
|
||
log_info "Check logs: ~/.provisioning/logs/"
|
||
} else {
|
||
log_success "All services started successfully!"
|
||
}
|
||
|
||
print ""
|
||
log_info "Service URLs:"
|
||
print " Control Center: http://localhost:8000"
|
||
print " Control Center UI: http://localhost:3000"
|
||
print " Orchestrator: http://localhost:9090"
|
||
print " AI Service: http://localhost:8083"
|
||
print " Vault Service: grpc://localhost:8081"
|
||
|
||
print ""
|
||
log_info "Logs location: ~/.provisioning/logs/"
|
||
log_info "Stop all services: ./start-local-binaries.nu --stop"
|
||
|
||
print ""
|
||
|
||
cd $original_dir
|
||
}
|
||
|
||
# Run main with arguments
|
||
#main $nu.env.ARGS
|