Skip to content

Backup_scripts

This chapter covers comprehensive backup and restore scripts for databases, files, and entire systems. These scripts are essential for disaster recovery and data protection.


#!/usr/bin/env bash
# backup_mysql.sh - Backup MySQL/MariaDB databases
set -euo pipefail
# Configuration
DB_HOST="${DB_HOST:-localhost}"
DB_USER="${DB_USER:-root}"
DB_PASSWORD="${DB_PASSWORD:-}"
BACKUP_DIR="${BACKUP_DIR:-/backups/mysql}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
COMPRESSION="${COMPRESSION:-gzip}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; exit 1; }
# Validate configuration
: "${DB_PASSWORD:?DB_PASSWORD environment variable required}"
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Get list of databases
get_databases() {
if [[ "$DB_PASSWORD" == "" ]]; then
mysql -h "$DB_HOST" -u "$DB_USER" -e "SHOW DATABASES;" | grep -v Database
else
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" -e "SHOW DATABASES;" | grep -v Database
fi
}
# Backup single database
backup_database() {
local db_name="$1"
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="${BACKUP_DIR}/${db_name}_${timestamp}.sql"
log "Backing up database: $db_name"
if [[ "$DB_PASSWORD" == "" ]]; then
mysqldump -h "$DB_HOST" -u "$DB_USER" \
--single-transaction \
--quick \
--lock-tables=false \
"$db_name" > "$backup_file"
else
mysqldump -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" \
--single-transaction \
--quick \
--lock-tables=false \
"$db_name" > "$backup_file"
fi
# Compress if enabled
if [[ "$COMPRESSION" == "gzip" ]]; then
gzip "$backup_file"
backup_file="${backup_file}.gz"
fi
log "Backup created: $backup_file"
}
# Cleanup old backups
cleanup_old_backups() {
log "Cleaning up backups older than $RETENTION_DAYS days..."
find "$BACKUP_DIR" -name "*.sql*" -mtime +$RETENTION_DAYS -delete
log "Cleanup complete"
}
# Main
main() {
log "Starting MySQL backup..."
# Backup all databases
while read -r db; do
# Skip system databases
[[ "$db" == "information_schema" ]] && continue
[[ "$db" == "performance_schema" ]] && continue
[[ "$db" == "mysql" ]] && continue
backup_database "$db"
done < <(get_databases)
# Cleanup
cleanup_old_backups
log "MySQL backup complete"
}
main
#!/usr/bin/env bash
# backup_postgres.sh - Backup PostgreSQL databases
set -euo pipefail
# Configuration
DB_HOST="${DB_HOST:-localhost}"
DB_USER="${DB_USER:-postgres}"
DB_PORT="${DB_PORT:-5432}"
BACKUP_DIR="${BACKUP_DIR:-/backups/postgres}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Get list of databases
get_databases() {
psql -h "$DB_HOST" -U "$DB_USER" -p "$DB_PORT" -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;"
}
# Backup single database
backup_database() {
local db_name="$1"
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="${BACKUP_DIR}/${db_name}_${timestamp}.sql"
log "Backing up database: $db_name"
PGPASSWORD="$DB_PASSWORD" pg_dump -h "$DB_HOST" -U "$DB_USER" -p "$DB_PORT" \
-Fc \
"$db_name" > "$backup_file"
log "Backup created: $backup_file"
}
# Main
main() {
log "Starting PostgreSQL backup..."
while read -r db; do
db=$(echo "$db" | xargs)
[[ -z "$db" ]] && continue
backup_database "$db"
done < <(get_databases)
log "PostgreSQL backup complete"
}
main

#!/usr/bin/env bash
# backup_incremental.sh - Incremental file backup
set -euo pipefail
# Configuration
SOURCE_DIR="${1:-/data}"
BACKUP_DIR="${2:-/backups}"
EXCLUDE_FILE="${EXCLUDE_FILE:-/etc/backup/exclude.txt}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
# Create timestamped backup directory
timestamp=$(date +%Y%m%d_%H%M%S)
current_backup="$BACKUP_DIR/current"
new_backup="$BACKUP_DIR/backup_$timestamp"
log "Starting incremental backup..."
# Create hard-link based incremental backup
# This saves space by using hard links for unchanged files
if [[ -d "$current_backup" ]]; then
# Use rsync with link-dest for incremental backup
rsync -avh \
--delete \
--delete-excluded \
--link-dest="$current_backup" \
$([[ -f "$EXCLUDE_FILE" ]] && echo "--exclude-from=$EXCLUDE_FILE") \
"$SOURCE_DIR/" "$new_backup/"
# Remove old current and link new backup
rm -rf "$current_backup"
ln -s "$new_backup" "$current_backup"
else
# First backup - full backup
rsync -avh \
--delete \
--delete-excluded \
$([[ -f "$EXCLUDE_FILE" ]] && echo "--exclude-from=$EXCLUDE_FILE") \
"$SOURCE_DIR/" "$new_backup/"
ln -s "$new_backup" "$current_backup"
fi
log "Backup complete: $new_backup"
log "Current symlink updated to: $current_backup"
#!/usr/bin/env bash
# backup_encrypted.sh - Encrypted file backup
set -euo pipefail
# Configuration
SOURCE_DIR="${1:-/data}"
BACKUP_DIR="${2:-/backups/encrypted}"
ENCRYPTION_KEY="${ENCRYPTION_KEY:-}"
PASSPHRASE="${PASSPHRASE:-}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
# Validate encryption key
: "${ENCRYPTION_KEY:?ENCRYPTION_KEY environment variable required}"
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Create backup filename
timestamp=$(date +%Y%m%d_%H%M%S)
backup_file="$BACKUP_DIR/backup_${timestamp}.tar.gz.gpg"
log "Creating encrypted backup..."
# Create compressed archive and encrypt
tar -czf - -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")" | \
gpg --symmetric \
--batch \
--passphrase "$ENCRYPTION_KEY" \
--cipher-algo AES256 \
--compress-algo none \
-o "$backup_file"
log "Encrypted backup created: $backup_file"
log "Backup size: $(du -h "$backup_file" | cut -f1)"

