#!/bin/bash # Laravel Timebank Complete Restoration Script # Restores both database and storage from backups # Usage: ./restore-all.sh [options] set -e # Exit on any error # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" BACKUP_ROOT_DIR="$PROJECT_ROOT/backups" LOG_FILE="$BACKUP_ROOT_DIR/restore.log" # Create log directory mkdir -p "$BACKUP_ROOT_DIR/logs" # 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" local message="$2" local timestamp="[$(date '+%Y-%m-%d %H:%M:%S')]" case "$level" in "INFO") echo -e "${timestamp} ${BLUE}[INFO]${NC} $message" | tee -a "$LOG_FILE" ;; "SUCCESS") echo -e "${timestamp} ${GREEN}[SUCCESS]${NC} $message" | tee -a "$LOG_FILE" ;; "WARNING") echo -e "${timestamp} ${YELLOW}[WARNING]${NC} $message" | tee -a "$LOG_FILE" ;; "ERROR") echo -e "${timestamp} ${RED}[ERROR]${NC} $message" | tee -a "$LOG_FILE" ;; *) echo "$timestamp $message" | tee -a "$LOG_FILE" ;; esac } # Function to show usage show_usage() { echo "Usage: $0 [options]" echo "" echo "Options:" echo " --latest - Restore from latest backups" echo " --database-file FILE - Specify database backup file" echo " --storage-file FILE - Specify storage backup file" echo " --database-latest - Use latest database backup only" echo " --storage-latest - Use latest storage backup only" echo " --confirm - Skip confirmation prompts" echo " --list-backups - List available backups" echo " --help - Show this help message" echo "" echo "Restore modes:" echo " Complete restore: Both database and storage" echo " Database only: --database-file or --database-latest" echo " Storage only: --storage-file or --storage-latest" echo "" echo "Examples:" echo " $0 --latest # Restore latest database and storage" echo " $0 --database-latest --storage-latest # Same as above" echo " $0 --database-file db.sql.gz --storage-file storage.tar.gz" echo " $0 --list-backups # Show available backups" echo "" exit 0 } # Function to list available backups list_backups() { log "INFO" "Available backups for restoration:" echo "" if [ ! -d "$BACKUP_ROOT_DIR" ]; then log "WARNING" "No backup directory found" return 0 fi local backup_found=false # Database backups if [ -d "$BACKUP_ROOT_DIR/database" ]; then echo -e "${BLUE}Database backups:${NC}" for backup_type in daily weekly monthly; do local backup_dir="$BACKUP_ROOT_DIR/database/$backup_type" if [ -d "$backup_dir" ]; then local backups=($(find "$backup_dir" -name "*.sql.gz" -type f | sort -r | head -3)) if [ ${#backups[@]} -gt 0 ]; then echo -e " ${backup_type}:" for backup in "${backups[@]}"; do local size=$(du -h "$backup" | cut -f1) local date_created=$(date -r "$backup" '+%Y-%m-%d %H:%M:%S') echo " $(basename "$backup") ($size, $date_created)" done backup_found=true fi fi done echo "" fi # Storage backups if [ -d "$BACKUP_ROOT_DIR/storage" ]; then echo -e "${BLUE}Storage backups:${NC}" for backup_type in daily weekly monthly; do local backup_dir="$BACKUP_ROOT_DIR/storage/$backup_type" if [ -d "$backup_dir" ]; then local backups=($(find "$backup_dir" -name "*.tar.gz" -type f | sort -r | head -3)) if [ ${#backups[@]} -gt 0 ]; then echo -e " ${backup_type}:" for backup in "${backups[@]}"; do local size=$(du -h "$backup" | cut -f1) local date_created=$(date -r "$backup" '+%Y-%m-%d %H:%M:%S') echo " $(basename "$backup") ($size, $date_created)" done backup_found=true fi fi done echo "" fi if [ "$backup_found" = false ]; then log "WARNING" "No backups found" fi } # Function to find latest backup get_latest_backup() { local backup_type="$1" # database or storage local extension="$2" # sql.gz or tar.gz local latest_backup="" local latest_time=0 if [ -d "$BACKUP_ROOT_DIR/$backup_type" ]; then while IFS= read -r -d '' backup; do local backup_time=$(stat -c %Y "$backup") if [ "$backup_time" -gt "$latest_time" ]; then latest_time=$backup_time latest_backup=$backup fi done < <(find "$BACKUP_ROOT_DIR/$backup_type" -name "*.$extension" -type f -print0) fi echo "$latest_backup" } # Function to confirm restoration confirm_restore() { local db_file="$1" local storage_file="$2" echo "" echo -e "${YELLOW}WARNING: This will REPLACE current data!${NC}" echo "" if [ -n "$db_file" ]; then echo -e "${RED}Database restore:${NC}" echo -e " File: $(basename "$db_file")" echo -e " Size: $(du -h "$db_file" | cut -f1)" echo -e " Date: $(date -r "$db_file" '+%Y-%m-%d %H:%M:%S')" echo "" fi if [ -n "$storage_file" ]; then echo -e "${RED}Storage restore:${NC}" echo -e " File: $(basename "$storage_file")" echo -e " Size: $(du -h "$storage_file" | cut -f1)" echo -e " Date: $(date -r "$storage_file" '+%Y-%m-%d %H:%M:%S')" echo "" fi read -p "Are you sure you want to continue? [y/N]: " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then log "INFO" "Restoration cancelled by user" exit 0 fi } # Function to validate backup files validate_backup_file() { local file="$1" local type="$2" if [ ! -f "$file" ] || [ ! -r "$file" ]; then log "ERROR" "$type backup file not found or not readable: $file" return 1 fi # Test if backup file is valid case "$type" in "database") if ! gzip -t "$file" 2>/dev/null; then log "ERROR" "Database backup file is corrupted or not a valid gzip file" return 1 fi ;; "storage") if ! tar -tzf "$file" >/dev/null 2>&1; then log "ERROR" "Storage backup file is corrupted or not a valid tar.gz file" return 1 fi ;; esac return 0 } # Function to restore database restore_database() { local db_file="$1" log "INFO" "Starting database restoration..." if ! validate_backup_file "$db_file" "database"; then return 1 fi # Use the database restore script if [ -x "$SCRIPT_DIR/restore-database.sh" ]; then log "INFO" "Using database restore script" if "$SCRIPT_DIR/restore-database.sh" "$db_file" --confirm; then log "SUCCESS" "Database restoration completed" return 0 else log "ERROR" "Database restoration failed" return 1 fi else log "ERROR" "Database restore script not found or not executable" return 1 fi } # Function to restore storage restore_storage() { local storage_file="$1" log "INFO" "Starting storage restoration..." if ! validate_backup_file "$storage_file" "storage"; then return 1 fi # Use the storage restore script if [ -x "$SCRIPT_DIR/restore-storage.sh" ]; then log "INFO" "Using storage restore script" if "$SCRIPT_DIR/restore-storage.sh" "$storage_file" --confirm; then log "SUCCESS" "Storage restoration completed" return 0 else log "ERROR" "Storage restoration failed" return 1 fi else log "ERROR" "Storage restore script not found or not executable" return 1 fi } # Function to perform post-restore tasks post_restore_tasks() { log "INFO" "Running post-restore tasks..." # Laravel optimization commands if [ -f "$PROJECT_ROOT/artisan" ]; then log "INFO" "Running Laravel optimization commands..." # Change to project directory cd "$PROJECT_ROOT" # Clear caches php artisan config:clear 2>/dev/null || true php artisan cache:clear 2>/dev/null || true php artisan view:clear 2>/dev/null || true php artisan route:clear 2>/dev/null || true # Check migration status log "INFO" "Checking database migration status..." php artisan migrate:status 2>/dev/null || log "WARNING" "Could not check migration status" log "SUCCESS" "Laravel optimization completed" else log "WARNING" "Laravel artisan not found, skipping optimization" fi } # Parse command line arguments DATABASE_FILE="" STORAGE_FILE="" DATABASE_LATEST=false STORAGE_LATEST=false LATEST=false CONFIRM=false LIST_BACKUPS=false while [[ $# -gt 0 ]]; do case $1 in --database-file) DATABASE_FILE="$2" shift 2 ;; --storage-file) STORAGE_FILE="$2" shift 2 ;; --database-latest) DATABASE_LATEST=true shift ;; --storage-latest) STORAGE_LATEST=true shift ;; --latest) LATEST=true shift ;; --confirm) CONFIRM=true shift ;; --list-backups) LIST_BACKUPS=true shift ;; --help) show_usage ;; *) log "ERROR" "Unknown option: $1" show_usage ;; esac done # Handle list backups option if [ "$LIST_BACKUPS" = true ]; then list_backups exit 0 fi # Handle latest option if [ "$LATEST" = true ]; then DATABASE_LATEST=true STORAGE_LATEST=true fi # Determine backup files if [ "$DATABASE_LATEST" = true ]; then DATABASE_FILE=$(get_latest_backup "database" "sql.gz") if [ -z "$DATABASE_FILE" ]; then log "ERROR" "No database backups found" exit 1 fi log "INFO" "Using latest database backup: $(basename "$DATABASE_FILE")" fi if [ "$STORAGE_LATEST" = true ]; then STORAGE_FILE=$(get_latest_backup "storage" "tar.gz") if [ -z "$STORAGE_FILE" ]; then log "ERROR" "No storage backups found" exit 1 fi log "INFO" "Using latest storage backup: $(basename "$STORAGE_FILE")" fi # Validate that at least one restoration is requested if [ -z "$DATABASE_FILE" ] && [ -z "$STORAGE_FILE" ]; then log "ERROR" "No restoration specified. Use --help for usage information." exit 1 fi # Resolve relative paths if [ -n "$DATABASE_FILE" ] && [[ "$DATABASE_FILE" != /* ]]; then if [ -f "$BACKUP_ROOT_DIR/$DATABASE_FILE" ]; then DATABASE_FILE="$BACKUP_ROOT_DIR/$DATABASE_FILE" elif [ -f "$BACKUP_ROOT_DIR/database/daily/$DATABASE_FILE" ]; then DATABASE_FILE="$BACKUP_ROOT_DIR/database/daily/$DATABASE_FILE" elif [ -f "$BACKUP_ROOT_DIR/database/weekly/$DATABASE_FILE" ]; then DATABASE_FILE="$BACKUP_ROOT_DIR/database/weekly/$DATABASE_FILE" elif [ -f "$BACKUP_ROOT_DIR/database/monthly/$DATABASE_FILE" ]; then DATABASE_FILE="$BACKUP_ROOT_DIR/database/monthly/$DATABASE_FILE" elif [ -f "$DATABASE_FILE" ]; then DATABASE_FILE="$(realpath "$DATABASE_FILE")" else log "ERROR" "Database backup file not found: $DATABASE_FILE" log "INFO" "Checked locations:" log "INFO" " $BACKUP_ROOT_DIR/$DATABASE_FILE" log "INFO" " $BACKUP_ROOT_DIR/database/daily/$DATABASE_FILE" log "INFO" " $BACKUP_ROOT_DIR/database/weekly/$DATABASE_FILE" log "INFO" " $BACKUP_ROOT_DIR/database/monthly/$DATABASE_FILE" log "INFO" " ./$DATABASE_FILE" exit 1 fi fi if [ -n "$STORAGE_FILE" ] && [[ "$STORAGE_FILE" != /* ]]; then if [ -f "$BACKUP_ROOT_DIR/$STORAGE_FILE" ]; then STORAGE_FILE="$BACKUP_ROOT_DIR/$STORAGE_FILE" elif [ -f "$BACKUP_ROOT_DIR/storage/daily/$STORAGE_FILE" ]; then STORAGE_FILE="$BACKUP_ROOT_DIR/storage/daily/$STORAGE_FILE" elif [ -f "$BACKUP_ROOT_DIR/storage/weekly/$STORAGE_FILE" ]; then STORAGE_FILE="$BACKUP_ROOT_DIR/storage/weekly/$STORAGE_FILE" elif [ -f "$BACKUP_ROOT_DIR/storage/monthly/$STORAGE_FILE" ]; then STORAGE_FILE="$BACKUP_ROOT_DIR/storage/monthly/$STORAGE_FILE" elif [ -f "$STORAGE_FILE" ]; then STORAGE_FILE="$(realpath "$STORAGE_FILE")" else log "ERROR" "Storage backup file not found: $STORAGE_FILE" log "INFO" "Checked locations:" log "INFO" " $BACKUP_ROOT_DIR/$STORAGE_FILE" log "INFO" " $BACKUP_ROOT_DIR/storage/daily/$STORAGE_FILE" log "INFO" " $BACKUP_ROOT_DIR/storage/weekly/$STORAGE_FILE" log "INFO" " $BACKUP_ROOT_DIR/storage/monthly/$STORAGE_FILE" log "INFO" " ./$STORAGE_FILE" exit 1 fi fi # Main execution main() { log "INFO" "============================================" log "INFO" "Starting complete system restoration" log "INFO" "Time: $(date)" log "INFO" "============================================" local start_time=$(date +%s) local overall_success=true # Confirm restoration unless --confirm was specified if [ "$CONFIRM" = false ]; then confirm_restore "$DATABASE_FILE" "$STORAGE_FILE" fi # Restore database if [ -n "$DATABASE_FILE" ]; then if ! restore_database "$DATABASE_FILE"; then overall_success=false log "ERROR" "Database restoration failed" fi else log "INFO" "Skipping database restoration (not requested)" fi # Restore storage if [ -n "$STORAGE_FILE" ]; then if ! restore_storage "$STORAGE_FILE"; then overall_success=false log "ERROR" "Storage restoration failed" fi else log "INFO" "Skipping storage restoration (not requested)" fi # Post-restore tasks if [ "$overall_success" = true ]; then post_restore_tasks local end_time=$(date +%s) local execution_time=$((end_time - start_time)) local execution_time_formatted=$(date -d@$execution_time -u +%H:%M:%S) log "SUCCESS" "Complete system restoration finished in $execution_time_formatted" # Send notification if command -v mail >/dev/null 2>&1; then local restored_items="" if [ -n "$DATABASE_FILE" ]; then restored_items="Database: $(basename "$DATABASE_FILE")" fi if [ -n "$STORAGE_FILE" ]; then if [ -n "$restored_items" ]; then restored_items="$restored_items, Storage: $(basename "$STORAGE_FILE")" else restored_items="Storage: $(basename "$STORAGE_FILE")" fi fi echo "Complete system restoration finished successfully at $(date). Restored: $restored_items" | mail -s "Timebank Complete Restore Success" "${BACKUP_NOTIFY_EMAIL:-$USER@localhost}" 2>/dev/null || true fi echo "" log "INFO" "Restoration summary:" if [ -n "$DATABASE_FILE" ]; then log "INFO" " Database restored from: $(basename "$DATABASE_FILE")" fi if [ -n "$STORAGE_FILE" ]; then log "INFO" " Storage restored from: $(basename "$STORAGE_FILE")" fi echo "" log "INFO" "Recommended next steps:" log "INFO" " 1. Test application functionality" log "INFO" " 2. Verify file permissions are correct" log "INFO" " 3. Check that all services are running properly" log "INFO" " 4. Review any Laravel logs for issues" else log "ERROR" "System restoration completed with errors" exit 1 fi log "INFO" "============================================" log "INFO" "Restoration process finished" log "INFO" "============================================" } # Load environment loader for database credentials (if needed) if [ -f "$SCRIPT_DIR/load-env.sh" ]; then source "$SCRIPT_DIR/load-env.sh" fi # Run main function main