website-htmx-rustelo-code/scripts/build/deploy.nu
2026-07-10 03:44:13 +01:00

637 lines
No EOL
22 KiB
Text
Executable file

#!/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
}
}