#!/usr/bin/env nu # Helper Functions for Provisioning Platform Deployment export def check-prerequisites []: nothing -> record { print "🔍 Checking prerequisites..." let checks = [ {name: "nushell", cmd: "nu", min_version: "0.107.0"} {name: "docker", cmd: "docker", min_version: "20.10.0"} {name: "git", cmd: "git", min_version: "2.30.0"} ] mut failures = [] for check in $checks { let available = (which $check.cmd | is-not-empty) if not $available { $failures = ($failures | append { tool: $check.name, reason: "Not found in PATH" }) } } if ($failures | is-empty) { print "✅ All prerequisites satisfied" {success: true, failures: []} } else { print "❌ Missing prerequisites:" for failure in $failures { print $" - ($failure.tool): ($failure.reason)" } { success: false, error: "Missing required tools", failures: $failures } } } export def validate-deployment-params [platform: string, mode: string]: nothing -> record { let valid_platforms = ["docker", "podman", "kubernetes", "orbstack"] let valid_modes = ["solo", "multi-user", "cicd", "enterprise"] if $platform not-in $valid_platforms { return { success: false, error: $"Invalid platform '($platform)'. Must be one of: ($valid_platforms | str join ', ')" } } if $mode not-in $valid_modes { return { success: false, error: $"Invalid mode '($mode)'. Must be one of: ($valid_modes | str join ', ')" } } {success: true} } export def build-deployment-config [params: record]: nothing -> record { let default_services = get-default-services $params.mode let services = if ($params.services | is-empty) { $default_services } else { $default_services | where {|svc| $svc.name in $params.services or $svc.required } } { platform: $params.platform mode: $params.mode domain: $params.domain services: $services auto_generate_secrets: ($params.auto_generate_secrets? | default true) } } def get-default-services [mode: string]: nothing -> list { let base_services = [ {name: "orchestrator", description: "Task coordination", port: 8080, enabled: true, required: true} {name: "control-center", description: "Web UI", port: 8081, enabled: true, required: true} {name: "coredns", description: "DNS service", port: 5353, enabled: true, required: true} ] let mode_services = match $mode { "solo" => [ {name: "oci-registry", description: "OCI Registry (Zot)", port: 5000, enabled: false, required: false} {name: "catalog-registry", description: "Extension hosting", port: 8082, enabled: false, required: false} {name: "mcp-server", description: "Model Context Protocol", port: 8084, enabled: false, required: false} {name: "api-gateway", description: "REST API access", port: 8085, enabled: false, required: false} ] "multi-user" => [ {name: "gitea", description: "Git server", port: 3000, enabled: true, required: true} {name: "postgres", description: "Shared database", port: 5432, enabled: true, required: true} {name: "oci-registry", description: "OCI Registry (Zot)", port: 5000, enabled: false, required: false} ] "cicd" => [ {name: "gitea", description: "Git server", port: 3000, enabled: true, required: true} {name: "postgres", description: "Shared database", port: 5432, enabled: true, required: true} {name: "api-server", description: "REST API", port: 8083, enabled: true, required: true} {name: "oci-registry", description: "OCI Registry (Zot)", port: 5000, enabled: false, required: false} ] "enterprise" => [ {name: "gitea", description: "Git server", port: 3000, enabled: true, required: true} {name: "postgres", description: "Shared database", port: 5432, enabled: true, required: true} {name: "api-server", description: "REST API", port: 8083, enabled: true, required: true} {name: "harbor", description: "Harbor OCI Registry", port: 5000, enabled: true, required: true} {name: "kms", description: "Cosmian KMS", port: 9998, enabled: true, required: true} {name: "prometheus", description: "Metrics", port: 9090, enabled: true, required: true} {name: "grafana", description: "Dashboards", port: 3001, enabled: true, required: true} {name: "loki", description: "Log aggregation", port: 3100, enabled: true, required: true} {name: "nginx", description: "Reverse proxy", port: 80, enabled: true, required: true} ] _ => [] } $base_services | append $mode_services } export def save-deployment-config [config: record]: nothing -> path { let timestamp = (date now | format date "%Y%m%d_%H%M%S") let config_dir = $env.PWD | path join "configs" mkdir $config_dir let config_file = $config_dir | path join $"deployment_($timestamp).toml" $config | to toml | save -f $config_file $config_file } export def load-config-from-file [config_path: path]: nothing -> record { if not ($config_path | path exists) { error make {msg: $"Config file not found: ($config_path)"} } open $config_path | from toml } export def validate-deployment-config [ config: record --strict ]: nothing -> record { let required_fields = ["platform", "mode", "domain", "services"] mut errors = [] for field in $required_fields { if $field not-in ($config | columns) { $errors = ($errors | append $"Missing required field: ($field)") } } let valid_platforms = ["docker", "podman", "kubernetes", "orbstack"] if "platform" in ($config | columns) and ($config.platform not-in $valid_platforms) { $errors = ($errors | append $"Invalid platform: ($config.platform)") } let valid_modes = ["solo", "multi-user", "cicd", "enterprise"] if "mode" in ($config | columns) and ($config.mode not-in $valid_modes) { $errors = ($errors | append $"Invalid mode: ($config.mode)") } if "services" in ($config | columns) { if ($config.services | is-empty) { $errors = ($errors | append "No services configured") } if $strict { let required_services = $config.services | where required | get name let enabled_services = $config.services | where enabled | get name for req_svc in $required_services { if $req_svc not-in $enabled_services { $errors = ($errors | append $"Required service not enabled: ($req_svc)") } } } } if ($errors | is-empty) { {success: true} } else { { success: false, error: ($errors | str join "; "), errors: $errors } } } export def confirm-deployment [config: record]: nothing -> bool { print " 📋 Deployment Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ " print $"Platform: ($config.platform)" print $"Mode: ($config.mode)" print $"Domain: ($config.domain)" print "" print "Services:" for svc in $config.services { let status = if $svc.enabled { "✅" } else { "⬜" } let req_mark = if $svc.required { "(required)" } else { "" } print $" ($status) ($svc.name):($svc.port) - ($svc.description) ($req_mark)" } print " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ " let response = (input "Proceed with deployment? [y/N]: ") $response =~ "(?i)^y(es)?$" } export def check-deployment-health [config: record]: nothing -> record { print "🏥 Running health checks..." let enabled_services = $config.services | where enabled let failed_services = ($enabled_services | each {|svc| let health_url = $"http://($config.domain):($svc.port)/health" print $" Checking ($svc.name)..." let result = (http get $health_url --max-time 5sec | get status? | default "failed") if $result != "ok" { $svc.name } else { null } } | compact) if ($failed_services | is-empty) { print "✅ All health checks passed" {success: true} } else { print $"❌ Health checks failed for: ($failed_services | str join ', ')" { success: false, error: $"Health checks failed for: ($failed_services | str join ', ')", failed_services: $failed_services } } } export def rollback-deployment [config: record]: nothing -> record { print "🔄 Rolling back deployment..." match $config.platform { "docker" => { rollback-docker $config } "podman" => { rollback-podman $config } "kubernetes" => { rollback-kubernetes $config } "orbstack" => { rollback-orbstack $config } _ => { error make {msg: $"Unsupported platform for rollback: ($config.platform)"} } } } def rollback-docker [config: record]: nothing -> record { let compose_base = get-platform-path "docker-compose" let base_file = $compose_base | path join "docker-compose.yaml" let result = (do --ignore-errors { ^docker-compose -f $base_file down --volumes } | complete) if $result.exit_code == 0 { print "✅ Docker deployment rolled back successfully" {success: true, platform: "docker"} } else { {success: false, platform: "docker", error: $result.stderr} } } def rollback-podman [config: record]: nothing -> record { let compose_base = get-platform-path "docker-compose" let base_file = $compose_base | path join "docker-compose.yaml" let result = (do --ignore-errors { ^podman-compose -f $base_file down --volumes } | complete) if $result.exit_code == 0 { print "✅ Podman deployment rolled back successfully" {success: true, platform: "podman"} } else { {success: false, platform: "podman", error: $result.stderr} } } def rollback-kubernetes [config: record]: nothing -> record { let namespace = "provisioning-platform" let result = (do --ignore-errors { ^kubectl delete namespace $namespace } | complete) if $result.exit_code == 0 { print "✅ Kubernetes deployment rolled back successfully" {success: true, platform: "kubernetes"} } else { {success: false, platform: "kubernetes", error: $result.stderr} } } def rollback-orbstack [config: record]: nothing -> record { rollback-docker $config | update platform "orbstack" } export def check-platform-availability [platform: string]: nothing -> record { match $platform { "docker" => { {platform: "docker", available: (which docker | is-not-empty)} } "podman" => { {platform: "podman", available: (which podman | is-not-empty)} } "kubernetes" => { {platform: "kubernetes", available: (which kubectl | is-not-empty)} } "orbstack" => { {platform: "orbstack", available: (which orb | is-not-empty)} } _ => { {platform: $platform, available: false} } } } export def generate-secrets [config: record]: nothing -> record { print "🔐 Generating secrets..." { jwt_secret: (random chars -l 64) postgres_password: (random chars -l 32) admin_password: (random chars -l 16) api_key: (random chars -l 48) encryption_key: (random chars -l 32) } } export def create-deployment-manifests [config: record, secrets: record]: nothing -> path { let manifests_dir = $env.PWD | path join "manifests" mkdir $manifests_dir let secrets_file = $manifests_dir | path join "secrets.toml" $secrets | to toml | save -f $secrets_file print $"📝 Secrets saved to: ($secrets_file)" $manifests_dir } def get-platform-path [subpath: string = ""]: nothing -> path { let base_path = $env.PWD | path dirname | path dirname if $subpath == "" { $base_path } else { $base_path | path join $subpath } } export def get-installer-path []: nothing -> path { let installer_dir = $env.PWD | path dirname let installer_name = if $nu.os-info.name == "windows" { "provisioning-installer.exe" } else { "provisioning-installer" } let release_path = $installer_dir | path join "target" "release" $installer_name let debug_path = $installer_dir | path join "target" "debug" $installer_name if ($release_path | path exists) { $release_path } else if ($debug_path | path exists) { $debug_path } else { error make { msg: "Installer binary not found" help: "Build with: cargo build --release" } } }