#!/bin/bash # Safe Database Migration Script # Usage: ./safe-migrate.sh [environment] # Environments: Development, Sandbox, Production, Oda set -e # Exit on any error ENVIRONMENT=${1:-"Development"} # Default to Development for safer initial testing TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR_NAME="backups" # Just the directory name LOGS_DIR_NAME="logs" # Just the directory name # Get the directory where the script is located SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" # Create logs directory first (before LOG_FILE is used) LOGS_DIR="$SCRIPT_DIR/$LOGS_DIR_NAME" mkdir -p "$LOGS_DIR" || { echo "Failed to create logs directory: $LOGS_DIR"; exit 1; } LOG_FILE="./logs/migration_${ENVIRONMENT}_${TIMESTAMP}.log" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging function log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" | tee -a "$LOG_FILE" } warn() { echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}" | tee -a "$LOG_FILE" } error() { echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" | tee -a "$LOG_FILE" exit 1 } info() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}" | tee -a "$LOG_FILE" } # --- Determine Base Paths --- # Get the directory where the script is located SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" log "Script is located in: $SCRIPT_DIR" # Define absolute paths for projects and common directories relative to the script # Assuming the project structure is: # your_repo/ # ├── scripts/safe-migrate.sh # └── src/ # ├── Managing.Api/ # ├── Managing.Infrastructure.Database/ # └── Managing.Docker/ PROJECT_ROOT_DIR="$(dirname "$SCRIPT_DIR")" # One level up from scripts/ SRC_DIR="$PROJECT_ROOT_DIR/src" DB_PROJECT_PATH="$SRC_DIR/Managing.Infrastructure.Database" API_PROJECT_PATH="$SRC_DIR/Managing.Api" DOCKER_DIR="$SRC_DIR/Managing.Docker" # Adjust if your docker-compose files are elsewhere # Define absolute path for backup directory with environment subfolder BACKUP_DIR="$SCRIPT_DIR/$BACKUP_DIR_NAME/$ENVIRONMENT" # --- Pre-checks and Setup --- info "Pre-flight checks..." command -v dotnet >/dev/null 2>&1 || error ".NET SDK is not installed. Please install .NET SDK to run this script." command -v docker >/dev/null 2>&1 || warn "Docker is not installed. This is fine if not running Development or Oda environment with Docker." command -v psql >/dev/null 2>&1 || warn "PostgreSQL CLI (psql) is not installed. Database connectivity checks will be skipped." command -v pg_dump >/dev/null 2>&1 || warn "PostgreSQL pg_dump is not installed. Will use EF Core migration script for backup instead." # Create backup directory (with environment subfolder) mkdir -p "$BACKUP_DIR" || error "Failed to create backup directory: $BACKUP_DIR" log "Backup directory created/verified: $BACKUP_DIR" log "🚀 Starting safe migration for environment: $ENVIRONMENT" # Validate environment case $ENVIRONMENT in "Development"|"Sandbox"|"Production"|"Oda") log "✅ Environment '$ENVIRONMENT' is valid" ;; *) error "❌ Invalid environment '$ENVIRONMENT'. Use: Development, Sandbox, Production, or Oda" ;; esac # Helper function to start PostgreSQL for Development (if still using Docker Compose) start_postgres_if_needed() { if [ "$ENVIRONMENT" = "Development" ] || [ "$ENVIRONMENT" = "Oda" ]; then # Assuming Oda also uses local Docker log "🔍 Checking if PostgreSQL is running for $ENVIRONMENT..." if ! docker ps --filter "name=postgres" --format "{{.Names}}" | grep -q "postgres"; then log "🐳 Starting PostgreSQL container for $ENVIRONMENT from $DOCKER_DIR..." # Execute docker-compose from the DOCKER_DIR (cd "$DOCKER_DIR" && docker-compose -f docker-compose.yml -f docker-compose.local.yml up -d postgres) || error "Failed to start PostgreSQL container." log "⏳ Waiting for PostgreSQL to be ready (15 seconds)..." sleep 15 else log "✅ PostgreSQL container is already running." fi fi } # Helper function to extract connection details from appsettings extract_connection_details() { local appsettings_file="$API_PROJECT_PATH/appsettings.$ENVIRONMENT.json" local default_appsettings="$API_PROJECT_PATH/appsettings.json" # Try environment-specific file first, then default if [ -f "$appsettings_file" ]; then log "📋 Reading connection string from: appsettings.$ENVIRONMENT.json" # Look for PostgreSql.ConnectionString first, then fallback to ConnectionString CONNECTION_STRING=$(grep -A 3 '"PostgreSql"' "$appsettings_file" | grep -o '"ConnectionString": *"[^"]*"' | cut -d'"' -f4) if [ -z "$CONNECTION_STRING" ]; then CONNECTION_STRING=$(grep -o '"ConnectionString": *"[^"]*"' "$appsettings_file" | cut -d'"' -f4) fi elif [ -f "$default_appsettings" ]; then log "📋 Reading connection string from: appsettings.json (default)" # Look for PostgreSql.ConnectionString first, then fallback to ConnectionString CONNECTION_STRING=$(grep -A 3 '"PostgreSql"' "$default_appsettings" | grep -o '"ConnectionString": *"[^"]*"' | cut -d'"' -f4) if [ -z "$CONNECTION_STRING" ]; then CONNECTION_STRING=$(grep -o '"ConnectionString": *"[^"]*"' "$default_appsettings" | cut -d'"' -f4) fi else warn "⚠️ Could not find appsettings file for environment $ENVIRONMENT" return 1 fi if [ -z "$CONNECTION_STRING" ]; then error "❌ Could not extract connection string from appsettings file" return 1 fi log "📋 Found connection string: $CONNECTION_STRING" # Parse connection string DB_HOST=$(echo "$CONNECTION_STRING" | grep -o 'Host=[^;]*' | cut -d'=' -f2) DB_PORT=$(echo "$CONNECTION_STRING" | grep -o 'Port=[^;]*' | cut -d'=' -f2) DB_NAME=$(echo "$CONNECTION_STRING" | grep -o 'Database=[^;]*' | cut -d'=' -f2) DB_USER=$(echo "$CONNECTION_STRING" | grep -o 'Username=[^;]*' | cut -d'=' -f2) DB_PASSWORD=$(echo "$CONNECTION_STRING" | grep -o 'Password=[^;]*' | cut -d'=' -f2) # Set defaults if not found DB_HOST=${DB_HOST:-"localhost"} DB_PORT=${DB_PORT:-"5432"} DB_NAME=${DB_NAME:-"postgres"} DB_USER=${DB_USER:-"postgres"} DB_PASSWORD=${DB_PASSWORD:-"postgres"} log "📋 Extracted connection details: $DB_HOST:$DB_PORT/$DB_NAME (user: $DB_USER, password: $DB_PASSWORD)" } # Helper function to test PostgreSQL connectivity test_postgres_connectivity() { if ! command -v psql >/dev/null 2>&1; then warn "⚠️ psql not available, skipping PostgreSQL connectivity test" return 0 fi log "🔍 Testing PostgreSQL connectivity with psql..." # For remote servers or when target database might not exist, test with postgres database first local test_database="$DB_NAME" if [ "$TARGET_DB_EXISTS" = "false" ]; then test_database="postgres" log "🔍 Target database doesn't exist, testing connectivity with 'postgres' database..." fi # Test basic connectivity if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$test_database" -c "SELECT version();" >/dev/null 2>&1; then log "✅ PostgreSQL connectivity test passed" # Get database info log "📊 Database Information:" DB_INFO=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$test_database" -t -c " SELECT 'Database: ' || current_database() || ' (Size: ' || pg_size_pretty(pg_database_size(current_database())) || ')', 'PostgreSQL Version: ' || version(), 'Connection: ' || inet_server_addr() || ':' || inet_server_port() " 2>/dev/null | tr '\n' ' ') log " $DB_INFO" # Only check migrations if we're testing the actual target database if [ "$test_database" = "$DB_NAME" ]; then # Check if __EFMigrationsHistory table exists if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "\dt __EFMigrationsHistory" >/dev/null 2>&1; then log "✅ EF Core migrations history table exists" # Count applied migrations MIGRATION_COUNT=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM \"__EFMigrationsHistory\";" 2>/dev/null | tr -d ' ') log "📋 Applied migrations count: $MIGRATION_COUNT" # Show recent migrations if [ "$MIGRATION_COUNT" -gt 0 ]; then log "📋 Recent migrations:" RECENT_MIGRATIONS=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c " SELECT \"MigrationId\" FROM \"__EFMigrationsHistory\" ORDER BY \"MigrationId\" DESC LIMIT 5; " 2>/dev/null | sed 's/^/ /') echo "$RECENT_MIGRATIONS" fi else warn "⚠️ EF Core migrations history table not found - database may be empty" fi else log "📋 Connectivity test completed using 'postgres' database (target database will be created)" fi return 0 else error "❌ PostgreSQL connectivity test failed" error " Host: $DB_HOST, Port: $DB_PORT, Database: $test_database, User: $DB_USER" return 1 fi } # --- Core Logic --- # No global 'cd' needed here. All paths are now absolute. # This makes the script much more robust to where it's executed from. # Set ASPNETCORE_ENVIRONMENT to load the correct appsettings export ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" log "ASPNETCORE_ENVIRONMENT set to: $ASPNETCORE_ENVIRONMENT" # If Development or Oda, start local PostgreSQL start_postgres_if_needed # Extract connection details from appsettings extract_connection_details # Step 0: Build the .NET projects to ensure they are up to date log "🔨 Step 0: Building .NET projects..." log "🔧 Building Managing.Infrastructure.Database project..." if (cd "$DB_PROJECT_PATH" && dotnet build); then log "✅ Managing.Infrastructure.Database project built successfully" else error "❌ Failed to build Managing.Infrastructure.Database project" fi log "🔧 Building Managing.Api project..." if (cd "$API_PROJECT_PATH" && dotnet build); then log "✅ Managing.Api project built successfully" else error "❌ Failed to build Managing.Api project" fi # Step 1: Check Database Connection and Create if Needed log "🔧 Step 1: Checking database connection and creating database if needed..." # Log the environment and expected connection details (for user info, still relies on appsettings) log "🔧 Using environment: $ENVIRONMENT" log "📋 Connection details: $DB_HOST:$DB_PORT/$DB_NAME (user: $DB_USER)" # Initial connectivity check - test if we can reach the database server log "🔍 Step 1a: Testing basic database server connectivity..." if command -v psql >/dev/null 2>&1; then # Test if we can connect to the postgres database (which should always exist) log "🔍 Connecting to default 'postgres' database to verify server connectivity..." if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "SELECT 1;" >/dev/null 2>&1; then log "✅ Database server connectivity test passed" # Check if our target database exists log "🔍 Checking if target database '$DB_NAME' exists..." DB_EXISTS=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -t -c "SELECT 1 FROM pg_database WHERE datname = '$DB_NAME';" 2>/dev/null | tr -d ' ') if [ "$DB_EXISTS" = "1" ]; then log "✅ Target database '$DB_NAME' exists" TARGET_DB_EXISTS=true else log "⚠️ Target database '$DB_NAME' does not exist - will be created" TARGET_DB_EXISTS=false fi else error "❌ Database server connectivity test failed" error " Cannot reach PostgreSQL server at $DB_HOST:$DB_PORT with database 'postgres'" error " Please verify:" error " - Database server is running" error " - Network connectivity to $DB_HOST:$DB_PORT" error " - Credentials are correct (user: $DB_USER)" error " - Firewall settings allow connections" error " - The 'postgres' database exists (default PostgreSQL database)" fi else # Fallback: try to connect using EF Core to test basic connectivity log "🔄 psql not available, testing connectivity via EF Core..." if (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") >/dev/null 2>&1; then log "✅ Database server connectivity test passed (via EF Core)" TARGET_DB_EXISTS=true # Assume it exists if EF Core can connect else warn "⚠️ Could not verify database server connectivity (psql not available)" warn " Proceeding with caution - connectivity will be tested during migration" TARGET_DB_EXISTS=false # Assume it doesn't exist if EF Core can't connect fi fi log "🔍 Step 1b: Testing database connection and checking if database exists via EF CLI..." # Test connection by listing migrations. If it fails, the database likely doesn't exist or is inaccessible. # Execute dotnet ef from DB_PROJECT_PATH for correct context, but pass API_PROJECT_PATH as startup. # Since we just built the projects, we can safely use --no-build flag for faster execution if (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") >/dev/null 2>&1; then log "✅ EF Core database connection successful and database appears to exist." # Now test with psql for additional verification (this will use postgres db if target doesn't exist) test_postgres_connectivity # If psql connectivity test fails, stop the migration if [ $? -ne 0 ]; then error "❌ PostgreSQL connectivity test failed. Migration aborted for safety." error " Please verify your database connection and try again." fi else # Database doesn't exist or connection failed if [ "$TARGET_DB_EXISTS" = "false" ]; then log "📝 Database '$DB_NAME' does not exist. Creating database and applying migrations..." # Test connectivity with postgres database first (since target doesn't exist) test_postgres_connectivity # If connectivity test fails, stop the migration if [ $? -ne 0 ]; then error "❌ PostgreSQL connectivity test failed. Cannot proceed with database creation." error " Please verify your connection settings and try again." fi # Step 1: Create the database first log "🔧 Step 1: Creating database '$DB_NAME'..." if command -v psql >/dev/null 2>&1; then if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "CREATE DATABASE \"$DB_NAME\";" >/dev/null 2>&1; then log "✅ Database '$DB_NAME' created successfully" else error "❌ Failed to create database '$DB_NAME'" error " Please verify you have sufficient privileges to create databases." fi else warn "⚠️ psql not available, attempting to create database via EF Core..." # EF Core will attempt to create the database during update fi # Step 2: Generate migration script for the new database log "📝 Step 2: Generating migration script for new database..." TEMP_MIGRATION_SCRIPT="$BACKUP_DIR/temp_migration_${ENVIRONMENT}_${TIMESTAMP}.sql" if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$TEMP_MIGRATION_SCRIPT"); then log "✅ Migration script generated successfully: $(basename "$TEMP_MIGRATION_SCRIPT")" # Step 3: Apply the migration script to the new database log "🔧 Step 3: Applying migration script to new database..." if command -v psql >/dev/null 2>&1; then if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$TEMP_MIGRATION_SCRIPT" >/dev/null 2>&1; then log "✅ Migration script applied successfully to new database" else error "❌ Failed to apply migration script to newly created database" fi else # Fallback to EF Core database update log "🔄 psql not available, using EF Core to apply migrations..." if (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING"); then log "✅ Database created and initialized successfully using EF Core" else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true ) error "❌ Failed to create and initialize database using EF Core." error " EF CLI Output: $ERROR_OUTPUT" fi fi # Clean up temporary migration script rm -f "$TEMP_MIGRATION_SCRIPT" else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$TEMP_MIGRATION_SCRIPT") 2>&1 || true ) error "❌ Failed to generate migration script." error " EF CLI Output: $ERROR_OUTPUT" fi else warn "⚠️ Database connection failed but database may exist. Attempting to update existing database..." # Try to update the existing database if (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING"); then log "✅ Database updated successfully" else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true ) error "❌ Failed to update database." error " EF CLI Output: $ERROR_OUTPUT" error " This usually means the connection string in your .NET project's appsettings.$ENVIRONMENT.json is incorrect," error " or the database server is not running/accessible for environment '$ENVIRONMENT'." fi fi # Test connectivity after creation/update test_postgres_connectivity # If connectivity test fails after creation, stop the migration if [ $? -ne 0 ]; then error "❌ PostgreSQL connectivity test failed after database creation. Migration aborted for safety." error " Database may have been created but is not accessible. Please verify your connection settings." fi fi # Final verification of connection log "🔍 Verifying database connection post-creation/update..." if (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") >/dev/null 2>&1; then log "✅ Database connectivity verification passed." else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true ) error "❌ Final database connectivity verification failed." error " EF CLI Output: $ERROR_OUTPUT" error " This is critical. Please review the previous error messages and your connection string for '$ENVIRONMENT'." fi # Step 2: Create Backup log "📦 Step 2: Creating database backup using pg_dump..." # Define the actual backup file path (absolute) BACKUP_FILE="$BACKUP_DIR/managing_${ENVIRONMENT}_backup_${TIMESTAMP}.sql" # Backup file display path (relative to script execution) BACKUP_FILE_DISPLAY="$BACKUP_DIR_NAME/$ENVIRONMENT/managing_${ENVIRONMENT}_backup_${TIMESTAMP}.sql" # Create backup with retry logic BACKUP_SUCCESS=false for attempt in 1 2 3; do log "Backup attempt $attempt/3..." # Create real database backup using pg_dump if command -v pg_dump >/dev/null 2>&1; then if PGPASSWORD="$DB_PASSWORD" pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" --no-password --verbose --clean --if-exists --create --format=plain > "$BACKUP_FILE" 2>/dev/null; then log "✅ Database backup created using pg_dump: $BACKUP_FILE_DISPLAY" BACKUP_SUCCESS=true break else # If pg_dump fails, fall back to EF Core migration script warn "⚠️ pg_dump failed, falling back to EF Core migration script..." if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE"); then log "✅ EF Core Migration SQL Script generated: $BACKUP_FILE_DISPLAY" BACKUP_SUCCESS=true break else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE") 2>&1 || true) if [ $attempt -lt 3 ]; then warn "⚠️ Backup attempt $attempt failed. Retrying in 5 seconds..." warn " EF CLI Output: $ERROR_OUTPUT" sleep 5 else error "❌ Database backup failed after 3 attempts." error " EF CLI Output: $ERROR_OUTPUT" error " Migration aborted for safety reasons." fi fi fi else # If pg_dump is not available, use EF Core migration script warn "⚠️ pg_dump not available, using EF Core migration script for backup..." if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE"); then log "✅ EF Core Migration SQL Script generated: $BACKUP_FILE_DISPLAY" BACKUP_SUCCESS=true break else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$BACKUP_FILE") 2>&1 || true) if [ $attempt -lt 3 ]; then warn "⚠️ Backup attempt $attempt failed. Retrying in 5 seconds..." warn " EF CLI Output: $ERROR_OUTPUT" sleep 5 else error "❌ Database backup failed after 3 attempts." error " EF CLI Output: $ERROR_OUTPUT" error " Migration aborted for safety reasons." fi fi fi done # Check if backup was successful before proceeding if [ "$BACKUP_SUCCESS" != "true" ]; then error "❌ Database backup failed. Migration aborted for safety." error " Cannot proceed with migration without a valid backup." error " Please resolve backup issues and try again." fi # Step 3: Run Migration (This effectively is a retry if previous "update" failed, or a final apply) log "🔄 Step 3: Running database migration (final application of pending migrations)..." # Check if database exists and create it if needed before applying migrations log "🔍 Step 3a: Ensuring target database exists..." if [ "$TARGET_DB_EXISTS" = "false" ]; then log "🔧 Creating database '$DB_NAME'..." if command -v psql >/dev/null 2>&1; then if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "CREATE DATABASE \"$DB_NAME\";" >/dev/null 2>&1; then log "✅ Database '$DB_NAME' created successfully" else error "❌ Failed to create database '$DB_NAME'" error " Please verify you have sufficient privileges to create databases." fi else error "❌ psql not available, cannot create database. Please create database '$DB_NAME' manually." fi fi # Generate migration script first (Microsoft recommended approach) MIGRATION_SCRIPT="$BACKUP_DIR/migration_${ENVIRONMENT}_${TIMESTAMP}.sql" log "📝 Step 3b: Generating migration script for pending migrations..." if (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$MIGRATION_SCRIPT"); then log "✅ Migration script generated: $(basename "$MIGRATION_SCRIPT")" # Show the migration script path to the user for review echo "" echo "==========================================" echo "📋 MIGRATION SCRIPT READY FOR REVIEW" echo "==========================================" echo "Generated script: $MIGRATION_SCRIPT" echo "Environment: $ENVIRONMENT" echo "Database: $DB_HOST:$DB_PORT/$DB_NAME" echo "" echo "⚠️ IMPORTANT: Please review the migration script before proceeding!" echo " You can examine the script with: cat $MIGRATION_SCRIPT" echo " Or open it in your editor to review the changes." echo "" echo "==========================================" echo "" # Ask for user confirmation read -p "🔍 Have you reviewed the migration script and want to proceed? Type 'yes' to continue: " user_confirmation if [ "$user_confirmation" != "yes" ]; then log "❌ Migration cancelled by user." log " Migration script is available at: $(basename "$MIGRATION_SCRIPT")" log " You can apply it manually later with:" log " PGPASSWORD=\"$DB_PASSWORD\" psql -h \"$DB_HOST\" -p \"$DB_PORT\" -U \"$DB_USER\" -d \"$DB_NAME\" -f \"$MIGRATION_SCRIPT\"" exit 0 fi log "✅ User confirmed migration. Proceeding with database update..." # Apply the migration script using psql (recommended approach) log "🔧 Step 3c: Applying migration script to database..." if command -v psql >/dev/null 2>&1; then if PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_SCRIPT" >/dev/null 2>&1; then log "✅ Migration script applied successfully to database" else ERROR_OUTPUT=$( (PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_SCRIPT") 2>&1 || true ) error "❌ Failed to apply migration script to database" error " PSQL Output: $ERROR_OUTPUT" error " Migration script available at: $(basename "$MIGRATION_SCRIPT")" fi else # Fallback to EF Core database update if psql is not available log "🔄 psql not available, falling back to EF Core database update..." if (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING"); then log "✅ Database migration completed successfully using EF Core." else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef database update --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 || true ) error "❌ Database migration failed during final update." error " EF CLI Output: $ERROR_OUTPUT" error " Check the .NET project logs for detailed errors." error " Backup script available at: $BACKUP_FILE_DISPLAY" fi fi # Clean up migration script after successful application rm -f "$MIGRATION_SCRIPT" else ERROR_OUTPUT=$( (cd "$DB_PROJECT_PATH" && ASPNETCORE_ENVIRONMENT="$ENVIRONMENT" dotnet ef migrations script --idempotent --no-build --startup-project "$API_PROJECT_PATH" --output "$MIGRATION_SCRIPT") 2>&1 || true ) error "❌ Failed to generate migration script." error " EF CLI Output: $ERROR_OUTPUT" error " Check the .NET project logs for detailed errors." error " Backup script available at: $BACKUP_FILE_DISPLAY" fi # Step 4: Verify Migration log "🔍 Step 4: Verifying migration status..." # List migrations to check applied status MIGRATION_LIST_OUTPUT=$( (cd "$DB_PROJECT_PATH" && dotnet ef migrations list --no-build --startup-project "$API_PROJECT_PATH" --connection "$CONNECTION_STRING") 2>&1 ) log "📋 Current migration status:\n$MIGRATION_LIST_OUTPUT" # Check if there are any pending migrations after update PENDING_MIGRATIONS=$(echo "$MIGRATION_LIST_OUTPUT" | grep -c "\[ \]" || echo "0") PENDING_MIGRATIONS=$(echo "$PENDING_MIGRATIONS" | tr -d '\n') # Remove any newlines if [ "$PENDING_MIGRATIONS" -gt 0 ]; then warn "⚠️ WARNING: $PENDING_MIGRATIONS pending migration(s) found after update." warn " This indicates the 'dotnet ef database update' command may not have fully completed." else log "✅ All migrations appear to be applied successfully." fi # --- Step 5: Cleanup Backups (keep only 5 dumps max) --- log "🧹 Step 5: Cleaning up old backups..." # Keep only the last 5 backups for this environment (in the environment-specific subfolder) ls -t "$BACKUP_DIR"/managing_${ENVIRONMENT}_backup_*.sql 2>/dev/null | tail -n +6 | xargs -r rm -f || true # Added -f for force removal log "✅ Kept last 5 backups for $ENVIRONMENT environment in $BACKUP_DIR_NAME/$ENVIRONMENT/" # Success Summary log "🎉 Migration completed successfully for environment: $ENVIRONMENT!" log "📁 EF Core Migration SQL Script: $BACKUP_FILE_DISPLAY" log "📝 Full Log file: $LOG_FILE" echo "" echo "==========================================" echo "📋 MIGRATION SUMMARY" echo "==========================================" echo "Environment: $ENVIRONMENT" echo "Timestamp: $TIMESTAMP" echo "Status: ✅ SUCCESS" echo "EF Core SQL Backup: $BACKUP_FILE_DISPLAY" echo "Log: $LOG_FILE" echo "=========================================="