#!/bin/bash set -e # ============================================================================ # Rollback Deployment Script # ============================================================================ # Restores previous deployment from backup # Based on rollback logic from Gitea workflows # ============================================================================ # Color output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Usage function usage() { echo "Usage: $0 [backup_name]" echo "" echo "Arguments:" echo " environment staging or production" echo " backup_name (optional) specific backup to restore" echo " If not specified, restores latest backup" echo "" echo "Examples:" echo " $0 staging # Restore latest staging backup" echo " $0 production # Restore latest production backup" echo " $0 production backup_20250124_143022 # Restore specific backup" echo "" exit 1 } # Validate arguments if [ $# -lt 1 ]; then usage fi ENVIRONMENT="$1" SPECIFIC_BACKUP="${2:-}" # Validate environment if [ "$ENVIRONMENT" != "staging" ] && [ "$ENVIRONMENT" != "production" ]; then echo -e "${RED}❌ Error: Invalid environment '${ENVIRONMENT}'${NC}" echo "Must be 'staging' or 'production'" usage fi # Set environment-specific variables if [ "$ENVIRONMENT" = "staging" ]; then REMOTE_USER="${STAGING_USER:-deploy}" REMOTE_HOST="${STAGING_HOST:-staging.michaelschiemer.de}" REMOTE_PORT="${STAGING_SSH_PORT:-22}" REMOTE_PATH="/opt/framework-staging" COMPOSE_FILES="docker-compose.base.yml -f docker-compose.staging.yml" else REMOTE_USER="${PRODUCTION_USER:-deploy}" REMOTE_HOST="${PRODUCTION_HOST:-michaelschiemer.de}" REMOTE_PORT="${PRODUCTION_SSH_PORT:-22}" REMOTE_PATH="/opt/framework-production" COMPOSE_FILES="docker-compose.base.yml -f docker-compose.prod.yml" fi # Validation if [ -z "$REMOTE_HOST" ]; then echo -e "${RED}❌ Error: ${ENVIRONMENT^^}_HOST environment variable is required${NC}" echo "Export ${ENVIRONMENT^^}_HOST before running this script" exit 1 fi echo "==================================================" echo "🔄 Starting Rollback: ${ENVIRONMENT}" echo "==================================================" echo "Remote: ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}" echo "Path: ${REMOTE_PATH}" if [ -n "$SPECIFIC_BACKUP" ]; then echo "Target Backup: ${SPECIFIC_BACKUP}" else echo "Target Backup: Latest available" fi echo "" # Confirm rollback echo -e "${YELLOW}⚠️ WARNING: This will rollback the ${ENVIRONMENT} deployment${NC}" echo -e "${YELLOW} Current deployment will be stopped and replaced with backup${NC}" echo "" read -p "Are you sure you want to continue? (yes/no): " CONFIRM if [ "$CONFIRM" != "yes" ]; then echo "Rollback cancelled" exit 0 fi # Step 1: List available backups echo -e "${GREEN}[1/5] Listing available backups...${NC}" ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" " cd ${REMOTE_PATH} echo 'Available backups:' ls -dt backup_* 2>/dev/null || { echo '❌ No backups found' exit 1 } " if [ $? -ne 0 ]; then echo -e "${RED}❌ No backups available for rollback${NC}" exit 1 fi # Step 2: Determine which backup to restore echo -e "${GREEN}[2/5] Determining backup to restore...${NC}" if [ -n "$SPECIFIC_BACKUP" ]; then BACKUP_TO_RESTORE="$SPECIFIC_BACKUP" echo "Using specified backup: ${BACKUP_TO_RESTORE}" else BACKUP_TO_RESTORE=$(ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" " cd ${REMOTE_PATH} ls -dt backup_* 2>/dev/null | head -n1 ") echo "Using latest backup: ${BACKUP_TO_RESTORE}" fi # Verify backup exists ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" " cd ${REMOTE_PATH} if [ ! -d '${BACKUP_TO_RESTORE}' ]; then echo '❌ Backup ${BACKUP_TO_RESTORE} not found' exit 1 fi echo '✅ Backup ${BACKUP_TO_RESTORE} verified' " if [ $? -ne 0 ]; then echo -e "${RED}❌ Backup verification failed${NC}" exit 1 fi # Step 3: Stop current deployment echo -e "${GREEN}[3/5] Stopping current deployment...${NC}" ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" " cd ${REMOTE_PATH}/current echo 'Stopping containers...' docker-compose -f ${COMPOSE_FILES} down 2>/dev/null || { echo '⚠️ Some containers may not have stopped cleanly' } echo '✅ Current deployment stopped' " # Step 4: Restore backup echo -e "${GREEN}[4/5] Restoring backup...${NC}" ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" " cd ${REMOTE_PATH} # Backup current (failed) deployment for investigation if [ -d 'current' ]; then failed_backup=\"failed_\$(date +%Y%m%d_%H%M%S)\" echo \"Archiving failed deployment as \${failed_backup}...\" mv current \"\${failed_backup}\" fi # Restore backup echo 'Restoring backup ${BACKUP_TO_RESTORE}...' cp -r '${BACKUP_TO_RESTORE}' current echo '✅ Backup restored' " if [ $? -ne 0 ]; then echo -e "${RED}❌ Backup restoration failed${NC}" exit 1 fi # Step 5: Start restored deployment echo -e "${GREEN}[5/5] Starting restored deployment...${NC}" ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" " cd ${REMOTE_PATH}/current echo 'Starting containers...' docker-compose -f ${COMPOSE_FILES} up -d # Wait for services echo 'Waiting for services to start...' sleep 30 # Verify deployment echo 'Verifying deployment...' docker-compose -f ${COMPOSE_FILES} ps echo '✅ Restored deployment is running' " if [ $? -ne 0 ]; then echo -e "${RED}❌ Failed to start restored deployment${NC}" exit 1 fi # Health check echo "" echo -e "${GREEN}Performing health check...${NC}" ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" " cd ${REMOTE_PATH}/current # Wait a bit more for full initialization sleep 10 # Check container health healthy_containers=\$(docker-compose -f ${COMPOSE_FILES} ps --filter 'status=running' | wc -l) echo \"Healthy containers: \${healthy_containers}\" if [ \"\${healthy_containers}\" -gt 0 ]; then echo '✅ Containers are running' else echo '⚠️ No containers running - manual investigation required' fi " echo "" echo "==================================================" echo -e "${GREEN}✅ Rollback Complete${NC}" echo "==================================================" echo "Environment: ${ENVIRONMENT}" echo "Restored: ${BACKUP_TO_RESTORE}" echo "Completed at: $(date)" echo "" echo "Next steps:" echo " - Verify application: https://${REMOTE_HOST}" echo " - Monitor logs: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose logs -f'" echo " - Check status: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose ps'" echo "" echo "Failed deployment archived as: failed_YYYYMMDD_HHMMSS (if applicable)" echo "You can investigate the failure by SSH'ing to the server and examining:" echo " ${REMOTE_PATH}/failed_*" echo ""