Some checks failed
Deploy Application / deploy (push) Has been cancelled
237 lines
7.1 KiB
Bash
Executable File
237 lines
7.1 KiB
Bash
Executable File
#!/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 <environment> [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 ""
|