Initial commit
This commit is contained in:
394
scripts/restore-database.sh
Executable file
394
scripts/restore-database.sh
Executable file
@@ -0,0 +1,394 @@
|
||||
#!/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" <<EOF
|
||||
[mysqldump]
|
||||
host=${DB_HOST:-127.0.0.1}
|
||||
port=${DB_PORT:-3306}
|
||||
user=$DB_USERNAME
|
||||
password=$DB_PASSWORD
|
||||
EOF
|
||||
|
||||
# Set secure permissions on the config file
|
||||
chmod 600 "$mysql_cnf_file"
|
||||
|
||||
# Create a temporary backup
|
||||
mysqldump \
|
||||
--defaults-extra-file="$mysql_cnf_file" \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--add-drop-database \
|
||||
--databases "$DB_DATABASE" | gzip > "$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" <<EOF
|
||||
[mysql]
|
||||
host=${DB_HOST:-127.0.0.1}
|
||||
port=${DB_PORT:-3306}
|
||||
user=$DB_USERNAME
|
||||
password=$DB_PASSWORD
|
||||
EOF
|
||||
|
||||
# Set secure permissions on the config file
|
||||
chmod 600 "$mysql_cnf_file"
|
||||
|
||||
# Perform the restore using the config file
|
||||
if gzip -dc "$backup_file" | mysql --defaults-extra-file="$mysql_cnf_file"; then
|
||||
log "SUCCESS" "Database restore completed successfully"
|
||||
rm -f "$mysql_cnf_file"
|
||||
return 0
|
||||
else
|
||||
log "ERROR" "Database restore failed"
|
||||
rm -f "$mysql_cnf_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
BACKUP_FILE=""
|
||||
CONFIRM=false
|
||||
LIST_BACKUPS=false
|
||||
LATEST=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--confirm)
|
||||
CONFIRM=true
|
||||
shift
|
||||
;;
|
||||
--list-backups)
|
||||
LIST_BACKUPS=true
|
||||
shift
|
||||
;;
|
||||
--latest)
|
||||
LATEST=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_usage
|
||||
;;
|
||||
-*)
|
||||
log "ERROR" "Unknown option: $1"
|
||||
show_usage
|
||||
;;
|
||||
*)
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE="$1"
|
||||
else
|
||||
log "ERROR" "Multiple backup files specified"
|
||||
show_usage
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Load environment loader
|
||||
source "$SCRIPT_DIR/load-env.sh"
|
||||
|
||||
# Load environment variables
|
||||
if ! load_env "$PROJECT_ROOT/.env"; then
|
||||
log "ERROR" ".env file not found in $PROJECT_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate required environment variables
|
||||
if [ -z "$DB_DATABASE" ] || [ -z "$DB_USERNAME" ] || [ -z "$DB_HOST" ]; then
|
||||
log "ERROR" "Required database environment variables not found (DB_DATABASE, DB_USERNAME, DB_HOST)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Handle list backups option
|
||||
if [ "$LIST_BACKUPS" = true ]; then
|
||||
list_backups
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Handle latest backup option
|
||||
if [ "$LATEST" = true ]; then
|
||||
BACKUP_FILE=$(get_latest_backup)
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
log "ERROR" "No backups found"
|
||||
exit 1
|
||||
fi
|
||||
log "INFO" "Using latest backup: $(basename "$BACKUP_FILE")"
|
||||
fi
|
||||
|
||||
# Validate backup file argument
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
log "ERROR" "No backup file specified"
|
||||
show_usage
|
||||
fi
|
||||
|
||||
# Resolve backup file path
|
||||
if [[ "$BACKUP_FILE" != /* ]]; then
|
||||
# Relative path - check if it exists relative to backup directory
|
||||
if [ -f "$BACKUP_ROOT_DIR/$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE="$BACKUP_ROOT_DIR/$BACKUP_FILE"
|
||||
elif [ -f "$BACKUP_ROOT_DIR/database/daily/$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE="$BACKUP_ROOT_DIR/database/daily/$BACKUP_FILE"
|
||||
elif [ -f "$BACKUP_ROOT_DIR/database/weekly/$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE="$BACKUP_ROOT_DIR/database/weekly/$BACKUP_FILE"
|
||||
elif [ -f "$BACKUP_ROOT_DIR/database/monthly/$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE="$BACKUP_ROOT_DIR/database/monthly/$BACKUP_FILE"
|
||||
elif [ -f "$BACKUP_FILE" ]; then
|
||||
# Exists relative to current directory
|
||||
BACKUP_FILE="$(realpath "$BACKUP_FILE")"
|
||||
else
|
||||
log "ERROR" "Database backup file not found: $BACKUP_FILE"
|
||||
log "INFO" "Checked locations:"
|
||||
log "INFO" " $BACKUP_ROOT_DIR/$BACKUP_FILE"
|
||||
log "INFO" " $BACKUP_ROOT_DIR/database/daily/$BACKUP_FILE"
|
||||
log "INFO" " $BACKUP_ROOT_DIR/database/weekly/$BACKUP_FILE"
|
||||
log "INFO" " $BACKUP_ROOT_DIR/database/monthly/$BACKUP_FILE"
|
||||
log "INFO" " ./$BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log "INFO" "============================================"
|
||||
log "INFO" "Starting database restore process"
|
||||
log "INFO" "Time: $(date)"
|
||||
log "INFO" "============================================"
|
||||
|
||||
local start_time=$(date +%s)
|
||||
|
||||
# Confirm restore unless --confirm was specified
|
||||
if [ "$CONFIRM" = false ]; then
|
||||
confirm_restore "$BACKUP_FILE"
|
||||
fi
|
||||
|
||||
# Create pre-restore backup
|
||||
if ! create_pre_restore_backup; then
|
||||
log "WARNING" "Pre-restore backup failed, but continuing with restore"
|
||||
fi
|
||||
|
||||
# Perform restore
|
||||
if restore_database "$BACKUP_FILE"; then
|
||||
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" "Database restore completed successfully in $execution_time_formatted"
|
||||
log "INFO" "Restored from: $(basename "$BACKUP_FILE")"
|
||||
|
||||
# Send notification
|
||||
if command -v mail >/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
|
||||
Reference in New Issue
Block a user