#!/usr/bin/env nu # Start local provisioning platform services # Usage: ./start-local-binaries.nu [FLAGS] [--services [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] { 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] { 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] { 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] { 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