#!/usr/bin/env nu # Platform-Specific Deployment Functions # # Implements deployment logic for each supported platform: # - Docker (docker-compose) # - Podman (podman-compose) # - Kubernetes (kubectl) # - OrbStack (orb CLI) use helpers.nu * # Deploy to Docker using docker-compose # # @param config: Deployment configuration record # @returns: Deployment result record export def deploy-docker [config: record]: nothing -> record { print $"🐳 Deploying to Docker..." # Determine compose file based on mode let compose_base = get-platform-path "docker-compose" let compose_overlay = match $config.mode { "solo" => "docker-compose.solo.yaml" "multi-user" => "docker-compose.multi-user.yaml" "cicd" => "docker-compose.cicd.yaml" "enterprise" => "docker-compose.enterprise.yaml" _ => "docker-compose.solo.yaml" } let base_file = $compose_base | path join "docker-compose.yaml" let overlay_file = $compose_base | path join $compose_overlay # Validate compose files exist if not ($base_file | path exists) { error make {msg: $"Compose file not found: ($base_file)"} } if not ($overlay_file | path exists) { error make {msg: $"Overlay file not found: ($overlay_file)"} } # Generate .env file with configuration let env_file = generate-docker-env $config print $"πŸ“ Generated environment file: ($env_file)" # Pull images first print "πŸ“¦ Pulling Docker images..." try { ^docker-compose -f $base_file -f $overlay_file pull } catch {|err| return { success: false platform: "docker" error: $"Failed to pull images: ($err.msg)" timestamp: (date now) } } # Start services print "πŸš€ Starting services..." try { ^docker-compose -f $base_file -f $overlay_file up -d { success: true platform: "docker" compose_files: [$base_file, $overlay_file] env_file: $env_file message: "Docker deployment successful" timestamp: (date now) } } catch {|err| { success: false platform: "docker" error: $"Failed to start services: ($err.msg)" timestamp: (date now) } } } # Deploy to Podman using podman-compose # # @param config: Deployment configuration record # @returns: Deployment result record export def deploy-podman [config: record]: nothing -> record { print $"🦭 Deploying to Podman..." # Check if podman-compose is available let has_podman_compose = (which podman-compose | is-not-empty) if not $has_podman_compose { error make { msg: "podman-compose not found" help: "Install with: pip install podman-compose" } } # Determine compose file based on mode let compose_base = get-platform-path "docker-compose" let compose_overlay = match $config.mode { "solo" => "docker-compose.solo.yaml" "multi-user" => "docker-compose.multi-user.yaml" "cicd" => "docker-compose.cicd.yaml" "enterprise" => "docker-compose.enterprise.yaml" _ => "docker-compose.solo.yaml" } let base_file = $compose_base | path join "docker-compose.yaml" let overlay_file = $compose_base | path join $compose_overlay # Generate .env file let env_file = generate-docker-env $config print $"πŸ“ Generated environment file: ($env_file)" # Pull images print "πŸ“¦ Pulling Podman images..." try { ^podman-compose -f $base_file -f $overlay_file pull } catch {|err| return { success: false platform: "podman" error: $"Failed to pull images: ($err.msg)" timestamp: (date now) } } # Start services print "πŸš€ Starting services..." try { ^podman-compose -f $base_file -f $overlay_file up -d { success: true platform: "podman" compose_files: [$base_file, $overlay_file] env_file: $env_file message: "Podman deployment successful" timestamp: (date now) } } catch {|err| { success: false platform: "podman" error: $"Failed to start services: ($err.msg)" timestamp: (date now) } } } # Deploy to Kubernetes using kubectl # # @param config: Deployment configuration record # @returns: Deployment result record export def deploy-kubernetes [config: record]: nothing -> record { print $"☸️ Deploying to Kubernetes..." # Create namespace if it doesn't exist let namespace = "provisioning-platform" print $"πŸ“¦ Creating namespace: ($namespace)" try { ^kubectl create namespace $namespace --dry-run=client -o yaml | ^kubectl apply -f - } catch {|err| return { success: false platform: "kubernetes" error: $"Failed to create namespace: ($err.msg)" timestamp: (date now) } } # Generate Kubernetes manifests print "πŸ“ Generating Kubernetes manifests..." let manifests_dir = generate-k8s-manifests $config $namespace # Apply manifests print $"πŸš€ Applying manifests from: ($manifests_dir)" try { ^kubectl apply -f $manifests_dir -n $namespace { success: true platform: "kubernetes" namespace: $namespace manifests_dir: $manifests_dir message: "Kubernetes deployment successful" timestamp: (date now) } } catch {|err| { success: false platform: "kubernetes" error: $"Failed to apply manifests: ($err.msg)" timestamp: (date now) } } } # Deploy to OrbStack using orb CLI # # @param config: Deployment configuration record # @returns: Deployment result record export def deploy-orbstack [config: record]: nothing -> record { print $"🌐 Deploying to OrbStack..." # Check if orb CLI is available let has_orb = (which orb | is-not-empty) if not $has_orb { error make { msg: "orb CLI not found" help: "OrbStack must be installed with CLI tools enabled" } } # OrbStack uses Docker Compose under the hood # but with orb-specific optimizations let compose_base = get-platform-path "docker-compose" let compose_overlay = match $config.mode { "solo" => "docker-compose.solo.yaml" "multi-user" => "docker-compose.multi-user.yaml" "cicd" => "docker-compose.cicd.yaml" "enterprise" => "docker-compose.enterprise.yaml" _ => "docker-compose.solo.yaml" } let base_file = $compose_base | path join "docker-compose.yaml" let overlay_file = $compose_base | path join $compose_overlay # Generate .env file let env_file = generate-docker-env $config print $"πŸ“ Generated environment file: ($env_file)" # Use docker-compose via OrbStack's Docker print "πŸ“¦ Pulling images via OrbStack..." try { ^docker-compose -f $base_file -f $overlay_file pull } catch {|err| return { success: false platform: "orbstack" error: $"Failed to pull images: ($err.msg)" timestamp: (date now) } } # Start services print "πŸš€ Starting services..." try { ^docker-compose -f $base_file -f $overlay_file up -d { success: true platform: "orbstack" compose_files: [$base_file, $overlay_file] env_file: $env_file message: "OrbStack deployment successful" timestamp: (date now) } } catch {|err| { success: false platform: "orbstack" error: $"Failed to start services: ($err.msg)" timestamp: (date now) } } } # Generate Docker/Podman environment file # # @param config: Deployment configuration record # @returns: Path to generated .env file def generate-docker-env [config: record]: nothing -> path { let env_content = [ $"# Generated by provisioning-installer" $"# Deployment mode: ($config.mode)" $"# Platform: ($config.platform)" $"# Generated at: (date now)" "" $"PROVISIONING_MODE=($config.mode)" $"PROVISIONING_DOMAIN=($config.domain)" $"PROVISIONING_PLATFORM=($config.platform)" "" "# Service Configuration" ] # Add service-specific environment variables let service_vars = $config.services | each {|svc| let port_var = $"($svc.name | str upcase | str replace '-' '_')_PORT=($svc.port)" let enabled_var = $"($svc.name | str upcase | str replace '-' '_')_ENABLED=($svc.enabled)" [$port_var, $enabled_var] } | flatten let full_content = ($env_content | append $service_vars | str join "\n") # Save to .env file let env_file = get-platform-path "docker-compose" | path join ".env" $full_content | save -f $env_file $env_file } # Generate Kubernetes manifests from templates # # @param config: Deployment configuration record # @param namespace: Target namespace # @returns: Path to manifests directory def generate-k8s-manifests [config: record, namespace: string]: nothing -> path { let manifests_dir = $env.PWD | path join "k8s-manifests" # Create directory mkdir $manifests_dir # Generate namespace manifest let ns_manifest = { apiVersion: "v1" kind: "Namespace" metadata: { name: $namespace labels: { provisioning-mode: $config.mode managed-by: "provisioning-installer" } } } $ns_manifest | to yaml | save -f ($manifests_dir | path join "namespace.yaml") # Generate service manifests for each enabled service $config.services | each {|svc| if $svc.enabled { let deployment = generate-k8s-deployment $svc $config $namespace let service = generate-k8s-service $svc $namespace $deployment | to yaml | save -f ($manifests_dir | path join $"deployment-($svc.name).yaml") $service | to yaml | save -f ($manifests_dir | path join $"service-($svc.name).yaml") } } $manifests_dir } # Generate Kubernetes Deployment manifest # # @param service: Service configuration # @param config: Deployment configuration # @param namespace: Target namespace # @returns: Deployment manifest record def generate-k8s-deployment [service: record, config: record, namespace: string]: nothing -> record { { apiVersion: "apps/v1" kind: "Deployment" metadata: { name: $service.name namespace: $namespace labels: { app: $service.name mode: $config.mode } } spec: { replicas: 1 selector: { matchLabels: { app: $service.name } } template: { metadata: { labels: { app: $service.name } } spec: { containers: [{ name: $service.name image: $"ghcr.io/provisioning-platform/($service.name):latest" ports: [{ containerPort: $service.port }] env: [ {name: "PROVISIONING_MODE", value: $config.mode} {name: "PROVISIONING_DOMAIN", value: $config.domain} ] }] } } } } } # Generate Kubernetes Service manifest # # @param service: Service configuration # @param namespace: Target namespace # @returns: Service manifest record def generate-k8s-service [service: record, namespace: string]: nothing -> record { { apiVersion: "v1" kind: "Service" metadata: { name: $service.name namespace: $namespace } spec: { selector: { app: $service.name } ports: [{ port: $service.port targetPort: $service.port protocol: "TCP" }] type: "ClusterIP" } } } # Get platform-specific base path # # @param subpath: Subpath within platform directory # @returns: Full path to platform directory 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 } }