#!/usr/bin/env bash
# restore_mysql.sh - Restore MySQL database
set -euo pipefail
# Configuration
DB_HOST="${DB_HOST:-localhost}"
DB_USER="${DB_USER:-root}"
DB_PASSWORD="${DB_PASSWORD:-}"
BACKUP_FILE="${1:-}"
TARGET_DB="${2:-}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; exit 1; }
# Validate
[[ -z "$BACKUP_FILE" ]] && error "Usage: $0 <backup_file> [target_database]"
[[ -f "$BACKUP_FILE" ]] || error "Backup file not found: $BACKUP_FILE"
: "${DB_PASSWORD:?DB_PASSWORD environment variable required}"
# Determine if compressed
if [[ "$BACKUP_FILE" == *.gz ]]; then
DECOMPRESS="zcat"
else
DECOMPRESS="cat"
fi
log "Restoring database from: $BACKUP_FILE"
# Restore database
if [[ -n "$TARGET_DB" ]]; then
log "Restoring to database: $TARGET_DB"
$DECOMPRESS "$BACKUP_FILE" | \
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$TARGET_DB"
else
# Extract database name from filename
db_name=$(basename "$BACKUP_FILE" | sed 's/_[0-9].*//')
log "Restoring to database: $db_name"
$DECOMPRESS "$BACKUP_FILE" | \
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD"
fi
log "Restore complete"

#!/usr/bin/env bash
# verify_backup.sh - Verify backup integrity
set -euo pipefail
BACKUP_FILE="${1:-}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; exit 1; }
[[ -z "$BACKUP_FILE" ]] && error "Usage: $0 <backup_file>"
[[ -f "$BACKUP_FILE" ]] || error "File not found: $BACKUP_FILE"
log "Verifying backup: $BACKUP_FILE"
# Check file exists and is readable
if [[ -r "$BACKUP_FILE" ]]; then
log "✓ File is readable"
else
error "✗ File is not readable"
fi
# Check file is not empty
if [[ -s "$BACKUP_FILE" ]]; then
log "✓ File is not empty"
log " Size: $(du -h "$BACKUP_FILE" | cut -f1)"
else
error "✗ File is empty"
fi
# Verify based on extension
case "$BACKUP_FILE" in
*.tar.gz|*.tgz)
log "Verifying tar.gz integrity..."
if tar -tzf "$BACKUP_FILE" &>/dev/null; then
log "✓ tar.gz integrity OK"
else
error "✗ tar.gz integrity check failed"
fi
;;
*.sql.gz)
log "Verifying SQL backup..."
if zcat "$BACKUP_FILE" | head -1 | grep -q "MySQL\|MariaDB"; then
log "✓ SQL backup appears valid"
fi
;;
*.gpg)
log "✓ GPG encrypted backup - cannot verify without key"
;;
esac
log "Verification complete"

#!/usr/bin/env bash
# backup_rotation.sh - Manage backup rotation
set -euo pipefail
BACKUP_DIR="${BACKUP_DIR:-/backups}"
DAILY_COUNT="${DAILY_COUNT:-7}"
WEEKLY_COUNT="${WEEKLY_COUNT:-4}"
MONTHLY_COUNT="${MONTHLY_COUNT:-12}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
rotate_backups() {
local pattern="$1"
local keep_count="$2"
log "Rotating backups matching: $pattern (keep $keep_count)"
# List backups sorted by date
local backups
backups=($(ls -1t "$BACKUP_DIR"/$pattern 2>/dev/null))
# Keep only the specified number
if [[ ${#backups[@]} -gt $keep_count ]]; then
for ((i=keep_count; i<${#backups[@]}; i++)); do
log "Removing old backup: ${backups[$i]}"
rm -f "${backups[$i]}"
done
fi
log "Rotation complete"
}
main() {
log "Starting backup rotation..."
# Daily backups (keep 7)
rotate_backups "*daily*" $DAILY_COUNT
# Weekly backups (keep 4)
rotate_backups "*weekly*" $WEEKLY_COUNT
# Monthly backups (keep 12)
rotate_backups "*monthly*" $MONTHLY_COUNT
log "Backup rotation complete"
}
main

In this chapter, you learned:

  • ✅ MySQL/MariaDB backup scripts
  • ✅ PostgreSQL backup scripts
  • ✅ Incremental backup with rsync
  • ✅ Encrypted backups
  • ✅ Database restore scripts
  • ✅ Backup verification
  • ✅ Backup rotation management
  • ✅ Automated backup scheduling

Continue to the next chapter to learn about Log Analysis Scripts.


Previous Chapter: System Administration Scripts Next Chapter: Log Analysis Scripts