#!/bin/bash # ROA2WEB Quick Rollback Script # Fast rollback to previous deployment state set -e # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" BACKUP_DIR="$PROJECT_DIR/backups" LOG_FILE="$PROJECT_DIR/rollback.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() { local level=$1 shift local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" } # Error handling error_exit() { log "ERROR" "$1" exit 1 } # Success message success() { log "SUCCESS" "$1" } # Warning message warning() { log "WARNING" "$1" } # Info message info() { log "INFO" "$1" } # Get last backup get_last_backup() { if [[ -f "$PROJECT_DIR/.last_backup" ]]; then cat "$PROJECT_DIR/.last_backup" else # Find the most recent backup find "$BACKUP_DIR" -name "backup_*" -type d -printf '%T+ %p\n' 2>/dev/null | sort -r | head -n1 | cut -d' ' -f2- | xargs basename 2>/dev/null || echo "" fi } # List available deployments to rollback to list_rollback_targets() { info "Available rollback targets:" echo "" if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then warning "No backups found" return 1 fi local current_backup=$(get_last_backup) find "$BACKUP_DIR" -name "backup_*" -type d -printf '%T+ %p\n' | sort -r | head -n5 | while read -r line; do local date_part=$(echo "$line" | cut -d' ' -f1 | cut -d+ -f1) local backup_name=$(basename "$(echo "$line" | cut -d' ' -f2)") local backup_path="$(echo "$line" | cut -d' ' -f2)" local marker="" if [[ "$backup_name" == "$current_backup" ]]; then marker=" ${GREEN}(current)${NC}" fi echo -e "📦 ${BLUE}$backup_name${NC} ($date_part)$marker" if [[ -f "$backup_path/backup_info.txt" ]]; then grep -E "(Environment|Git)" "$backup_path/backup_info.txt" | sed 's/^/ /' | head -2 fi echo "" done } # Health check function health_check() { local service=$1 local url=$2 local timeout=${3:-30} info "Performing health check for $service..." local count=0 while [[ $count -lt $timeout ]]; do if curl -f -s "$url" > /dev/null 2>&1; then success "$service health check passed" return 0 fi sleep 1 ((count++)) done warning "$service health check failed after ${timeout}s" return 1 } # Check service health check_services_health() { info "Checking services health..." local healthy=true # Check backend health if ! health_check "Backend API" "http://localhost/api/health" 30; then healthy=false fi # Check frontend health if ! health_check "Frontend" "http://localhost/health" 30; then healthy=false fi # Check gateway health if ! health_check "Gateway" "http://localhost/health" 30; then healthy=false fi if [[ "$healthy" == "true" ]]; then success "All services are healthy" return 0 else error_exit "Some services are not healthy" fi } # Quick rollback without backup restore quick_rollback() { info "Performing quick rollback (restart with previous images)..." cd "$PROJECT_DIR" # Stop current services info "Stopping current services..." docker-compose -f docker-compose.yml -f docker-compose.production.yml down # Get previous image versions local backend_image=$(docker images roa2web/backend --format "{{.ID}}" | sed -n '2p') local frontend_image=$(docker images roa2web/frontend --format "{{.ID}}" | sed -n '2p') local gateway_image=$(docker images roa2web/nginx-gateway --format "{{.ID}}" | sed -n '2p') if [[ -n "$backend_image" ]]; then info "Rolling back to previous backend image: $backend_image" docker tag "$backend_image" roa2web/backend:rollback fi if [[ -n "$frontend_image" ]]; then info "Rolling back to previous frontend image: $frontend_image" docker tag "$frontend_image" roa2web/frontend:rollback fi if [[ -n "$gateway_image" ]]; then info "Rolling back to previous gateway image: $gateway_image" docker tag "$gateway_image" roa2web/nginx-gateway:rollback fi # Start services with rollback images info "Starting services with previous images..." docker-compose -f docker-compose.yml -f docker-compose.production.yml up -d # Wait for services to start sleep 15 # Check health if check_services_health; then success "Quick rollback completed successfully!" else warning "Quick rollback completed but some services may not be healthy" fi } # Full rollback using backup full_rollback() { local backup_name=$1 if [[ -z "$backup_name" ]]; then backup_name=$(get_last_backup) fi if [[ -z "$backup_name" ]]; then error_exit "No backup found for rollback" fi local backup_path="$BACKUP_DIR/$backup_name" if [[ ! -d "$backup_path" ]]; then error_exit "Backup not found: $backup_name" fi warning "Performing full rollback to backup: $backup_name" warning "This will restore configuration and Docker volumes" warning "Press Ctrl+C to cancel or wait 10 seconds to continue..." sleep 10 cd "$PROJECT_DIR" # Stop current services info "Stopping current services..." docker-compose -f docker-compose.yml -f docker-compose.production.yml down # Restore Docker images if [[ -f "$backup_path/images.tar" ]]; then info "Restoring Docker images..." docker load -i "$backup_path/images.tar" fi # Restore configuration files if [[ -d "$backup_path/config" ]]; then info "Restoring configuration files..." # Backup current config before restore local config_backup="config_backup_$(date +%Y%m%d_%H%M%S)" mkdir -p "$BACKUP_DIR/$config_backup" cp -r .env* "$BACKUP_DIR/$config_backup/" 2>/dev/null || true cp docker-compose*.yml "$BACKUP_DIR/$config_backup/" # Restore from backup cp "$backup_path"/.env* . 2>/dev/null || true cp "$backup_path"/docker-compose*.yml . if [[ -d "$backup_path/config/conf" && -d "nginx" ]]; then cp -r "$backup_path/config/conf" nginx/ fi fi # Restore Docker volumes if [[ -d "$backup_path/volumes" ]]; then info "Restoring Docker volumes..." for volume_backup in "$backup_path/volumes"/*.tar.gz; do if [[ -f "$volume_backup" ]]; then local volume_name=$(basename "$volume_backup" .tar.gz) local full_volume_name="roa2web_$volume_name" # Remove current volume docker volume rm "$full_volume_name" 2>/dev/null || true # Create new volume docker volume create "$full_volume_name" # Restore data docker run --rm \ -v "$full_volume_name":/data \ -v "$backup_path/volumes":/backup \ alpine tar xzf "/backup/$(basename "$volume_backup")" -C /data info "Restored volume: $volume_name" fi done fi # Start services info "Starting services..." docker-compose -f docker-compose.yml -f docker-compose.production.yml up -d # Wait for services to start sleep 20 # Check health if check_services_health; then success "Full rollback completed successfully!" else warning "Full rollback completed but some services may not be healthy" warning "You may need to check logs: docker-compose logs" fi } # Emergency stop emergency_stop() { warning "Performing emergency stop of all ROA2WEB services..." cd "$PROJECT_DIR" # Stop all compose services docker-compose -f docker-compose.yml -f docker-compose.production.yml down -v 2>/dev/null || true docker-compose -f docker-compose.yml down -v 2>/dev/null || true # Stop any remaining ROA2WEB containers docker ps -q --filter "name=roa-" | xargs -r docker stop success "Emergency stop completed" } # Main function main() { local action=${1:-quick} case $action in "quick") info "=== ROA2WEB Quick Rollback ===" quick_rollback ;; "full") info "=== ROA2WEB Full Rollback ===" full_rollback "$2" ;; "list") list_rollback_targets ;; "emergency") emergency_stop ;; "health") check_services_health ;; *) echo "Usage: $0 {quick|full [backup_name]|list|emergency|health}" echo "" echo "Commands:" echo " quick - Quick rollback using previous Docker images" echo " full - Full rollback using backup (restores config + volumes)" echo " list - List available rollback targets" echo " emergency - Emergency stop all services" echo " health - Check current services health" echo "" echo "Examples:" echo " $0 quick # Quick rollback" echo " $0 full # Full rollback to last backup" echo " $0 full backup_20240131_143022 # Full rollback to specific backup" exit 1 ;; esac } # Run main function main "$@"