#!/usr/bin/env nu # Rustelo Application Deployment Script # Nushell version of deploy.sh # Handles deployment of the Rustelo application in various environments def main [...args] { # Default configuration let mut config = { environment: "production", compose_file: "docker-compose.yml", build_args: "", migrate_db: false, backup_db: false, health_check: true, timeout: 300, project_name: "rustelo", docker_registry: "", image_tag: "latest", force_recreate: false, scale_replicas: 1, features: "production", use_default_features: false, debug: false } # Parse command line arguments let parsed = parse_deployment_args $args $config = ($config | merge $parsed.config) let command = $parsed.command print $"(ansi blue)๐Ÿš€ Rustelo Deployment Manager(ansi reset)" # Validate command if ($command | is-empty) { print $"(ansi red)โŒ No command specified(ansi reset)" show_deployment_usage exit 1 } # Validate and set environment $config = (validate_deployment_environment $config) # Check prerequisites check_deployment_prerequisites $config # Set environment variables set_deployment_environment_vars $config # Execute command execute_deployment_command $command $config } # Parse deployment command line arguments def parse_deployment_args [args] { let mut config = {} let mut command = "" let mut i = 0 while $i < ($args | length) { let arg = ($args | get $i) match $arg { "-h" | "--help" => { show_deployment_usage exit 0 } "-e" | "--env" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert environment ($args | get $i)) } } "-f" | "--file" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert compose_file ($args | get $i)) } } "-p" | "--project" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert project_name ($args | get $i)) } } "-t" | "--tag" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert image_tag ($args | get $i)) } } "-r" | "--registry" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert docker_registry ($args | get $i)) } } "-s" | "--scale" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert scale_replicas ($args | get $i | into int)) } } "--migrate" => { $config = ($config | upsert migrate_db true) } "--backup" => { $config = ($config | upsert backup_db true) } "--no-health-check" => { $config = ($config | upsert health_check false) } "--force-recreate" => { $config = ($config | upsert force_recreate true) } "--timeout" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert timeout ($args | get $i | into int)) } } "--build-arg" => { $i = $i + 1 if $i < ($args | length) { let current_args = ($config | get -i build_args | default "") $config = ($config | upsert build_args $"($current_args) --build-arg ($args | get $i)") } } "--features" => { $i = $i + 1 if $i < ($args | length) { $config = ($config | upsert features ($args | get $i)) } } "--default-features" => { $config = ($config | upsert use_default_features true) } "--debug" => { $config = ($config | upsert debug true) } $cmd if not ($cmd | str starts-with "-") => { if ($command | is-empty) { $command = $cmd } } _ => { print $"(ansi red)โŒ Unknown option: ($arg)(ansi reset)" show_deployment_usage exit 1 } } $i = $i + 1 } {config: $config, command: $command} } # Show deployment usage information def show_deployment_usage [] { print "Usage: nu deploy.nu [OPTIONS] COMMAND" print "" print "Commands:" print " deploy Deploy the application" print " stop Stop the application" print " restart Restart the application" print " status Show deployment status" print " logs Show application logs" print " scale Scale application replicas" print " backup Create database backup" print " migrate Run database migrations" print " rollback Rollback to previous version" print " health Check application health" print " update Update application to latest version" print " clean Clean up unused containers and images" print "" print "Options:" print " -e, --env ENV Environment (dev|staging|production) [default: production]" print " -f, --file FILE Docker compose file [default: docker-compose.yml]" print " -p, --project PROJECT Project name [default: rustelo]" print " -t, --tag TAG Docker image tag [default: latest]" print " -r, --registry REGISTRY Docker registry URL" print " -s, --scale REPLICAS Number of replicas [default: 1]" print " --migrate Run database migrations before deployment" print " --backup Create database backup before deployment" print " --no-health-check Skip health check after deployment" print " --force-recreate Force recreation of containers" print " --timeout SECONDS Deployment timeout [default: 300]" print " --build-arg ARG Docker build arguments" print " --features FEATURES Cargo features to enable [default: production]" print " --default-features Use default features instead of custom" print " --debug Enable debug output" print " -h, --help Show this help message" print "" print "Examples:" print " nu deploy.nu deploy # Deploy production" print " nu deploy.nu deploy -e staging # Deploy staging" print " nu deploy.nu deploy --migrate --backup # Deploy with migration and backup" print " nu deploy.nu scale -s 3 # Scale to 3 replicas" print " nu deploy.nu logs # Show logs" print " nu deploy.nu health # Check health status" print " nu deploy.nu deploy --features \"auth,metrics\" # Deploy with specific features" print " nu deploy.nu deploy --default-features # Deploy with all default features" } # Validate deployment environment def validate_deployment_environment [config] { let environment = ($config | get environment) let mut updated_config = $config match $environment { "dev" | "development" => { $updated_config = ($updated_config | upsert environment "development") $updated_config = ($updated_config | upsert compose_file "docker-compose.yml") } "staging" => { $updated_config = ($updated_config | upsert environment "staging") $updated_config = ($updated_config | upsert compose_file "docker-compose.staging.yml") } "prod" | "production" => { $updated_config = ($updated_config | upsert environment "production") $updated_config = ($updated_config | upsert compose_file "docker-compose.yml") } _ => { print $"(ansi red)โŒ Invalid environment: ($environment)(ansi reset)" print $"(ansi red)Valid environments: dev, staging, production(ansi reset)" exit 1 } } $updated_config } # Check deployment prerequisites def check_deployment_prerequisites [config] { print $"(ansi blue)๐Ÿ” Checking prerequisites...(ansi reset)" # Check if Docker is installed and running try { docker --version | ignore } catch { print $"(ansi red)โŒ Docker is not installed or not in PATH(ansi reset)" exit 1 } try { docker info | ignore } catch { print $"(ansi red)โŒ Docker daemon is not running(ansi reset)" exit 1 } # Check if Docker Compose is installed try { docker-compose --version | ignore } catch { print $"(ansi red)โŒ Docker Compose is not installed or not in PATH(ansi reset)" exit 1 } # Check if compose file exists let compose_file = ($config | get compose_file) if not ($compose_file | path exists) { print $"(ansi red)โŒ Compose file not found: ($compose_file)(ansi reset)" exit 1 } print $"(ansi green)โœ… Prerequisites check passed(ansi reset)" } # Set deployment environment variables def set_deployment_environment_vars [config] { $env.COMPOSE_PROJECT_NAME = ($config | get project_name) $env.DOCKER_REGISTRY = ($config | get docker_registry) $env.IMAGE_TAG = ($config | get image_tag) $env.ENVIRONMENT = ($config | get environment) # Source environment-specific variables let env_file = $".env.($config | get environment)" let default_env_file = ".env" if ($env_file | path exists) { print $"(ansi blue)๐Ÿ“„ Loading environment variables from ($env_file)(ansi reset)" # Note: Nushell doesn't have direct source equivalent, would need custom implementation } else if ($default_env_file | path exists) { print $"(ansi blue)๐Ÿ“„ Loading environment variables from ($default_env_file)(ansi reset)" # Note: Nushell doesn't have direct source equivalent, would need custom implementation } if ($config | get debug) { print $"(ansi blue)๐Ÿ› Environment variables set:(ansi reset)" print $"(ansi blue) COMPOSE_PROJECT_NAME=($env.COMPOSE_PROJECT_NAME)(ansi reset)" print $"(ansi blue) DOCKER_REGISTRY=($env.DOCKER_REGISTRY)(ansi reset)" print $"(ansi blue) IMAGE_TAG=($env.IMAGE_TAG)(ansi reset)" print $"(ansi blue) ENVIRONMENT=($env.ENVIRONMENT)(ansi reset)" print $"(ansi blue) FEATURES=($config | get features)(ansi reset)" print $"(ansi blue) USE_DEFAULT_FEATURES=($config | get use_default_features)(ansi reset)" } } # Execute deployment command def execute_deployment_command [command, config] { match $command { "deploy" => { deployment_build_images $config deployment_create_backup $config deployment_run_migrations $config deployment_deploy_application $config deployment_wait_for_health $config deployment_show_status $config } "stop" => { deployment_stop_application $config } "restart" => { deployment_restart_application $config deployment_wait_for_health $config } "status" => { deployment_show_status $config } "logs" => { deployment_show_logs $config } "scale" => { deployment_scale_application $config } "backup" => { deployment_create_backup $config } "migrate" => { deployment_run_migrations $config } "rollback" => { deployment_rollback_application $config } "health" => { deployment_check_health $config } "update" => { deployment_update_application $config deployment_wait_for_health $config } "clean" => { deployment_cleanup $config } _ => { print $"(ansi red)โŒ Unknown command: ($command)(ansi reset)" show_deployment_usage exit 1 } } } # Build Docker images def deployment_build_images [config] { print $"(ansi blue)๐Ÿ—๏ธ Building Docker images...(ansi reset)" let compose_file = ($config | get compose_file) let mut build_cmd = ["docker-compose", "-f", $compose_file, "build"] # Add build arguments let build_args = ($config | get build_args) if not ($build_args | is-empty) { $build_cmd = ($build_cmd | append ($build_args | split row " ")) } # Add feature arguments if not ($config | get use_default_features) { $build_cmd = ($build_cmd | append ["--build-arg", $"CARGO_FEATURES=($config | get features)", "--build-arg", "NO_DEFAULT_FEATURES=true"]) } else { $build_cmd = ($build_cmd | append ["--build-arg", "CARGO_FEATURES=", "--build-arg", "NO_DEFAULT_FEATURES=false"]) } if ($config | get debug) { print $"(ansi blue)๐Ÿ› Build command: ($build_cmd | str join ' ')(ansi reset)" } try { run-external ($build_cmd | first) ..($build_cmd | skip 1) print $"(ansi green)โœ… Docker images built successfully(ansi reset)" } catch { print $"(ansi red)โŒ Failed to build Docker images(ansi reset)" exit 1 } } # Create database backup def deployment_create_backup [config] { if ($config | get backup_db) { print $"(ansi blue)๐Ÿ’พ Creating database backup...(ansi reset)" let compose_file = ($config | get compose_file) let backup_file = $"backup_(date now | format date %Y%m%d_%H%M%S).sql" try { docker-compose -f $compose_file exec -T db pg_dump -U postgres rustelo_prod | save $backup_file print $"(ansi green)โœ… Database backup created: ($backup_file)(ansi reset)" } catch { print $"(ansi red)โŒ Failed to create database backup(ansi reset)" exit 1 } } } # Run database migrations def deployment_run_migrations [config] { if ($config | get migrate_db) { print $"(ansi blue)๐Ÿš€ Running database migrations...(ansi reset)" let compose_file = ($config | get compose_file) try { docker-compose -f $compose_file run --rm migrate print $"(ansi green)โœ… Database migrations completed successfully(ansi reset)" } catch { print $"(ansi red)โŒ Database migrations failed(ansi reset)" exit 1 } } } # Deploy application def deployment_deploy_application [config] { print $"(ansi blue)๐Ÿš€ Deploying application...(ansi reset)" let compose_file = ($config | get compose_file) let mut compose_cmd = ["docker-compose", "-f", $compose_file, "up", "-d"] if ($config | get force_recreate) { $compose_cmd = ($compose_cmd | append "--force-recreate") } let scale_replicas = ($config | get scale_replicas) if $scale_replicas > 1 { $compose_cmd = ($compose_cmd | append ["--scale", $"app=($scale_replicas)"]) } if ($config | get debug) { print $"(ansi blue)๐Ÿ› Deploy command: ($compose_cmd | str join ' ')(ansi reset)" } try { run-external ($compose_cmd | first) ..($compose_cmd | skip 1) print $"(ansi green)โœ… Application deployed successfully(ansi reset)" } catch { print $"(ansi red)โŒ Failed to deploy application(ansi reset)" exit 1 } } # Wait for application health def deployment_wait_for_health [config] { if ($config | get health_check) { print $"(ansi blue)๐Ÿฅ Waiting for application to be healthy...(ansi reset)" let start_time = (date now | into int) let health_url = "http://localhost:3030/health" let timeout = ($config | get timeout) loop { let current_time = (date now | into int) let elapsed = ($current_time - $start_time) if $elapsed > $timeout { print $"(ansi red)โŒ Health check timeout after ($timeout) seconds(ansi reset)" exit 1 } try { let response = (http get $health_url) if ($response | get -i status | default "" | str contains "healthy") { print $"(ansi green)โœ… Application is healthy(ansi reset)" break } } catch { # Health check failed, continue retrying } if ($config | get debug) { print $"(ansi blue)๐Ÿ› Health check failed, retrying in 5 seconds... (($elapsed)s elapsed)(ansi reset)" } sleep 5sec } } } # Show deployment status def deployment_show_status [config] { let compose_file = ($config | get compose_file) print $"(ansi blue)๐Ÿ“Š Deployment status:(ansi reset)" try { docker-compose -f $compose_file ps } catch { print $"(ansi yellow)โš ๏ธ Failed to get container status(ansi reset)" } print $"(ansi blue)๐Ÿ“ˆ Container resource usage:(ansi reset)" try { docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" } catch { print $"(ansi yellow)โš ๏ธ Failed to get resource usage(ansi reset)" } } # Show application logs def deployment_show_logs [config] { let compose_file = ($config | get compose_file) try { docker-compose -f $compose_file logs } catch { print $"(ansi red)โŒ Failed to retrieve logs(ansi reset)" exit 1 } } # Scale application def deployment_scale_application [config] { let scale_replicas = ($config | get scale_replicas) let compose_file = ($config | get compose_file) print $"(ansi blue)๐Ÿ“ Scaling application to ($scale_replicas) replicas...(ansi reset)" try { docker-compose -f $compose_file up -d --scale $"app=($scale_replicas)" print $"(ansi green)โœ… Application scaled successfully(ansi reset)" } catch { print $"(ansi red)โŒ Failed to scale application(ansi reset)" exit 1 } } # Stop application def deployment_stop_application [config] { let compose_file = ($config | get compose_file) print $"(ansi blue)๐Ÿ›‘ Stopping application...(ansi reset)" try { docker-compose -f $compose_file down print $"(ansi green)โœ… Application stopped successfully(ansi reset)" } catch { print $"(ansi red)โŒ Failed to stop application(ansi reset)" exit 1 } } # Restart application def deployment_restart_application [config] { let compose_file = ($config | get compose_file) print $"(ansi blue)๐Ÿ”„ Restarting application...(ansi reset)" try { docker-compose -f $compose_file restart print $"(ansi green)โœ… Application restarted successfully(ansi reset)" } catch { print $"(ansi red)โŒ Failed to restart application(ansi reset)" exit 1 } } # Check application health def deployment_check_health [config] { print $"(ansi blue)๐Ÿฅ Checking application health...(ansi reset)" let health_url = "http://localhost:3030/health" try { let health_response = (http get $health_url) if ($health_response | get -i status | default "" | str contains "healthy") { print $"(ansi green)โœ… Application is healthy(ansi reset)" print $"(ansi blue)๐Ÿ“‹ Health details:(ansi reset)" $health_response | table } else { print $"(ansi red)โŒ Application is not healthy(ansi reset)" $health_response | table exit 1 } } catch { print $"(ansi red)โŒ Failed to check application health(ansi reset)" exit 1 } } # Update application def deployment_update_application [config] { let compose_file = ($config | get compose_file) print $"(ansi blue)๐Ÿ”„ Updating application...(ansi reset)" try { # Pull latest images docker-compose -f $compose_file pull # Restart with new images docker-compose -f $compose_file up -d --force-recreate print $"(ansi green)โœ… Application updated successfully(ansi reset)" } catch { print $"(ansi red)โŒ Failed to update application(ansi reset)" exit 1 } } # Rollback application def deployment_rollback_application [config] { print $"(ansi yellow)โš ๏ธ Rollback functionality not implemented yet(ansi reset)" print $"(ansi yellow)๐Ÿ’ก Please manually specify the desired image tag and redeploy(ansi reset)" } # Cleanup unused containers and images def deployment_cleanup [config] { print $"(ansi blue)๐Ÿงน Cleaning up unused containers and images...(ansi reset)" try { # Remove stopped containers docker container prune -f # Remove unused images docker image prune -f # Remove unused volumes docker volume prune -f # Remove unused networks docker network prune -f print $"(ansi green)โœ… Cleanup completed(ansi reset)" } catch { print $"(ansi red)โŒ Cleanup failed(ansi reset)" exit 1 } }