#!/bin/bash # Rustelo Application Deployment Script # This script handles deployment of the Rustelo application in various environments set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Default values 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 # Function to print colored output print_status() { echo -e "${GREEN}[INFO]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARN]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } print_debug() { if [[ "$DEBUG" == "true" ]]; then echo -e "${BLUE}[DEBUG]${NC} $1" fi } # Function to show usage show_usage() { cat << EOF Usage: $0 [OPTIONS] COMMAND Commands: deploy Deploy the application stop Stop the application restart Restart the application status Show deployment status logs Show application logs scale Scale application replicas backup Create database backup migrate Run database migrations rollback Rollback to previous version health Check application health update Update application to latest version clean Clean up unused containers and images Options: -e, --env ENV Environment (dev|staging|production) [default: production] -f, --file FILE Docker compose file [default: docker-compose.yml] -p, --project PROJECT Project name [default: rustelo] -t, --tag TAG Docker image tag [default: latest] -r, --registry REGISTRY Docker registry URL -s, --scale REPLICAS Number of replicas [default: 1] --migrate Run database migrations before deployment --backup Create database backup before deployment --no-health-check Skip health check after deployment --force-recreate Force recreation of containers --timeout SECONDS Deployment timeout [default: 300] --build-arg ARG Docker build arguments --features FEATURES Cargo features to enable [default: production] --default-features Use default features instead of custom --debug Enable debug output -h, --help Show this help message Examples: $0 deploy # Deploy production $0 deploy -e staging # Deploy staging $0 deploy --migrate --backup # Deploy with migration and backup $0 scale -s 3 # Scale to 3 replicas $0 logs -f # Follow logs $0 health # Check health status $0 deploy --features "auth,metrics" # Deploy with specific features $0 deploy --default-features # Deploy with all default features Environment Variables: DOCKER_REGISTRY Docker registry URL RUSTELO_ENV Environment override COMPOSE_PROJECT_NAME Docker compose project name DATABASE_URL Database connection string DEBUG Enable debug mode EOF } # Function to parse command line arguments parse_args() { while [[ $# -gt 0 ]]; do case $1 in -e|--env) ENVIRONMENT="$2" shift 2 ;; -f|--file) COMPOSE_FILE="$2" shift 2 ;; -p|--project) PROJECT_NAME="$2" shift 2 ;; -t|--tag) IMAGE_TAG="$2" shift 2 ;; -r|--registry) DOCKER_REGISTRY="$2" shift 2 ;; -s|--scale) SCALE_REPLICAS="$2" shift 2 ;; --migrate) MIGRATE_DB=true shift ;; --backup) BACKUP_DB=true shift ;; --no-health-check) HEALTH_CHECK=false shift ;; --force-recreate) FORCE_RECREATE=true shift ;; --timeout) TIMEOUT="$2" shift 2 ;; --build-arg) BUILD_ARGS="$BUILD_ARGS --build-arg $2" shift 2 ;; --features) FEATURES="$2" shift 2 ;; --default-features) USE_DEFAULT_FEATURES=true shift ;; --debug) DEBUG=true shift ;; -h|--help) show_usage exit 0 ;; -*) print_error "Unknown option: $1" show_usage exit 1 ;; *) COMMAND="$1" shift ;; esac done } # Function to validate environment validate_environment() { case $ENVIRONMENT in dev|development) ENVIRONMENT="development" COMPOSE_FILE="docker-compose.yml" ;; staging) ENVIRONMENT="staging" COMPOSE_FILE="docker-compose.staging.yml" ;; prod|production) ENVIRONMENT="production" COMPOSE_FILE="docker-compose.yml" ;; *) print_error "Invalid environment: $ENVIRONMENT" print_error "Valid environments: dev, staging, production" exit 1 ;; esac } # Function to check prerequisites check_prerequisites() { print_status "Checking prerequisites..." # Check if Docker is installed and running if ! command -v docker &> /dev/null; then print_error "Docker is not installed or not in PATH" exit 1 fi if ! docker info &> /dev/null; then print_error "Docker daemon is not running" exit 1 fi # Check if Docker Compose is installed if ! command -v docker-compose &> /dev/null; then print_error "Docker Compose is not installed or not in PATH" exit 1 fi # Check if compose file exists if [[ ! -f "$COMPOSE_FILE" ]]; then print_error "Compose file not found: $COMPOSE_FILE" exit 1 fi print_status "Prerequisites check passed" } # Function to set environment variables set_environment_vars() { export COMPOSE_PROJECT_NAME="${PROJECT_NAME}" export DOCKER_REGISTRY="${DOCKER_REGISTRY}" export IMAGE_TAG="${IMAGE_TAG}" export ENVIRONMENT="${ENVIRONMENT}" # Source environment-specific variables if [[ -f ".env.${ENVIRONMENT}" ]]; then print_status "Loading environment variables from .env.${ENVIRONMENT}" source ".env.${ENVIRONMENT}" elif [[ -f ".env" ]]; then print_status "Loading environment variables from .env" source ".env" fi print_debug "Environment variables set:" print_debug " COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME}" print_debug " DOCKER_REGISTRY=${DOCKER_REGISTRY}" print_debug " IMAGE_TAG=${IMAGE_TAG}" print_debug " ENVIRONMENT=${ENVIRONMENT}" print_debug " FEATURES=${FEATURES}" print_debug " USE_DEFAULT_FEATURES=${USE_DEFAULT_FEATURES}" } # Function to build Docker images build_images() { print_status "Building Docker images..." local build_cmd="docker-compose -f $COMPOSE_FILE build" if [[ -n "$BUILD_ARGS" ]]; then build_cmd="$build_cmd $BUILD_ARGS" fi # Add feature arguments to build args if [[ "$USE_DEFAULT_FEATURES" == "false" ]]; then build_cmd="$build_cmd --build-arg CARGO_FEATURES=\"$FEATURES\" --build-arg NO_DEFAULT_FEATURES=\"true\"" else build_cmd="$build_cmd --build-arg CARGO_FEATURES=\"\" --build-arg NO_DEFAULT_FEATURES=\"false\"" fi if [[ "$DEBUG" == "true" ]]; then print_debug "Build command: $build_cmd" fi if ! $build_cmd; then print_error "Failed to build Docker images" exit 1 fi print_status "Docker images built successfully" } # Function to create database backup create_backup() { if [[ "$BACKUP_DB" == "true" ]]; then print_status "Creating database backup..." local backup_file="backup_$(date +%Y%m%d_%H%M%S).sql" if docker-compose -f "$COMPOSE_FILE" exec -T db pg_dump -U postgres rustelo_prod > "$backup_file"; then print_status "Database backup created: $backup_file" else print_error "Failed to create database backup" exit 1 fi fi } # Function to run database migrations run_migrations() { if [[ "$MIGRATE_DB" == "true" ]]; then print_status "Running database migrations..." if docker-compose -f "$COMPOSE_FILE" run --rm migrate; then print_status "Database migrations completed successfully" else print_error "Database migrations failed" exit 1 fi fi } # Function to deploy application deploy_application() { print_status "Deploying application..." local compose_cmd="docker-compose -f $COMPOSE_FILE up -d" if [[ "$FORCE_RECREATE" == "true" ]]; then compose_cmd="$compose_cmd --force-recreate" fi if [[ "$SCALE_REPLICAS" -gt 1 ]]; then compose_cmd="$compose_cmd --scale app=$SCALE_REPLICAS" fi if [[ "$DEBUG" == "true" ]]; then print_debug "Deploy command: $compose_cmd" fi if ! $compose_cmd; then print_error "Failed to deploy application" exit 1 fi print_status "Application deployed successfully" } # Function to wait for application to be ready wait_for_health() { if [[ "$HEALTH_CHECK" == "true" ]]; then print_status "Waiting for application to be healthy..." local start_time=$(date +%s) local health_url="http://localhost:3030/health" while true; do local current_time=$(date +%s) local elapsed=$((current_time - start_time)) if [[ $elapsed -gt $TIMEOUT ]]; then print_error "Health check timeout after ${TIMEOUT} seconds" exit 1 fi if curl -f -s "$health_url" > /dev/null 2>&1; then print_status "Application is healthy" break fi print_debug "Health check failed, retrying in 5 seconds... (${elapsed}s elapsed)" sleep 5 done fi } # Function to show deployment status show_status() { print_status "Deployment status:" docker-compose -f "$COMPOSE_FILE" ps print_status "Container resource usage:" docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}" } # Function to show logs show_logs() { local follow_flag="" if [[ "$1" == "-f" ]]; then follow_flag="-f" fi docker-compose -f "$COMPOSE_FILE" logs $follow_flag } # Function to scale application scale_application() { print_status "Scaling application to $SCALE_REPLICAS replicas..." if docker-compose -f "$COMPOSE_FILE" up -d --scale app="$SCALE_REPLICAS"; then print_status "Application scaled successfully" else print_error "Failed to scale application" exit 1 fi } # Function to stop application stop_application() { print_status "Stopping application..." if docker-compose -f "$COMPOSE_FILE" down; then print_status "Application stopped successfully" else print_error "Failed to stop application" exit 1 fi } # Function to restart application restart_application() { print_status "Restarting application..." if docker-compose -f "$COMPOSE_FILE" restart; then print_status "Application restarted successfully" else print_error "Failed to restart application" exit 1 fi } # Function to check application health check_health() { print_status "Checking application health..." local health_url="http://localhost:3030/health" if curl -f -s "$health_url" | jq '.status' | grep -q "healthy"; then print_status "Application is healthy" # Show detailed health information curl -s "$health_url" | jq . else print_error "Application is not healthy" exit 1 fi } # Function to update application update_application() { print_status "Updating application..." # Pull latest images docker-compose -f "$COMPOSE_FILE" pull # Restart with new images docker-compose -f "$COMPOSE_FILE" up -d --force-recreate print_status "Application updated successfully" } # Function to rollback application rollback_application() { print_warning "Rollback functionality not implemented yet" print_warning "Please manually specify the desired image tag and redeploy" } # Function to clean up cleanup() { print_status "Cleaning up unused containers and images..." # 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_status "Cleanup completed" } # Main function main() { # Parse command line arguments parse_args "$@" # Validate command if [[ -z "$COMMAND" ]]; then print_error "No command specified" show_usage exit 1 fi # Validate environment validate_environment # Check prerequisites check_prerequisites # Set environment variables set_environment_vars # Execute command case $COMMAND in deploy) build_images create_backup run_migrations deploy_application wait_for_health show_status ;; stop) stop_application ;; restart) restart_application wait_for_health ;; status) show_status ;; logs) show_logs "$@" ;; scale) scale_application ;; backup) create_backup ;; migrate) run_migrations ;; rollback) rollback_application ;; health) check_health ;; update) update_application wait_for_health ;; clean) cleanup ;; *) print_error "Unknown command: $COMMAND" show_usage exit 1 ;; esac } # Run main function main "$@"