2025-10-07 10:32:04 +01:00
|
|
|
#!/usr/bin/env nu
|
|
|
|
|
|
|
|
|
|
# Pre-flight Check System
|
|
|
|
|
# Validates service prerequisites before operations
|
|
|
|
|
|
|
|
|
|
use manager.nu [load-service-registry is-service-running get-service-definition]
|
|
|
|
|
use dependencies.nu [resolve-dependencies get-startup-order]
|
|
|
|
|
|
|
|
|
|
# Check required services for operation
|
|
|
|
|
export def check-required-services [
|
|
|
|
|
operation: string
|
2025-12-11 21:57:05 +00:00
|
|
|
]: nothing -> record {
|
2025-10-07 10:32:04 +01:00
|
|
|
let registry = (load-service-registry)
|
|
|
|
|
|
|
|
|
|
# Find all services required for this operation
|
|
|
|
|
let required_services = (
|
|
|
|
|
$registry
|
|
|
|
|
| transpose name config
|
|
|
|
|
| where { |row|
|
|
|
|
|
$operation in $row.config.required_for
|
|
|
|
|
}
|
|
|
|
|
| get name
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if ($required_services | is-empty) {
|
|
|
|
|
return {
|
|
|
|
|
operation: $operation
|
|
|
|
|
required_services: []
|
|
|
|
|
all_running: true
|
|
|
|
|
missing_services: []
|
|
|
|
|
can_auto_start: true
|
|
|
|
|
message: "No services required for this operation"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Check which services are running
|
2025-12-11 21:57:05 +00:00
|
|
|
def partition-services [services: list, running: list, missing: list]: nothing -> record {
|
|
|
|
|
if ($services | is-empty) {
|
|
|
|
|
return { running: $running, missing: $missing }
|
|
|
|
|
}
|
|
|
|
|
let first = ($services | get 0)
|
|
|
|
|
let rest = ($services | skip 1)
|
|
|
|
|
if (is-service-running $first) {
|
|
|
|
|
partition-services $rest ($running | append $first) $missing
|
2025-10-07 10:32:04 +01:00
|
|
|
} else {
|
2025-12-11 21:57:05 +00:00
|
|
|
partition-services $rest $running ($missing | append $first)
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
let service_status = (partition-services $required_services [] [])
|
|
|
|
|
let running = $service_status.running
|
|
|
|
|
let missing = $service_status.missing
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
# Check if missing services can be auto-started
|
|
|
|
|
let can_auto_start = (
|
|
|
|
|
$missing
|
|
|
|
|
| all { |service|
|
|
|
|
|
let service_def = (get-service-definition $service)
|
|
|
|
|
$service_def.startup.auto_start
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
operation: $operation
|
|
|
|
|
required_services: $required_services
|
|
|
|
|
running_services: $running
|
|
|
|
|
missing_services: $missing
|
|
|
|
|
all_running: ($missing | is-empty)
|
|
|
|
|
can_auto_start: $can_auto_start
|
|
|
|
|
message: (if ($missing | is-empty) {
|
|
|
|
|
"All required services are running"
|
|
|
|
|
} else if $can_auto_start {
|
|
|
|
|
$"Services need to be started: ($missing | str join ', ')"
|
|
|
|
|
} else {
|
|
|
|
|
$"Required services not running and cannot auto-start: ($missing | str join ', ')"
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Validate service prerequisites
|
|
|
|
|
export def validate-service-prerequisites [
|
|
|
|
|
service_name: string
|
2025-12-11 21:57:05 +00:00
|
|
|
]: nothing -> record {
|
2025-10-07 10:32:04 +01:00
|
|
|
let service_def = (get-service-definition $service_name)
|
|
|
|
|
|
|
|
|
|
# Check deployment mode requirements
|
2025-12-11 21:57:05 +00:00
|
|
|
let issues = (
|
|
|
|
|
if $service_def.deployment.mode == "docker" or $service_def.deployment.mode == "docker-compose" {
|
2025-10-07 10:32:04 +01:00
|
|
|
# Check if Docker is available
|
2025-12-11 21:57:05 +00:00
|
|
|
let docker_check = (do {
|
|
|
|
|
docker --version
|
|
|
|
|
} | complete)
|
|
|
|
|
|
|
|
|
|
if $docker_check.exit_code != 0 {
|
|
|
|
|
["Docker is not installed or not running"]
|
|
|
|
|
} else {
|
|
|
|
|
[]
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
} else if $service_def.deployment.mode == "kubernetes" {
|
2025-10-07 10:32:04 +01:00
|
|
|
# Check if kubectl is available
|
2025-12-11 21:57:05 +00:00
|
|
|
let kubectl_check = (do {
|
|
|
|
|
kubectl version --client
|
|
|
|
|
} | complete)
|
|
|
|
|
|
|
|
|
|
if $kubectl_check.exit_code != 0 {
|
|
|
|
|
["kubectl is not installed"]
|
|
|
|
|
} else {
|
|
|
|
|
[]
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
} else if $service_def.deployment.mode == "binary" {
|
2025-10-07 10:32:04 +01:00
|
|
|
# Check if binary exists
|
|
|
|
|
let binary_path = ($service_def.deployment.binary.binary_path | str replace -a '${HOME}' $env.HOME)
|
|
|
|
|
if not ($binary_path | path exists) {
|
2025-12-11 21:57:05 +00:00
|
|
|
[$"Binary not found: ($binary_path)"]
|
|
|
|
|
} else {
|
|
|
|
|
[]
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
} else {
|
|
|
|
|
[]
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
)
|
2025-10-07 10:32:04 +01:00
|
|
|
|
|
|
|
|
# Check dependencies
|
2025-12-11 21:57:05 +00:00
|
|
|
def check-deps [deps: list, warnings: list]: nothing -> list {
|
|
|
|
|
if ($deps | is-empty) {
|
|
|
|
|
return $warnings
|
|
|
|
|
}
|
|
|
|
|
let first = ($deps | get 0)
|
|
|
|
|
let rest = ($deps | skip 1)
|
|
|
|
|
let new_warnings = if (is-service-running $first) {
|
|
|
|
|
$warnings
|
|
|
|
|
} else {
|
|
|
|
|
$warnings | append $"Dependency '($first)' is not running"
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
check-deps $rest $new_warnings
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
let warnings = (check-deps $service_def.dependencies [])
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
# Check conflicts
|
2025-12-11 21:57:05 +00:00
|
|
|
def check-conflicts [conflicts: list, issues: list]: nothing -> list {
|
|
|
|
|
if ($conflicts | is-empty) {
|
|
|
|
|
return $issues
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
let first = ($conflicts | get 0)
|
|
|
|
|
let rest = ($conflicts | skip 1)
|
|
|
|
|
let new_issues = if (is-service-running $first) {
|
|
|
|
|
$issues | append $"Conflicting service '($first)' is running"
|
|
|
|
|
} else {
|
|
|
|
|
$issues
|
|
|
|
|
}
|
|
|
|
|
check-conflicts $rest $new_issues
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
let all_issues = (check-conflicts $service_def.conflicts $issues)
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
{
|
|
|
|
|
service: $service_name
|
2025-12-11 21:57:05 +00:00
|
|
|
valid: ($all_issues | is-empty)
|
|
|
|
|
issues: $all_issues
|
2025-10-07 10:32:04 +01:00
|
|
|
warnings: $warnings
|
2025-12-11 21:57:05 +00:00
|
|
|
can_start: ($all_issues | is-empty)
|
|
|
|
|
message: (if ($all_issues | is-empty) {
|
2025-10-07 10:32:04 +01:00
|
|
|
"All prerequisites met"
|
|
|
|
|
} else {
|
2025-12-11 21:57:05 +00:00
|
|
|
$"Prerequisites not met: ($all_issues | str join '; ')"
|
2025-10-07 10:32:04 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Auto-start required services
|
|
|
|
|
export def auto-start-required-services [
|
|
|
|
|
operation: string
|
2025-12-11 21:57:05 +00:00
|
|
|
]: nothing -> record {
|
2025-10-07 10:32:04 +01:00
|
|
|
let check = (check-required-services $operation)
|
|
|
|
|
|
|
|
|
|
if $check.all_running {
|
|
|
|
|
return {
|
|
|
|
|
success: true
|
|
|
|
|
message: "All required services already running"
|
|
|
|
|
started_services: []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if not $check.can_auto_start {
|
|
|
|
|
return {
|
|
|
|
|
success: false
|
|
|
|
|
message: "Some required services cannot be auto-started"
|
|
|
|
|
failed_services: $check.missing_services
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get startup order for missing services
|
|
|
|
|
let startup_order = (get-startup-order $check.missing_services)
|
|
|
|
|
|
|
|
|
|
print $"Starting required services in order: ($startup_order | str join ' -> ')"
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
# Helper to start services in sequence
|
|
|
|
|
def start-services-seq [services: list, started: list, failed: list]: nothing -> record {
|
|
|
|
|
if ($services | is-empty) {
|
|
|
|
|
return { started: $started, failed: $failed }
|
|
|
|
|
}
|
|
|
|
|
let first = ($services | get 0)
|
|
|
|
|
let rest = ($services | skip 1)
|
2025-10-07 10:32:04 +01:00
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
print $"Starting [$first]..."
|
2025-10-07 10:32:04 +01:00
|
|
|
|
|
|
|
|
use manager.nu start-service
|
2025-12-11 21:57:05 +00:00
|
|
|
let start_result = (do {
|
|
|
|
|
start-service $first
|
|
|
|
|
} | complete)
|
2025-10-07 10:32:04 +01:00
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
if $start_result.exit_code == 0 and ($start_result.stdout == "true" or $start_result.stdout == true) {
|
|
|
|
|
print $"✅ [$first] started successfully"
|
|
|
|
|
start-services-seq $rest ($started | append $first) $failed
|
|
|
|
|
} else {
|
|
|
|
|
print $"❌ Failed to start [$first]: ($start_result.stderr)"
|
|
|
|
|
{ started: $started, failed: ($failed | append $first) }
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
let result = (start-services-seq $startup_order [] [])
|
|
|
|
|
let started = $result.started
|
|
|
|
|
let failed = $result.failed
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
{
|
|
|
|
|
success: ($failed | is-empty)
|
|
|
|
|
message: (if ($failed | is-empty) {
|
|
|
|
|
$"Successfully started ($started | length) services"
|
|
|
|
|
} else {
|
|
|
|
|
$"Failed to start some services: ($failed | str join ', ')"
|
|
|
|
|
})
|
|
|
|
|
started_services: $started
|
|
|
|
|
failed_services: $failed
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Check service conflicts
|
|
|
|
|
export def check-service-conflicts [
|
|
|
|
|
service_name: string
|
2025-12-11 21:57:05 +00:00
|
|
|
]: nothing -> record {
|
2025-10-07 10:32:04 +01:00
|
|
|
let service_def = (get-service-definition $service_name)
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
# Helper to check conflicts
|
|
|
|
|
def find-conflicts [conflicts: list, result: list]: nothing -> list {
|
|
|
|
|
if ($conflicts | is-empty) {
|
|
|
|
|
return $result
|
|
|
|
|
}
|
|
|
|
|
let first = ($conflicts | get 0)
|
|
|
|
|
let rest = ($conflicts | skip 1)
|
|
|
|
|
let new_result = if (is-service-running $first) {
|
|
|
|
|
$result | append {
|
|
|
|
|
service: $first
|
2025-10-07 10:32:04 +01:00
|
|
|
status: "running"
|
|
|
|
|
action_required: "stop"
|
2025-12-11 21:57:05 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$result
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
find-conflicts $rest $new_result
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
let conflicts = (find-conflicts $service_def.conflicts [])
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
{
|
|
|
|
|
service: $service_name
|
|
|
|
|
has_conflicts: (not ($conflicts | is-empty))
|
|
|
|
|
conflicts: $conflicts
|
|
|
|
|
can_start: ($conflicts | is-empty)
|
|
|
|
|
message: (if ($conflicts | is-empty) {
|
|
|
|
|
"No conflicting services running"
|
|
|
|
|
} else {
|
|
|
|
|
$"Conflicting services running: (($conflicts | get service) | str join ', ')"
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Validate all services
|
2025-12-11 21:57:05 +00:00
|
|
|
export def validate-all-services []: nothing -> record {
|
2025-10-07 10:32:04 +01:00
|
|
|
let registry = (load-service-registry)
|
|
|
|
|
|
|
|
|
|
let validation_results = (
|
|
|
|
|
$registry
|
|
|
|
|
| transpose name config
|
|
|
|
|
| each { |row|
|
|
|
|
|
validate-service-prerequisites $row.name
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let total = ($validation_results | length)
|
|
|
|
|
let valid = ($validation_results | where valid | length)
|
|
|
|
|
let invalid = ($validation_results | where { |r| not $r.valid } | length)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
total_services: $total
|
|
|
|
|
valid_services: $valid
|
|
|
|
|
invalid_services: $invalid
|
|
|
|
|
all_valid: ($invalid == 0)
|
|
|
|
|
results: $validation_results
|
|
|
|
|
message: $"($valid)/($total) services have valid configurations"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Pre-flight check for service start
|
|
|
|
|
export def preflight-start-service [
|
|
|
|
|
service_name: string
|
2025-12-11 21:57:05 +00:00
|
|
|
]: nothing -> record {
|
2025-10-07 10:32:04 +01:00
|
|
|
print $"Running pre-flight checks for ($service_name)..."
|
|
|
|
|
|
|
|
|
|
# 1. Validate prerequisites
|
|
|
|
|
let prereq_check = (validate-service-prerequisites $service_name)
|
|
|
|
|
if not $prereq_check.valid {
|
|
|
|
|
return {
|
|
|
|
|
can_start: false
|
|
|
|
|
reason: "prerequisites"
|
|
|
|
|
details: $prereq_check
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 2. Check conflicts
|
|
|
|
|
let conflict_check = (check-service-conflicts $service_name)
|
|
|
|
|
if $conflict_check.has_conflicts {
|
|
|
|
|
return {
|
|
|
|
|
can_start: false
|
|
|
|
|
reason: "conflicts"
|
|
|
|
|
details: $conflict_check
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 3. Check dependencies
|
|
|
|
|
let service_def = (get-service-definition $service_name)
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
# Helper to collect missing dependencies
|
|
|
|
|
def collect-missing-deps [deps: list, missing: list]: nothing -> list {
|
|
|
|
|
if ($deps | is-empty) {
|
|
|
|
|
return $missing
|
|
|
|
|
}
|
|
|
|
|
let first = ($deps | get 0)
|
|
|
|
|
let rest = ($deps | skip 1)
|
|
|
|
|
let new_missing = if (is-service-running $first) {
|
|
|
|
|
$missing
|
|
|
|
|
} else {
|
|
|
|
|
let dep_def = (get-service-definition $first)
|
|
|
|
|
$missing | append {
|
|
|
|
|
service: $first
|
2025-10-07 10:32:04 +01:00
|
|
|
auto_start: $dep_def.startup.auto_start
|
2025-12-11 21:57:05 +00:00
|
|
|
}
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
2025-12-11 21:57:05 +00:00
|
|
|
collect-missing-deps $rest $new_missing
|
2025-10-07 10:32:04 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-11 21:57:05 +00:00
|
|
|
let missing_deps = (collect-missing-deps $service_def.dependencies [])
|
|
|
|
|
|
2025-10-07 10:32:04 +01:00
|
|
|
if not ($missing_deps | is-empty) {
|
|
|
|
|
let can_auto_start = ($missing_deps | all { |d| $d.auto_start })
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
can_start: $can_auto_start
|
|
|
|
|
reason: "dependencies"
|
|
|
|
|
details: {
|
|
|
|
|
missing_dependencies: $missing_deps
|
|
|
|
|
can_auto_start: $can_auto_start
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# All checks passed
|
|
|
|
|
{
|
|
|
|
|
can_start: true
|
|
|
|
|
reason: "all_checks_passed"
|
|
|
|
|
details: {
|
|
|
|
|
message: "Pre-flight checks passed"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get service readiness report
|
2025-12-11 21:57:05 +00:00
|
|
|
export def get-readiness-report []: nothing -> record {
|
2025-10-07 10:32:04 +01:00
|
|
|
let registry = (load-service-registry)
|
|
|
|
|
|
|
|
|
|
let services = (
|
|
|
|
|
$registry
|
|
|
|
|
| transpose name config
|
|
|
|
|
| each { |row|
|
|
|
|
|
let is_running = (is-service-running $row.name)
|
|
|
|
|
let prereq_check = (validate-service-prerequisites $row.name)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
name: $row.name
|
|
|
|
|
type: $row.config.type
|
|
|
|
|
category: $row.config.category
|
|
|
|
|
running: $is_running
|
|
|
|
|
auto_start: $row.config.startup.auto_start
|
|
|
|
|
valid_config: $prereq_check.valid
|
|
|
|
|
can_start: $prereq_check.can_start
|
|
|
|
|
issues: ($prereq_check.issues | length)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let total = ($services | length)
|
|
|
|
|
let running = ($services | where running | length)
|
|
|
|
|
let ready = ($services | where can_start | length)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
total_services: $total
|
|
|
|
|
running_services: $running
|
|
|
|
|
ready_to_start: $ready
|
|
|
|
|
services: $services
|
|
|
|
|
summary: $"($running) running, ($ready) ready to start out of ($total) total"
|
|
|
|
|
}
|
|
|
|
|
}
|