provisioning/scripts/start-local-binaries.nu
Jesús Pérez 334f351fc5 docs: add complete platform services documentation
- Add Platform Services Inventory with all 10 services and 50+ endpoints
- Add Local Services Setup Guide with build, config, and troubleshooting
- Add start-local-binaries.nu automation script with dependency resolution
- Update SUMMARY.md navigation for new operational documentation

This resolves layout convention violations by:
  - Moving root markdown files to operations/ directory
  - Using lowercase kebab-case for all filenames
  - Consolidating operational documentation
  - Updating navigation index

Coverage:
  - All 10 services documented: vault, registry, control-center, rag, ai, mcp, daemon, orchestrator, detector, ui
  - Service dependencies tracked and visualized
  - Local development workflows documented
  - Nushell automation script with core/all/custom service groups
2026-02-03 13:16:12 +00:00

584 lines
15 KiB
Text
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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] -> bool {
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>] -> list<string> {
let mut ordered = []
let mut remaining = $services
let mut max_iterations = 100
let mut iterations = 0
while ($remaining | length > 0) and ($iterations < $max_iterations) {
let 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 | filter { |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>] -> record {
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] -> list<string> {
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] -> bool {
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] -> bool {
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" => {
$env_vars = {
CONTROL_CENTER_PORT: ($port | into string),
CONTROL_CENTER_CONFIG: ($config.config_file ?? "~/.config/provisioning/control-center.toml"),
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 = "~/.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] -> bool {
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
let 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 2s
}
}
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