prvng_platform/scripts/deploy-platform.nu

359 lines
10 KiB
Plaintext
Raw Normal View History

2025-10-07 10:59:52 +01:00
#!/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<string>, --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<string>] {
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<string>] {
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<string>] {
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 <service>"
}