#!/bin/bash # Laravel Timebank Database Restore Script # Restores MySQL database from compressed backup # Usage: ./restore-database.sh [backup_file] [options] # Options: --confirm, --list-backups, --latest 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 [backup_file] [options]" echo "" echo "Arguments:" echo " backup_file - Path to backup file (relative to backup directory or absolute)" echo "" echo "Options:" echo " --confirm - Skip confirmation prompt" echo " --list-backups - List available backups" echo " --latest - Restore from latest backup" echo " --help - Show this help message" echo "" echo "Examples:" echo " $0 --list-backups # List available backups" echo " $0 --latest # Restore latest backup" echo " $0 database/daily/mydb_daily_20240101.sql.gz # Restore specific backup" echo "" exit 0 } # Function to list available backups list_backups() { log "INFO" "Available database backups:" echo "" if [ ! -d "$BACKUP_ROOT_DIR/database" ]; then log "WARNING" "No backup directory found" return 0 fi local backup_found=false 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)) if [ ${#backups[@]} -gt 0 ]; then echo -e "${BLUE}$backup_type backups:${NC}" 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 echo "" backup_found=true fi fi done if [ "$backup_found" = false ]; then log "WARNING" "No database backups found" fi } # Function to find latest backup get_latest_backup() { local latest_backup="" local latest_time=0 if [ -d "$BACKUP_ROOT_DIR/database" ]; 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/database" -name "*.sql.gz" -type f -print0) fi echo "$latest_backup" } # Function to confirm restore confirm_restore() { local backup_file="$1" echo "" echo -e "${YELLOW}WARNING: This will REPLACE the current database!${NC}" echo -e "Database: ${RED}$DB_DATABASE${NC}" echo -e "Backup file: ${BLUE}$(basename "$backup_file")${NC}" echo -e "Backup size: $(du -h "$backup_file" | cut -f1)" echo -e "Backup date: $(date -r "$backup_file" '+%Y-%m-%d %H:%M:%S')" echo "" read -p "Are you sure you want to continue? [y/N]: " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then log "INFO" "Restore cancelled by user" exit 0 fi } # Function to create database backup before restore create_pre_restore_backup() { log "INFO" "Creating pre-restore backup of current database" local pre_restore_dir="$BACKUP_ROOT_DIR/database/pre-restore" mkdir -p "$pre_restore_dir" local timestamp=$(date '+%Y%m%d_%H%M%S') local backup_file="$pre_restore_dir/${DB_DATABASE}_pre_restore_${timestamp}.sql.gz" # Use the database backup script if available if [ -x "$SCRIPT_DIR/backup-database.sh" ]; then log "INFO" "Using backup script for pre-restore backup" # Create MySQL configuration file for secure password handling local mysql_cnf_file="/tmp/mysql_pre_restore_$$.cnf" cat > "$mysql_cnf_file" < "$backup_file" # Clean up the temporary config file rm -f "$mysql_cnf_file" if [ -f "$backup_file" ] && [ -s "$backup_file" ]; then log "SUCCESS" "Pre-restore backup created: $backup_file" else log "ERROR" "Pre-restore backup failed" return 1 fi fi } # Function to restore database restore_database() { local backup_file="$1" log "INFO" "Starting database restore from: $(basename "$backup_file")" # Verify backup file exists and is readable if [ ! -f "$backup_file" ] || [ ! -r "$backup_file" ]; then log "ERROR" "Backup file not found or not readable: $backup_file" return 1 fi # Test if backup file is valid gzip if ! gzip -t "$backup_file" 2>/dev/null; then log "ERROR" "Backup file is corrupted or not a valid gzip file" return 1 fi # Extract and restore log "INFO" "Decompressing and restoring backup..." # Create MySQL configuration file for secure password handling local mysql_cnf_file="/tmp/mysql_restore_$$.cnf" cat > "$mysql_cnf_file" </dev/null 2>&1; then echo "Database restore completed successfully at $(date). Restored from: $(basename "$BACKUP_FILE")" | mail -s "Timebank DB Restore Success" "${BACKUP_NOTIFY_EMAIL:-$USER@localhost}" 2>/dev/null || true fi # Recommend running Laravel commands echo "" log "INFO" "Recommended post-restore steps:" log "INFO" " 1. php artisan migrate:status" log "INFO" " 2. php artisan config:clear" log "INFO" " 3. php artisan cache:clear" log "INFO" " 4. Test application functionality" else log "ERROR" "Database restore failed" exit 1 fi log "INFO" "============================================" log "INFO" "Restore process finished" log "INFO" "============================================" } # Run main function main