#!/bin/bash # Deployment Utilities and Helper Functions # This script provides common utilities for deployment operations # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Logging functions log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" } error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } info() { echo -e "${CYAN}[INFO]${NC} $1" } # Generate secure JWT secret generate_jwt_secret() { local length=${1:-64} openssl rand -base64 $length | tr -d "=+/" | cut -c1-$length } # Validate JWT secret validate_jwt_secret() { local secret=$1 if [ -z "$secret" ]; then error "JWT secret is empty" return 1 fi if [ ${#secret} -lt 32 ]; then error "JWT secret must be at least 32 characters long" return 1 fi if [[ "$secret" == *"change"* ]] || [[ "$secret" == *"your-"* ]] || [[ "$secret" == *"example"* ]]; then error "JWT secret appears to be a placeholder value" return 1 fi success "JWT secret validation passed" return 0 } # Wait for service to be ready wait_for_service() { local url=$1 local timeout=${2:-300} # 5 minutes default local interval=${3:-10} # 10 seconds default local service_name=${4:-"service"} log "Waiting for $service_name to be ready at $url..." local elapsed=0 while [ $elapsed -lt $timeout ]; do if curl -f -s "$url" > /dev/null 2>&1; then success "$service_name is ready" return 0 fi log "Waiting for $service_name... (${elapsed}s/${timeout}s)" sleep $interval elapsed=$((elapsed + interval)) done error "$service_name failed to become ready within ${timeout}s" return 1 } # Check service health check_service_health() { local url=$1 local service_name=${2:-"service"} local expected_status=${3:-200} log "Checking health of $service_name..." local response local status_code response=$(curl -s -w "%{http_code}" "$url" 2>/dev/null) status_code="${response: -3}" if [ "$status_code" = "$expected_status" ]; then success "$service_name health check passed (HTTP $status_code)" return 0 else error "$service_name health check failed (HTTP $status_code)" return 1 fi } # Run database migrations run_database_migrations() { local database_url=$1 local migration_dir=${2:-"alembic"} log "Running database migrations..." if [ ! -d "$migration_dir" ]; then warning "Migration directory $migration_dir not found, skipping migrations" return 0 fi # Set database URL for alembic export DATABASE_URL="$database_url" # Run migrations if command -v alembic &> /dev/null; then alembic upgrade head success "Database migrations completed" else warning "Alembic not found, skipping migrations" fi } # Initialize database initialize_database() { local database_url=$1 local init_script=${2:-"scripts/init-db.sh"} log "Initializing database..." if [ -f "$init_script" ]; then DATABASE_URL="$database_url" bash "$init_script" success "Database initialization completed" else warning "Database initialization script not found at $init_script" fi } # Backup SQLite database backup_sqlite_database() { local db_path=$1 local backup_dir=${2:-"backups"} local timestamp=$(date +"%Y%m%d_%H%M%S") if [ ! -f "$db_path" ]; then warning "Database file $db_path not found, skipping backup" return 0 fi mkdir -p "$backup_dir" local backup_file="$backup_dir/database_backup_$timestamp.db" log "Creating database backup..." cp "$db_path" "$backup_file" # Compress backup gzip "$backup_file" success "Database backup created: ${backup_file}.gz" } # Restore SQLite database restore_sqlite_database() { local backup_file=$1 local db_path=$2 if [ ! -f "$backup_file" ]; then error "Backup file $backup_file not found" return 1 fi log "Restoring database from backup..." # Handle compressed backups if [[ "$backup_file" == *.gz ]]; then gunzip -c "$backup_file" > "$db_path" else cp "$backup_file" "$db_path" fi success "Database restored from $backup_file" } # Check disk space check_disk_space() { local path=${1:-"."} local min_space_gb=${2:-1} log "Checking disk space..." local available_space available_space=$(df "$path" | awk 'NR==2 {print $4}') local available_gb=$((available_space / 1024 / 1024)) if [ $available_gb -lt $min_space_gb ]; then error "Insufficient disk space: ${available_gb}GB available, ${min_space_gb}GB required" return 1 fi success "Disk space check passed: ${available_gb}GB available" return 0 } # Check memory usage check_memory_usage() { local max_usage_percent=${1:-80} log "Checking memory usage..." local memory_usage memory_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}') if [ "$memory_usage" -gt "$max_usage_percent" ]; then warning "High memory usage: ${memory_usage}%" return 1 fi success "Memory usage check passed: ${memory_usage}%" return 0 } # Clean up old Docker images cleanup_docker_images() { local keep_images=${1:-3} log "Cleaning up old Docker images..." # Remove dangling images docker image prune -f # Remove old images (keep latest N) docker images --format "table {{.Repository}}:{{.Tag}}\t{{.CreatedAt}}" | \ grep -E "(knowledge-assistant|rag)" | \ sort -k2 -r | \ tail -n +$((keep_images + 1)) | \ awk '{print $1}' | \ xargs -r docker rmi -f success "Docker cleanup completed" } # Validate environment file validate_env_file() { local env_file=$1 local required_vars=("${@:2}") if [ ! -f "$env_file" ]; then error "Environment file $env_file not found" return 1 fi log "Validating environment file: $env_file" # Source the file source "$env_file" # Check required variables local missing_vars=() for var in "${required_vars[@]}"; do if [ -z "${!var}" ]; then missing_vars+=("$var") fi done if [ ${#missing_vars[@]} -ne 0 ]; then error "Missing required environment variables: ${missing_vars[*]}" return 1 fi success "Environment file validation passed" return 0 } # Create environment file from template create_env_from_template() { local template_file=$1 local env_file=$2 local auto_generate=${3:-false} if [ ! -f "$template_file" ]; then error "Template file $template_file not found" return 1 fi if [ -f "$env_file" ]; then warning "Environment file $env_file already exists" return 0 fi log "Creating environment file from template..." cp "$template_file" "$env_file" if [ "$auto_generate" = "true" ]; then # Auto-generate JWT secret local jwt_secret jwt_secret=$(generate_jwt_secret) # Replace placeholder values sed -i "s/your-super-secret-jwt-key-change-in-production-minimum-32-chars/$jwt_secret/g" "$env_file" sed -i "s/your-super-secure-jwt-secret-key-change-this-in-production/$jwt_secret/g" "$env_file" success "Environment file created with auto-generated values" else success "Environment file created from template" warning "Please edit $env_file with your configuration" fi } # Monitor deployment progress monitor_deployment() { local platform=$1 local services=("${@:2}") log "Monitoring deployment progress on $platform..." case $platform in railway) for service in "${services[@]}"; do log "Monitoring Railway service: $service" railway logs --service "$service" --tail 50 & done ;; cloudrun) for service in "${services[@]}"; do log "Monitoring Cloud Run service: $service" gcloud logging tail "resource.type=cloud_run_revision AND resource.labels.service_name=$service" & done ;; local) log "Monitoring local Docker containers" docker-compose -f docker-compose.prod.yml logs -f & ;; *) warning "Monitoring not implemented for platform: $platform" ;; esac # Wait for user input to stop monitoring read -p "Press Enter to stop monitoring..." # Kill background jobs jobs -p | xargs -r kill } # Export functions for use in other scripts export -f log error success warning info export -f generate_jwt_secret validate_jwt_secret export -f wait_for_service check_service_health export -f run_database_migrations initialize_database export -f backup_sqlite_database restore_sqlite_database export -f check_disk_space check_memory_usage export -f cleanup_docker_images validate_env_file export -f create_env_from_template monitor_deployment