#!/bin/bash # ============================================================================= # MySQL Database Backup Script # ============================================================================= # # Description: Professional MySQL database backup script with support for # multi-database backup, configuration files, notification # system, logging, automatic cleanup and more features # # Author: funnyzak # Version: 1.0.0 # Created: 2025-06-08 # Repository: https://gitee.com/funnyzak/dotfiles # # System Compatibility: # - Ubuntu 18.04+ # - Debian 9+ # - CentOS 7+ # - RHEL 7+ # - macOS 10.14+ # # Dependencies: # mysqldump (auto-install) # tar, gzip (system built-in) # curl (for notifications) # yq (YAML parsing, optional) # # Remote execution examples: # # Direct execution (using default configuration) # bash <(curl -fsSL https://gitee.com/funnyzak/dotfiles/raw/main/utilities/shell/mysql/mysql_backup.sh) # # # Execution with parameters # bash <(curl -fsSL https://gitee.com/funnyzak/dotfiles/raw/main/utilities/shell/mysql/mysql_backup.sh) \ # -h localhost -u backup_user -p backup_pass -d "db1,db2" -o /backup -c # # # Using remote configuration file # curl -fsSL https://gitee.com/funnyzak/dotfiles/raw/main/utilities/shell/mysql/mysql_backup.yaml > /tmp/backup.yaml # bash <(curl -fsSL https://gitee.com/funnyzak/dotfiles/raw/main/utilities/shell/mysql/mysql_backup.sh) \ # -f /tmp/backup.yaml # # Local usage examples: # # Basic backup # ./mysql_backup.sh # # # Backup specific databases to specific directory # ./mysql_backup.sh -h 192.168.1.100 -u root -p mypass -d "wordpress,nextcloud" -o /backup/mysql # # # Enable compression and retention # ./mysql_backup.sh -c -r 30 -v # # # Using configuration file # ./mysql_backup.sh -f ./mysql_backup.yaml # # # Enable notifications # ./mysql_backup.sh --apprise-url "http://localhost:8000/notify" --bark-url "https://api.day.app" --bark-key "your_key" # # Environment variable support: # MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD # BACKUP_OUTPUT_DIR, BACKUP_RETENTION_DAYS # APPRISE_URL, APPRISE_TAGS, BARK_URL, BARK_KEY # # ============================================================================= set -euo pipefail # ============================================================================= # Global constants definition # ============================================================================= readonly SCRIPT_NAME="$(basename "$0")" readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_VERSION="1.0.0" readonly SCRIPT_AUTHOR="funnyzak" readonly SCRIPT_URL="https://gitee.com/funnyzak/dotfiles/raw/main/utilities/shell/mysql/mysql_backup.sh" # Default configuration readonly DEFAULT_HOST="127.0.0.1" readonly DEFAULT_PORT="3306" readonly DEFAULT_USER="root" readonly DEFAULT_PASSWORD="root" readonly DEFAULT_OUTPUT_DIR="./" readonly DEFAULT_SUFFIX="sql" readonly DEFAULT_EXTRA_OPTS="--ssl-mode=DISABLED --single-transaction --routines --triggers --events --hex-blob --complete-insert" readonly DEFAULT_RETENTION="0" readonly DEFAULT_CONFIG_FILE="./mysql_backup.yaml" readonly DEFAULT_APPRISE_TAGS="all" readonly DEFAULT_NAME="$(hostname)" # Color definitions readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly BLUE='\033[0;34m' readonly PURPLE='\033[0;35m' readonly CYAN='\033[0;36m' readonly WHITE='\033[1;37m' readonly NC='\033[0m' # No Color # ============================================================================= # Global variables # ============================================================================= MYSQL_HOST="${MYSQL_HOST:-$DEFAULT_HOST}" MYSQL_PORT="${MYSQL_PORT:-$DEFAULT_PORT}" MYSQL_USER="${MYSQL_USER:-$DEFAULT_USER}" MYSQL_PASSWORD="${MYSQL_PASSWORD:-$DEFAULT_PASSWORD}" DATABASES="" OUTPUT_DIR="${BACKUP_OUTPUT_DIR:-$DEFAULT_OUTPUT_DIR}" FILE_SUFFIX="$DEFAULT_SUFFIX" EXTRA_OPTS="$DEFAULT_EXTRA_OPTS" PRE_CMD="" POST_CMD="" COMPRESS=false RETENTION="${BACKUP_RETENTION_DAYS:-$DEFAULT_RETENTION}" LOG_DIR="" VERBOSE=false CONFIG_FILE="$DEFAULT_CONFIG_FILE" APPRISE_URL="${APPRISE_URL:-}" APPRISE_TAGS="${APPRISE_TAGS:-$DEFAULT_APPRISE_TAGS}" BARK_URL="${BARK_URL:-}" BARK_KEY="${BARK_KEY:-}" INSTANCE_NAME="$DEFAULT_NAME" # Runtime variables LOG_FILE="" BACKUP_START_TIME="" BACKUP_END_TIME="" TOTAL_DATABASES=0 SUCCESSFUL_BACKUPS=0 FAILED_BACKUPS=0 BACKUP_FILES=() TOTAL_SIZE=0 # ============================================================================= # Utility functions # ============================================================================= # Logging function log() { local level="$1" shift local message="$*" local timestamp timestamp="$(date '+%Y-%m-%d %H:%M:%S')" local log_entry="[$timestamp] [$level] $message" # Console output case "$level" in "ERROR") echo -e "${RED}$log_entry${NC}" >&2 ;; "WARN") echo -e "${YELLOW}$log_entry${NC}" >&2 ;; "INFO") echo -e "${GREEN}$log_entry${NC}" ;; "DEBUG") if [[ "$VERBOSE" == "true" ]]; then echo -e "${CYAN}$log_entry${NC}" fi ;; *) echo "$log_entry" ;; esac # File output if [[ -n "$LOG_FILE" && -w "$(dirname "$LOG_FILE")" ]]; then echo "$log_entry" >> "$LOG_FILE" fi } # Error handling function error_exit() { log "ERROR" "$1" local error_body="Instance: $INSTANCE_NAME\\nError: $1\\nTime: $(date '+%Y-%m-%d %H:%M:%S')" send_notification "❌ MySQL Backup Failed" "$error_body" exit "${2:-1}" } # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Get operating system information get_os_info() { if [[ "$OSTYPE" == "darwin"* ]]; then echo "macos" elif [[ -f /etc/os-release ]]; then . /etc/os-release echo "$ID" elif [[ -f /etc/redhat-release ]]; then echo "rhel" else echo "unknown" fi } # Install MySQL client install_mysql_client() { local os_type os_type="$(get_os_info)" log "INFO" "Detected operating system: $os_type" log "INFO" "Installing MySQL client..." case "$os_type" in "ubuntu"|"debian") if command_exists apt-get; then sudo apt-get update -qq sudo apt-get install -y mysql-client else error_exit "Cannot find apt-get package manager" fi ;; "centos"|"rhel"|"fedora") if command_exists dnf; then sudo dnf install -y mysql elif command_exists yum; then sudo yum install -y mysql else error_exit "Cannot find yum/dnf package manager" fi ;; "macos") if command_exists brew; then brew install mysql-client else error_exit "Please install Homebrew first or manually install MySQL client" fi ;; *) error_exit "Unsupported operating system, please manually install mysqldump" ;; esac log "INFO" "MySQL client installation completed" } # Check system environment check_environment() { log "INFO" "Checking system environment..." log "DEBUG" "Starting environment check for required tools" # Check mysqldump if ! command_exists mysqldump; then log "WARN" "mysqldump not found, attempting to install..." install_mysql_client if ! command_exists mysqldump; then error_exit "mysqldump installation failed, please manually install MySQL client" fi fi log "DEBUG" "mysqldump found: $(which mysqldump)" # Check required tools local required_tools=("tar" "gzip" "curl") for tool in "${required_tools[@]}"; do if ! command_exists "$tool"; then error_exit "Missing required tool: $tool" fi log "DEBUG" "$tool found: $(which $tool)" done log "INFO" "System environment check completed" } # Note: MySQL connection test removed as client may only have mysqldump installed # The connection will be tested during the actual backup process # Get database list get_database_list() { if [[ -n "$DATABASES" ]]; then # Use specified database list echo "$DATABASES" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' else # Get all databases (excluding system databases) local mysql_cmd="mysql -h$MYSQL_HOST -P$MYSQL_PORT -u$MYSQL_USER" if [[ -n "$MYSQL_PASSWORD" ]]; then mysql_cmd="$mysql_cmd -p$MYSQL_PASSWORD" fi if command_exists mysql; then $mysql_cmd -e "SHOW DATABASES;" 2>/dev/null | grep -v -E '^(Database|information_schema|performance_schema|mysql|sys)$' || { error_exit "Unable to retrieve database list and no databases specified" } else error_exit "MySQL client not found and no databases specified" fi fi } # Create backup directory create_backup_directory() { if [[ ! -d "$OUTPUT_DIR" ]]; then log "INFO" "Creating backup directory: $OUTPUT_DIR" log "DEBUG" "Backup directory path: $(realpath "$OUTPUT_DIR" 2>/dev/null || echo "$OUTPUT_DIR")" mkdir -p "$OUTPUT_DIR" || error_exit "Cannot create backup directory: $OUTPUT_DIR" fi # Set directory permissions chmod 750 "$OUTPUT_DIR" || log "WARN" "Cannot set backup directory permissions" log "DEBUG" "Backup directory permissions: $(ls -ld "$OUTPUT_DIR")" } # Create log directory and file setup_logging() { if [[ -n "$LOG_DIR" ]]; then if [[ ! -d "$LOG_DIR" ]]; then mkdir -p "$LOG_DIR" || error_exit "Cannot create log directory: $LOG_DIR" fi LOG_FILE="$LOG_DIR/mysql_backup_$(date '+%Y%m%d_%H%M%S').log" touch "$LOG_FILE" || error_exit "Cannot create log file: $LOG_FILE" chmod 640 "$LOG_FILE" log "INFO" "Log file: $LOG_FILE" log "DEBUG" "Log directory: $LOG_DIR" log "DEBUG" "Log file permissions: $(ls -l "$LOG_FILE")" fi } # Parse YAML configuration file (simple implementation) parse_yaml_config() { local config_file="$1" if [[ ! -f "$config_file" ]]; then log "WARN" "Configuration file does not exist: $config_file" return 0 fi log "INFO" "Parsing configuration file: $config_file" log "DEBUG" "Configuration file size: $(wc -c < "$config_file") bytes" # Track current section context for nested YAML parsing local current_section="" local current_subsection="" # Simple YAML parsing (supports basic format only) while IFS= read -r line; do # Skip comments and empty lines [[ "$line" =~ ^[[:space:]]*# ]] && continue [[ "$line" =~ ^[[:space:]]*$ ]] && continue # Detect section headers (no indentation) if [[ "$line" =~ ^([^[:space:]]+):[[:space:]]*$ ]]; then current_section="${BASH_REMATCH[1]}" current_subsection="" log "DEBUG" "Entering section: $current_section" continue fi # Detect subsection headers (2 spaces indentation) if [[ "$line" =~ ^[[:space:]]{2}([^[:space:]]+):[[:space:]]*$ ]]; then current_subsection="${BASH_REMATCH[1]}" log "DEBUG" "Entering subsection: $current_section.$current_subsection" continue fi # Parse key-value pairs if [[ "$line" =~ ^[[:space:]]*([^:]+):[[:space:]]*(.*)$ ]]; then local key="${BASH_REMATCH[1]// /}" local value="${BASH_REMATCH[2]}" # Remove quotes value="${value#\"}" value="${value%\"}" value="${value#\'}" value="${value%\'}" log "DEBUG" "Parsing config: $current_section.$current_subsection.$key = $value" # Handle nested configuration based on section context if [[ "$current_section" == "notifications" ]]; then case "$current_subsection.$key" in "apprise.url") APPRISE_URL="$value" ;; "apprise.tags") APPRISE_TAGS="$value" ;; "bark.url") BARK_URL="$value" ;; "bark.device_key") BARK_KEY="$value" ;; esac else # Handle top-level and other nested configurations case "$key" in "name") INSTANCE_NAME="$value" ;; "host") MYSQL_HOST="$value" ;; "port") MYSQL_PORT="$value" ;; "user") MYSQL_USER="$value" ;; "password") MYSQL_PASSWORD="$value" ;; "databases") DATABASES="$value" ;; "output_dir") OUTPUT_DIR="$value" ;; "file_suffix") FILE_SUFFIX="$value" ;; "extra_options") EXTRA_OPTS="$value" ;; "compress") [[ "$value" == "true" ]] && COMPRESS=true ;; "retention_days") RETENTION="$value" ;; "pre_backup") PRE_CMD="$value" ;; "post_backup") POST_CMD="$value" ;; "log_dir") LOG_DIR="$value" ;; "verbose") [[ "$value" == "true" ]] && VERBOSE=true ;; esac fi fi done < "$config_file" log "INFO" "Configuration file parsing completed" log "DEBUG" "Final instance name: $INSTANCE_NAME" log "DEBUG" "Final Apprise URL: $APPRISE_URL" log "DEBUG" "Final Bark URL: $BARK_URL" } # Send notification send_notification() { local title="$1" local body="$2" local notification_sent=false log "DEBUG" "Attempting to send notification: $title" log "DEBUG" "Notification body: $body" # Apprise notification if [[ -n "$APPRISE_URL" ]]; then log "DEBUG" "Sending Apprise notification..." log "DEBUG" "Apprise URL: $APPRISE_URL, Tags: $APPRISE_TAGS" # Convert \n to actual newlines for Apprise form data local apprise_body apprise_body="$(echo -e "$body")" if curl -X POST \ -F "body=$apprise_body" \ -F "tags=$APPRISE_TAGS" \ "$APPRISE_URL" \ >/dev/null 2>&1; then log "INFO" "Apprise notification sent successfully" notification_sent=true else log "WARN" "Apprise notification failed" fi fi # Bark notification if [[ -n "$BARK_URL" && -n "$BARK_KEY" ]]; then log "DEBUG" "Sending Bark notification..." log "DEBUG" "Bark URL: $BARK_URL, Device Key: ${BARK_KEY:0:8}..." # Escape special characters for JSON and convert \n to \\n for proper JSON local bark_body bark_title bark_body="$(echo "$body")" bark_title="$(echo "$title")" if curl -X POST "$BARK_URL/push" \ -H 'Content-Type: application/json; charset=utf-8' \ -d "{ \"body\": \"$bark_body\", \"device_key\": \"$BARK_KEY\", \"title\": \"$bark_title\" }" \ >/dev/null 2>&1; then log "INFO" "Bark notification sent successfully" notification_sent=true else log "WARN" "Bark notification failed" fi fi # Log if no notification services are configured if [[ -z "$APPRISE_URL" && ( -z "$BARK_URL" || -z "$BARK_KEY" ) ]]; then log "DEBUG" "No notification services configured (APPRISE_URL or BARK_URL+BARK_KEY required)" fi if [[ "$notification_sent" == "false" ]]; then log "DEBUG" "No notifications were sent" fi } # Format file size format_size() { local size="$1" if [[ "$size" -gt 1073741824 ]]; then echo "$(( size / 1073741824 ))GB" elif [[ "$size" -gt 1048576 ]]; then echo "$(( size / 1048576 ))MB" elif [[ "$size" -gt 1024 ]]; then echo "$(( size / 1024 ))KB" else echo "${size}B" fi } # Serial database backup backup_databases_serial() { local databases=("$@") log "DEBUG" "Starting serial backup" for database in "${databases[@]}"; do local timestamp timestamp="$(date '+%Y-%m-%d_%H-%M-%S')" local backup_file="$OUTPUT_DIR/${database}_${timestamp}.$FILE_SUFFIX" log "INFO" "Starting backup for database: $database" log "DEBUG" "Backup file path: $backup_file" # Build mysqldump command local dump_cmd="mysqldump -h$MYSQL_HOST -P$MYSQL_PORT -u$MYSQL_USER" if [[ -n "$MYSQL_PASSWORD" ]]; then dump_cmd="$dump_cmd -p$MYSQL_PASSWORD" fi dump_cmd="$dump_cmd $EXTRA_OPTS $database" # Create safe debug output (mask password) local debug_cmd="$dump_cmd" if [[ -n "$MYSQL_PASSWORD" ]]; then debug_cmd="${dump_cmd//-p$MYSQL_PASSWORD/-p***}" fi log "DEBUG" "Mysqldump command: $debug_cmd" eval "$dump_cmd" > "$backup_file" # check backup file exists and not empty if [[ -s "$backup_file" ]]; then # Set file permissions chmod 640 "$backup_file" log "DEBUG" "Backup file permissions set to 640" # Compress file if [[ "$COMPRESS" == "true" ]]; then log "DEBUG" "Compressing backup file: $backup_file" tar -czf "${backup_file}.tar.gz" -C "$(dirname "$backup_file")" "$(basename "$backup_file")" && rm "$backup_file" backup_file="${backup_file}.tar.gz" log "DEBUG" "Compression completed: $backup_file" fi local file_size file_size="$(stat -c%s "$backup_file" 2>/dev/null || stat -f%z "$backup_file" 2>/dev/null || echo 0)" TOTAL_SIZE=$((TOTAL_SIZE + file_size)) BACKUP_FILES+=("$backup_file") SUCCESSFUL_BACKUPS=$((SUCCESSFUL_BACKUPS + 1)) log "INFO" "Database $database backup successful: $backup_file ($(format_size "$file_size"))" log "DEBUG" "Total backups completed: $SUCCESSFUL_BACKUPS" else FAILED_BACKUPS=$((FAILED_BACKUPS + 1)) log "ERROR" "Database $database backup failed" log "DEBUG" "Failed backups count: $FAILED_BACKUPS" [[ -f "$backup_file" ]] && rm -f "$backup_file" fi done } # Clean up old backups cleanup_old_backups() { if [[ "$RETENTION" -eq 0 ]]; then log "INFO" "Skipping backup cleanup (retention days is 0)" return 0 fi log "INFO" "Cleaning up backup files older than $RETENTION days..." log "DEBUG" "Cleanup directory: $OUTPUT_DIR" local deleted_count=0 while IFS= read -r -d '' file; do log "DEBUG" "Deleting old backup file: $file" rm -f "$file" deleted_count=$((deleted_count + 1)) done < <(find "$OUTPUT_DIR" -name "*.sql" -o -name "*.sql.tar.gz" -type f -mtime +"$RETENTION" -print0 2>/dev/null) if [[ "$deleted_count" -gt 0 ]]; then log "INFO" "Cleanup completed, deleted $deleted_count old backup files" else log "INFO" "No old backup files to clean up" fi log "DEBUG" "Cleanup process finished" } # Show backup statistics show_backup_statistics() { local duration=$((BACKUP_END_TIME - BACKUP_START_TIME)) local duration_formatted if [[ "$duration" -ge 3600 ]]; then duration_formatted="$((duration / 3600))h $((duration % 3600 / 60))m $((duration % 60))s" elif [[ "$duration" -ge 60 ]]; then duration_formatted="$((duration / 60))m $((duration % 60))s" else duration_formatted="${duration}s" fi log "INFO" "Backup Statistics:" log "INFO" " Total databases: $TOTAL_DATABASES" log "INFO" " Successful backups: $SUCCESSFUL_BACKUPS" log "INFO" " Failed backups: $FAILED_BACKUPS" log "INFO" " Total file size: $(format_size "$TOTAL_SIZE")" log "INFO" " Backup duration: $duration_formatted" log "INFO" " Backup files:" for file in "${BACKUP_FILES[@]}"; do local file_size file_size="$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)" log "INFO" " $(basename "$file") ($(format_size "$file_size"))" done log "DEBUG" "Statistics calculation completed" } # Show help information show_help() { echo -e " ${WHITE}MySQL Database Backup Script v$SCRIPT_VERSION${NC} ${CYAN}Author: $SCRIPT_AUTHOR${NC} ${YELLOW}Description:${NC} Professional MySQL database backup script with support for multi-database backup, configuration files, notification system, logging, automatic cleanup and more features. ${YELLOW}Usage:${NC} $SCRIPT_NAME [options] ${YELLOW}Options:${NC} ${GREEN}-n, --name${NC} Instance name for notifications (default: hostname) ${GREEN}-h, --host${NC} MySQL server address (default: $DEFAULT_HOST) ${GREEN}-P, --port${NC} MySQL port (default: $DEFAULT_PORT) ${GREEN}-u, --user${NC} MySQL username (default: $DEFAULT_USER) ${GREEN}-p, --password${NC} MySQL password (default: $DEFAULT_PASSWORD) ${GREEN}-d, --databases${NC} Database names to backup, comma-separated (default: backup all databases) ${GREEN}-o, --output${NC} Backup file output directory (default: $DEFAULT_OUTPUT_DIR) ${GREEN}-s, --suffix${NC} Backup file suffix (default: $DEFAULT_SUFFIX) ${GREEN}-e, --extra-opts${NC} Additional mysqldump parameters ${GREEN} --pre-cmd${NC} Command to execute before backup ${GREEN} --post-cmd${NC} Command to execute after backup ${GREEN}-c, --compress${NC} Use tar compression for backup files (default: false) ${GREEN}-r, --retention${NC} Backup retention days (default: $DEFAULT_RETENTION, 0 means no cleanup) ${GREEN}-l, --log-dir${NC} Log file directory (if not specified, no log file) ${GREEN}-v, --verbose${NC} Enable verbose debug output (default: false) ${GREEN}-f, --config${NC} Configuration file path (default: $DEFAULT_CONFIG_FILE) ${GREEN} --apprise-url${NC} Apprise notification URL ${GREEN} --apprise-tags${NC} Apprise notification tags (default: $DEFAULT_APPRISE_TAGS) ${GREEN} --bark-url${NC} Bark server URL ${GREEN} --bark-key${NC} Bark device key ${GREEN} --help${NC} Show help information ${YELLOW}Examples:${NC} # Basic backup $SCRIPT_NAME # Backup specific databases $SCRIPT_NAME -h localhost -u root -p mypass -d "db1,db2" -o /backup # Enable compression and retention $SCRIPT_NAME -c -r 30 -v # Use configuration file $SCRIPT_NAME -f ./mysql_backup.yaml # Remote execution bash <(curl -fsSL $SCRIPT_URL) -h localhost -u root -p mypass ${YELLOW}Environment Variables:${NC} MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD BACKUP_OUTPUT_DIR, BACKUP_RETENTION_DAYS APPRISE_URL, APPRISE_TAGS, BARK_URL, BARK_KEY ${YELLOW}More Information:${NC} Project: https://gitee.com/funnyzak/dotfiles Script: $SCRIPT_URL " } # Parse command line arguments parse_arguments() { while [[ $# -gt 0 ]]; do case $1 in -n|--name) INSTANCE_NAME="$2" shift 2 ;; -h|--host) MYSQL_HOST="$2" shift 2 ;; -P|--port) MYSQL_PORT="$2" shift 2 ;; -u|--user) MYSQL_USER="$2" shift 2 ;; -p|--password) MYSQL_PASSWORD="$2" shift 2 ;; -d|--databases) DATABASES="$2" shift 2 ;; -o|--output) OUTPUT_DIR="$2" shift 2 ;; -s|--suffix) FILE_SUFFIX="$2" shift 2 ;; -e|--extra-opts) EXTRA_OPTS="$2" shift 2 ;; --pre-cmd) PRE_CMD="$2" shift 2 ;; --post-cmd) POST_CMD="$2" shift 2 ;; -c|--compress) COMPRESS=true shift ;; -r|--retention) RETENTION="$2" shift 2 ;; -l|--log-dir) LOG_DIR="$2" shift 2 ;; -v|--verbose) VERBOSE=true shift ;; -f|--config) CONFIG_FILE="$2" shift 2 ;; --apprise-url) APPRISE_URL="$2" shift 2 ;; --apprise-tags) APPRISE_TAGS="$2" shift 2 ;; --bark-url) BARK_URL="$2" shift 2 ;; --bark-key) BARK_KEY="$2" shift 2 ;; --help) show_help exit 0 ;; *) error_exit "Unknown parameter: $1" ;; esac done } # Main function main() { # Parse command line arguments parse_arguments "$@" # Parse configuration file if [[ -f "$CONFIG_FILE" ]]; then parse_yaml_config "$CONFIG_FILE" fi # Set default instance name if empty if [[ -z "$INSTANCE_NAME" ]]; then INSTANCE_NAME="$DEFAULT_NAME" fi # Setup logging setup_logging log "INFO" "MySQL backup script started (version: $SCRIPT_VERSION)" log "INFO" "Script author: $SCRIPT_AUTHOR" log "INFO" "Instance name: $INSTANCE_NAME" log "DEBUG" "Configuration: Host=$MYSQL_HOST, Port=$MYSQL_PORT, User=$MYSQL_USER" log "DEBUG" "Output directory: $OUTPUT_DIR" log "DEBUG" "Notification config: Apprise URL=${APPRISE_URL:+configured}, Bark URL=${BARK_URL:+configured}, Bark Key=${BARK_KEY:+configured}" # Record start time BACKUP_START_TIME="$(date +%s)" # Check system environment check_environment # Create backup directory create_backup_directory # Execute pre-backup command if [[ -n "$PRE_CMD" ]]; then log "INFO" "Executing pre-backup command: $PRE_CMD" eval "$PRE_CMD" || log "WARN" "Pre-backup command execution failed" fi # Get database list log "INFO" "Getting database list..." local databases_array=() while IFS= read -r database; do [[ -n "$database" ]] && databases_array+=("$database") done < <(get_database_list) TOTAL_DATABASES="${#databases_array[@]}" if [[ "$TOTAL_DATABASES" -eq 0 ]]; then error_exit "No databases found for backup" fi log "INFO" "Found $TOTAL_DATABASES databases for backup" log "DEBUG" "Database list: ${databases_array[*]}" # Start backup log "INFO" "Starting database backup..." backup_databases_serial "${databases_array[@]}" # Record end time BACKUP_END_TIME="$(date +%s)" # Clean up old backups cleanup_old_backups # Execute post-backup command if [[ -n "$POST_CMD" ]]; then log "INFO" "Executing post-backup command: $POST_CMD" eval "$POST_CMD" || log "WARN" "Post-backup command execution failed" fi # Show statistics show_backup_statistics # Send notifications if [[ "$FAILED_BACKUPS" -eq 0 ]]; then local notification_body="✅ MySQL backup completed successfully\\nInstance: $INSTANCE_NAME\\nDatabases: ${databases_array[*]}($SUCCESSFUL_BACKUPS)\nTotal size: $(format_size "$TOTAL_SIZE")\\nTime: $(date '+%Y-%m-%d %H:%M:%S')" send_notification "MySQL Backup Success" "$notification_body" log "INFO" "All database backups completed successfully" else local notification_body="⚠️ MySQL backup partially failed\\nInstance: $INSTANCE_NAME\\nDatabases: ${databases_array[*]}($SUCCESSFUL_BACKUPS)\nFailed: $FAILED_BACKUPS\\nTime: $(date '+%Y-%m-%d %H:%M:%S')" send_notification "MySQL Backup Warning" "$notification_body" log "WARN" "Some database backups failed" exit 1 fi } # Script entry point if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi