Rustelo/scripts/deploy.sh

564 lines
15 KiB
Bash
Raw Permalink Normal View History

2025-07-07 23:53:50 +01:00
#!/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 "$@"