Backup script using rsync

Freepoorman

Active Member
Joined
Mar 8, 2024
Messages
223
Reaction score
135
Credits
1,916
Hi everyone,

I recently critically misconfigured my system while trying to implement security enhancements, and my backup/restore script let me down big time.

I have worked on this new backup script now the whole day, hoping that it is a secure alternative to the one I used before.
I have audited it and implemented as many improvements to functionality as possible while prioritizing system stability.

Please feel free to judge me as much as you want, all criticism and advice is welcome. And feel free to use this script for yourselves if you like it:


restore_backup.sh:
Bash:
#!/bin/bash

# Configuration file path
CONFIG_FILE="/home/erik/.backup_config"

# Load configuration
source "$CONFIG_FILE" || handle_error "Failed to load configuration file."

# Function to prompt user for confirmation
confirm() {
    while true; do
        read -p "$1 [Y/N]: " response
        case $response in
            [Yy]* ) return 0;;
            [Nn]* ) return 1;;
            * ) echo "Please answer Yes or No.";;
        esac
    done
}

# Function to log messages
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Function to handle errors
handle_error() {
    log_message "Error: $1"
    read -n 1 -s -r -p "Press any key to close the terminal..."
    exit 1
}

# Function to rotate log file
rotate_log() {
    local max_log_size=$((1024 * 1024 * 10))  # 10 MB
    if [ -f "$LOG_FILE" ] && [ "$(stat -c%s "$LOG_FILE")" -gt "$max_log_size" ]; then
        mv "$LOG_FILE" "${LOG_FILE}.1"
        log_message "Log file rotated."
    fi
}

# Check if the script is run as root
if [ "$EUID" -ne 0 ]; then
    handle_error "This script must be run as root. Please use sudo or run as root."
fi

# Function to verify backup integrity
verify_backup_integrity() {
    local backup_dir="$1"
    local checksum_file="${backup_dir}/checksum.md5"

    if [ ! -f "$checksum_file" ]; then
        handle_error "Checksum file not found. Backup integrity cannot be verified."
    fi

    log_message "Verifying backup integrity..."
    if md5sum -c "$checksum_file"; then
        log_message "Backup integrity verified."
    else
        handle_error "Backup integrity check failed. Please ensure the backup is intact."
    fi
}

# Function to restore snaps
restore_snaps() {
    local backup_dir="$1"
    local snaps_dir="${backup_dir}/snaps"

    if [ -d "$snaps_dir" ]; then
        log_message "Restoring snaps..."
        for snap in "$snaps_dir"/*; do
            snap_name=$(basename "$snap")
            if ! snap list | grep -q "$snap_name"; then
                log_message "Restoring snap: $snap_name"
                snap install "$snap"
            else
                log_message "Snap $snap_name is already installed. Skipping..."
            fi
        done
        log_message "Snaps restored successfully."
    else
        log_message "No snaps found to restore."
    fi
}

# Function to restore flatpaks
restore_flatpaks() {
    local backup_dir="$1"
    local flatpaks_dir="${backup_dir}/flatpaks"

    if [ -d "$flatpaks_dir" ]; then
        log_message "Restoring flatpaks..."
        for flatpak in "$flatpaks_dir"/*; do
            flatpak_name=$(basename "$flatpak")
            if ! flatpak list --app | grep -q "$flatpak_name"; then
                log_message "Restoring flatpak: $flatpak_name"
                flatpak install --user "$flatpak"
            else
                log_message "Flatpak $flatpak_name is already installed. Skipping..."
            fi
        done
        log_message "Flatpaks restored successfully."
    else
        log_message "No flatpaks found to restore."
    fi
}

# Function to perform backup
perform_backup() {
    # Rotate log file if necessary
    rotate_log

    # Check if the destination directory exists, if not, create it
    if [ ! -d "$DEST" ]; then
        mkdir -p "$DEST" || handle_error "Failed to create destination directory $DEST."
    fi

    # Calculate total size of files to be backed up
    total_size=$(du -sb --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/swapfile","/home/*"} "$SOURCE" | awk '{print $1}')

    # Perform the backup using rsync with pv for progress monitoring
    log_message "Starting backup..."
    if rsync -av --delete --ignore-missing-args --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/swapfile","/home/*"} "$SOURCE" "$DEST" | pv -s "$total_size" > /dev/null; then
        log_message "Backup completed successfully."
        # Generate checksum file for backup integrity verification
        log_message "Generating checksum file..."
        find "$DEST" -type f -exec md5sum {} + > "${DEST}/checksum.md5"
        log_message "Checksum file generated."
    else
        handle_error "Backup failed with error code $?"
    fi
}

# Function to perform restore
perform_restore() {
    # Rotate log file if necessary
    rotate_log

    # Check if the source directory exists
    if [ ! -d "$SOURCE" ]; then
        handle_error "Source directory $SOURCE not found. Please ensure the 2Tb_ext4 drive is mounted."
    fi

    # Verify backup integrity
    verify_backup_integrity "$SOURCE"

    # Prompt user to confirm restore
    if confirm "Do you want to restore a backup?"; then
        if confirm "Are you sure you want to proceed with the restore?"; then
            # Calculate total size of files to be restored
            total_size=$(du -sb "$SOURCE" | awk '{print $1}')

            # Perform the restore using rsync with pv for progress monitoring
            log_message "Starting restore..."
            if rsync -av --delete "$SOURCE" "$DEST" | pv -s "$total_size" > /dev/null; then
                log_message "Restore completed successfully."
                # Restore snaps
                restore_snaps "$SOURCE"
                # Restore flatpaks
                restore_flatpaks "$SOURCE"
            else
                handle_error "Restore failed with error code $?"
            fi
        else
            log_message "Restore operation cancelled."
            exit 0
        fi
    else
        log_message "Restore operation cancelled."
        exit 0
    fi
}

# Function to view logs
view_logs() {
    if [ -f "$LOG_FILE" ]; then
        less "$LOG_FILE"
    else
        handle_error "Log file not found."
    fi
}

# Interactive menu for backup or restore
PS3="Please select an option: "
options=("Backup" "Restore" "View Logs" "Check Backup Integrity" "Exit")
select opt in "${options[@]}"
do
    case $opt in
        "Backup")
            perform_backup
            break
            ;;
        "Restore")
            perform_restore
            break
            ;;
        "View Logs")
            view_logs
            ;;
        "Check Backup Integrity")
            verify_backup_integrity "$DEST"
            ;;
        "Exit")
            log_message "Exiting script."
            exit 0
            ;;
        *) handle_error "Invalid option. Please select a valid option.";;
    esac
done


/home/erik/.backup_config:

Bash:
# Backup Configuration
SOURCE="/"
DEST="/media/erik/2Tb_ext4/AAA_System/backup_recovery-rsync/"
LOG_FILE="/var/log/backup_script.log"


I would very much appreciate if I could get input from more experienced guys before I attempt running it..
 
Last edited:


I tried to delete the backup I created with it and I couldn't. No matter how I set permissions... I think the snaps and flatpaks are causing serious issues.


I will exclude them when I have energy again.
 

Members online


Latest posts

Top