#!/usr/bin/env nu # Deploy Provisioning Platform # Complete platform deployment automation def main [ --mode: string = "solo" # Deployment mode: solo, multi-user, cicd, enterprise --env-file: string = ".env" # Environment file path --pull # Pull latest images before deploy --build # Build images from source --clean # Clean volumes before deploy (WARNING: data loss) --wait: int = 300 # Wait timeout for health checks (seconds) --skip-health-check # Skip health checks after deploy --dry-run # Show what would be deployed without deploying ] { print $"(ansi green_bold)Provisioning Platform Deployment(ansi reset)" print $"Mode: ($mode)" print "" # Validate mode let valid_modes = ["solo", "multi-user", "cicd", "enterprise"] if $mode not-in $valid_modes { print $"(ansi red_bold)Error:(ansi reset) Invalid mode '($mode)'" print $"Valid modes: (valid_modes | str join ', ')" return 1 } # Change to platform directory let platform_dir = $env.FILE_PWD | path dirname cd $platform_dir # Check prerequisites print $"(ansi cyan)Checking prerequisites...(ansi reset)" if not (check_docker) { return 1 } if not (check_docker_compose) { return 1 } # Generate .env if missing if not ($env_file | path exists) { if (".env.example" | path exists) { print $"(ansi yellow)Warning:(ansi reset) ($env_file) not found. Creating from .env.example" cp .env.example $env_file print $"(ansi yellow)Please edit ($env_file) and set required secrets before deploying!(ansi reset)" if not $dry_run { print "" print "Press Enter to continue after editing .env, or Ctrl+C to cancel..." input } } else { print $"(ansi red_bold)Error:(ansi reset) Neither ($env_file) nor .env.example found" return 1 } } # Validate environment file print $"(ansi cyan)Validating environment configuration...(ansi reset)" if not (validate_env_file $env_file) { return 1 } # Build compose file list let compose_files = build_compose_file_list $mode print $"(ansi cyan)Compose files:(ansi reset)" for file in $compose_files { print $" - ($file)" } print "" if $dry_run { print $"(ansi yellow)DRY RUN: Would deploy with these settings(ansi reset)" return 0 } # Clean volumes if requested if $clean { print $"(ansi yellow_bold)WARNING: --clean will delete all data volumes!(ansi reset)" print "Type 'yes' to confirm: " let confirm = input if $confirm != "yes" { print "Cancelled." return 0 } print $"(ansi cyan)Cleaning volumes...(ansi reset)" docker_compose_down $compose_files --volumes } # Pull images if requested if $pull { print $"(ansi cyan)Pulling latest images...(ansi reset)" docker_compose_pull $compose_files } # Build images if requested if $build { print $"(ansi cyan)Building images from source...(ansi reset)" docker_compose_build $compose_files } # Create networks print $"(ansi cyan)Creating networks...(ansi reset)" create_networks # Deploy services print $"(ansi cyan)Deploying services...(ansi reset)" docker_compose_up $compose_files # Wait for health checks if not $skip_health_check { print "" print $"(ansi cyan)Waiting for services to be healthy (timeout: ($wait)s)...(ansi reset)" if not (wait_for_health $wait) { print $"(ansi red_bold)Error:(ansi reset) Some services failed health checks" print "Check logs with: docker compose logs" return 1 } } # Display access URLs print "" print $"(ansi green_bold)✓ Platform deployed successfully!(ansi reset)" print "" display_access_urls $mode return 0 } # Check if Docker is installed and running def check_docker [] { try { docker ps | complete | get exit_code | $in == 0 print $"(ansi green)✓ Docker is running(ansi reset)" true } catch { print $"(ansi red_bold)✗ Docker is not running or not installed(ansi reset)" print "Please install Docker and ensure it's running" false } } # Check if docker compose is installed def check_docker_compose [] { try { docker compose version | complete | get exit_code | $in == 0 print $"(ansi green)✓ docker compose is installed(ansi reset)" true } catch { print $"(ansi red_bold)✗ docker compose is not installed(ansi reset)" print "Please install docker compose plugin" false } } # Build list of compose files for mode def build_compose_file_list [mode: string] { mut files = ["docker-compose.yaml"] if $mode == "solo" { $files = ($files | append "docker-compose/docker-compose.solo.yaml") } else if $mode == "multi-user" { $files = ($files | append "docker-compose/docker-compose.multi-user.yaml") } else if $mode == "cicd" { $files = ($files | append "docker-compose/docker-compose.multi-user.yaml") $files = ($files | append "docker-compose/docker-compose.cicd.yaml") } else if $mode == "enterprise" { $files = ($files | append "docker-compose/docker-compose.multi-user.yaml") $files = ($files | append "docker-compose/docker-compose.cicd.yaml") $files = ($files | append "docker-compose/docker-compose.enterprise.yaml") } $files } # Validate environment file def validate_env_file [file: string] { let content = (open $file) # Check for placeholder secrets let placeholders = [ "CHANGE_ME_RANDOM_SECRET_HERE", "CHANGE_ME_GITEA_SECRET_KEY", "CHANGE_ME_ADMIN_PASSWORD", "CHANGE_ME_POSTGRES_PASSWORD", "CHANGE_ME_API_SERVER_JWT_SECRET", "CHANGE_ME_GRAFANA_PASSWORD" ] mut has_placeholder = false for placeholder in $placeholders { if ($content | str contains $placeholder) { print $"(ansi yellow)Warning:(ansi reset) Found placeholder '($placeholder)' in ($file)" $has_placeholder = true } } if $has_placeholder { print $"(ansi yellow)Please replace all CHANGE_ME placeholders with actual secrets(ansi reset)" print "You can generate secrets with: openssl rand -base64 32" } true } # Create Docker networks def create_networks [] { try { docker network create provisioning-net | ignore } catch {} try { docker network create provisioning-net-frontend | ignore } catch {} try { docker network create provisioning-net-backend | ignore } catch {} try { docker network create provisioning-net-storage | ignore } catch {} } # Run docker compose down def docker_compose_down [files: list, --volumes] { mut cmd = ["docker", "compose"] for file in $files { $cmd = ($cmd | append ["-f", $file]) } $cmd = ($cmd | append "down") if $volumes { $cmd = ($cmd | append "--volumes") } run-external $cmd.0 ...($cmd | skip 1) } # Run docker compose pull def docker_compose_pull [files: list] { mut cmd = ["docker", "compose"] for file in $files { $cmd = ($cmd | append ["-f", $file]) } $cmd = ($cmd | append "pull") run-external $cmd.0 ...($cmd | skip 1) } # Run docker compose build def docker_compose_build [files: list] { mut cmd = ["docker", "compose"] for file in $files { $cmd = ($cmd | append ["-f", $file]) } $cmd = ($cmd | append "build") run-external $cmd.0 ...($cmd | skip 1) } # Run docker compose up def docker_compose_up [files: list] { mut cmd = ["docker", "compose"] for file in $files { $cmd = ($cmd | append ["-f", $file]) } $cmd = ($cmd | append ["up", "-d"]) run-external $cmd.0 ...($cmd | skip 1) } # Wait for services to be healthy def wait_for_health [timeout: int] { let start = (date now) let end = ($start + ($timeout * 1sec)) mut all_healthy = false while (date now) < $end { let containers = (docker ps --filter "name=provisioning-" --format "{{.Names}}") if ($containers | is-empty) { print $"(ansi red)No containers running(ansi reset)" sleep 2sec continue } mut healthy_count = 0 mut total_count = 0 for container in ($containers | lines) { $total_count = $total_count + 1 let health = (docker inspect $container | from json | get 0.State.Health?.Status? | default "unknown") if $health == "healthy" or $health == "unknown" { $healthy_count = $healthy_count + 1 print $" (ansi green)✓(ansi reset) ($container): ($health)" } else { print $" (ansi yellow)○(ansi reset) ($container): ($health)" } } if $healthy_count == $total_count { $all_healthy = true break } print $"Healthy: ($healthy_count)/($total_count) - Retrying in 5s..." sleep 5sec print "" } $all_healthy } # Display access URLs def display_access_urls [mode: string] { print $"(ansi cyan_bold)Access URLs:(ansi reset)" print $" Orchestrator: http://localhost:8080" print $" Control Center: http://localhost:8081" if $mode in ["multi-user", "cicd", "enterprise"] { print $" Gitea: http://localhost:3000" } print $" OCI Registry: http://localhost:5000" print $" Extension Registry: http://localhost:8082" if $mode in ["cicd", "enterprise"] { print $" API Server: http://localhost:8083" } if $mode == "enterprise" { print $" Prometheus: http://localhost:9090" print $" Grafana: http://localhost:3001" print $" Kibana: http://localhost:5601" } print "" print $"(ansi cyan_bold)Useful commands:(ansi reset)" print $" View logs: docker compose logs -f" print $" Check status: docker compose ps" print $" Stop platform: docker compose down" print $" Restart service: docker compose restart " }