provisioning/scripts/start-local-binaries.nu

587 lines
15 KiB
Text
Raw Permalink Normal View History

#!/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