#!/bin/bash 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 # Configuration ENVIRONMENT=${1:-local} BACKUP_DIR="./backups/migrations/$(date +%Y%m%d_%H%M%S)" ROLLBACK_DIR="./rollback-backups/migrations" # Determine compose file if [ "$ENVIRONMENT" == "production" ]; then COMPOSE_FILE="docker-compose.production.yml" POSTGRES_USER="${POSTGRES_USER:-veza_user}" POSTGRES_DB="${POSTGRES_DB:-veza_db}" else COMPOSE_FILE="docker-compose.yml" POSTGRES_USER="${POSTGRES_USER:-veza_user}" POSTGRES_DB="${POSTGRES_DB:-veza_local}" fi # Function to print colored messages print_info() { echo -e "${BLUE}ℹ️ $1${NC}" } print_success() { echo -e "${GREEN}✅ $1${NC}" } print_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } print_error() { echo -e "${RED}❌ $1${NC}" } # Function to check prerequisites check_prerequisites() { print_info "Checking prerequisites..." command -v docker >/dev/null 2>&1 || { print_error "Docker is required but not installed. Aborting."; exit 1; } command -v docker-compose >/dev/null 2>&1 || { print_error "Docker Compose is required but not installed. Aborting."; exit 1; } if ! docker info >/dev/null 2>&1; then print_error "Docker daemon is not running. Please start Docker and try again."; exit 1; fi if [ ! -f "$COMPOSE_FILE" ]; then print_error "$COMPOSE_FILE not found. Aborting."; exit 1; fi # Check if postgres container is running if ! docker-compose -f "$COMPOSE_FILE" ps postgres | grep -q "Up"; then print_error "PostgreSQL container is not running. Please start it first."; exit 1; fi print_success "Prerequisites check passed" } # Function to create backup create_backup() { print_info "Creating database backup..." mkdir -p "${BACKUP_DIR}" mkdir -p "${ROLLBACK_DIR}" # Backup database print_info "Backing up database: ${POSTGRES_DB}..." docker-compose -f "$COMPOSE_FILE" exec -T postgres pg_dump -U "${POSTGRES_USER}" "${POSTGRES_DB}" > "${BACKUP_DIR}/database.sql" 2>/dev/null || { print_error "Failed to create database backup"; exit 1; } # Save backup to rollback location cp "${BACKUP_DIR}/database.sql" "${ROLLBACK_DIR}/latest.sql" print_success "Backup created at ${BACKUP_DIR}/database.sql" } # Function to check migration status check_migration_status() { print_info "Checking migration status..." # Check if schema_migrations table exists docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -tAc \ "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'schema_migrations');" \ > /dev/null 2>&1 || { print_warning "schema_migrations table does not exist (will be created)" return 0 } # Get list of applied migrations APPLIED_MIGRATIONS=$(docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -tAc \ "SELECT COUNT(*) FROM schema_migrations;" 2>/dev/null || echo "0") print_info "Currently applied migrations: ${APPLIED_MIGRATIONS}" } # Function to run backend API migrations run_backend_migrations() { print_info "Running Backend API migrations (GORM AutoMigrate)..." # Check if backend-api container is running if ! docker-compose -f "$COMPOSE_FILE" ps backend-api | grep -q "Up"; then print_warning "Backend API container is not running. Starting it..." docker-compose -f "$COMPOSE_FILE" up -d backend-api sleep 10 fi # Trigger migrations by restarting the backend-api service # The backend API runs migrations automatically on startup via Database.Initialize() print_info "Restarting backend-api to trigger migrations..." docker-compose -f "$COMPOSE_FILE" restart backend-api || { print_error "Failed to restart backend-api"; return 1; } # Wait for backend to be ready print_info "Waiting for backend-api to be ready..." sleep 15 # Check if backend is healthy if curl -sf "http://localhost:8080/health" >/dev/null 2>&1; then print_success "Backend API migrations completed successfully" return 0 else print_warning "Backend API health check failed (migrations may still be running)" return 0 fi } # Function to run chat server migrations run_chat_server_migrations() { print_info "Checking for Chat Server migrations..." # Check if chat-server has migrations directory if [ ! -d "veza-chat-server/migrations" ]; then print_warning "Chat Server migrations directory not found, skipping" return 0 fi # Check if chat-server uses sqlx migrate if docker-compose -f "$COMPOSE_FILE" exec -T chat-server cargo --version >/dev/null 2>&1; then print_info "Running Chat Server migrations (sqlx migrate)..." # Run sqlx migrate (if available in the container) docker-compose -f "$COMPOSE_FILE" exec -T chat-server \ sqlx migrate run --database-url "${DATABASE_URL}" 2>/dev/null || { print_warning "sqlx migrate not available or migrations already applied" return 0 } print_success "Chat Server migrations completed" else print_warning "Chat Server migrations will be handled by the service on startup" fi } # Function to verify migrations verify_migrations() { print_info "Verifying migrations..." # Check database connection if ! docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "SELECT 1;" >/dev/null 2>&1; then print_error "Failed to connect to database"; return 1; fi # Check if schema_migrations table exists TABLE_EXISTS=$(docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -tAc \ "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'schema_migrations');" 2>/dev/null || echo "false") if [ "$TABLE_EXISTS" = "t" ]; then MIGRATION_COUNT=$(docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -tAc \ "SELECT COUNT(*) FROM schema_migrations;" 2>/dev/null || echo "0") print_info "Migrations table exists with ${MIGRATION_COUNT} applied migrations" fi print_success "Migration verification passed" return 0 } # Function to rollback rollback() { print_error "Migration failed! Initiating rollback..." if [ ! -f "${ROLLBACK_DIR}/latest.sql" ]; then print_error "No rollback backup found. Manual intervention required." exit 1 fi print_info "Rolling back database to previous state..." # Drop and recreate database (or restore from backup) print_warning "Restoring database from backup..." # Restore database docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" < "${ROLLBACK_DIR}/latest.sql" || { print_error "Failed to restore database from backup. Manual intervention required."; exit 1; } print_warning "Rollback completed. Please verify database manually." exit 1 } # Main migration flow main() { print_info "Starting database migrations for ${ENVIRONMENT} environment..." # Trap errors and rollback trap rollback ERR check_prerequisites check_migration_status create_backup run_backend_migrations run_chat_server_migrations # Wait a bit for all migrations to complete sleep 5 # Verify migrations if ! verify_migrations; then rollback fi # Disable trap on success trap - ERR print_success "Database migrations completed successfully for ${ENVIRONMENT}!" print_info "Backup location: ${BACKUP_DIR}/database.sql" } # Run main function main