Skip to content

Instantly share code, notes, and snippets.

@meminens
Created January 26, 2025 18:26
Show Gist options
  • Save meminens/f9a226a9031e3c082e7c6fec9eebb126 to your computer and use it in GitHub Desktop.
Save meminens/f9a226a9031e3c082e7c6fec9eebb126 to your computer and use it in GitHub Desktop.
Arch Linux Backup Script
#!/bin/bash
#===============================================================================
# SYSTEM BACKUP SCRIPT
# This script performs a system backup, including:
# - Backing up directories, specific files, and package lists.
# - Creating an encrypted archive of the backup.
# - Uploading the backup to cloud storage using rclone.
# - Cleaning up old backups.
#
# Configuration and setup:
# - Modify the settings below to configure backup directories, cloud storage, and other parameters. Look for "<..>".
# - Make sure the required dependencies are installed: rsync, rclone, gpg, tar, pacman.
#===============================================================================
set -e
trap 'handle_error "Unexpected script failure"' ERR
# CONFIGURATION SETTINGS
# Set the root directory for backups
BACKUP_ROOT="<path_to_backup_dir>" # Example: /home/user/backups
# Log file path
LOG_FILE="<path_to_log_file>" # Example: /var/log/backup.log
# Maximum size for log file (in bytes)
LOG_MAX_SIZE=$((10 * 1024 * 1024)) # 10 MB maximum log size
# Number of backups to keep before removing old ones
KEEP_BACKUPS=3 # Example: Keep last 3 backups
# Rclone remote name for cloud uploads
RCLONE_REMOTE="<remote_name>" # Example: myremote
# GPG passphrase for encrypting backups
PASSWORD="<gpg_passphrase>"
# SOURCE DIRECTORIES AND FILES TO BACK UP
declare -A SOURCE_DIRS=(
["/etc"]="" # No exclusions by default
["/home"]="" # No exclusions by default
["/var/lib/pacman/local"]="" # No exclusions by default
["/boot"]="" # No exclusions by default
)
# Specific files to back up
SPECIFIC_FILES=(
"/home/<user>/.bashrc"
"/home/<user>/.bash_history"
"/home/<user>/.dircolors"
)
# LOGGING FUNCTION
log_message() {
if [[ -f "$LOG_FILE" && $(stat -c %s "$LOG_FILE") -gt $LOG_MAX_SIZE ]]; then
mv "$LOG_FILE" "${LOG_FILE}.old"
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') [$2] $1" >> "$LOG_FILE"
}
# ERROR HANDLER
handle_error() {
log_message "$1" "ERROR"
[[ -n "$backup_dir" ]] && rm -rf "$backup_dir"
[[ -n "$archive_name" && -f "$BACKUP_ROOT/$archive_name" ]] && rm -f "$BACKUP_ROOT/$archive_name"
exit 1
}
# CHECK DEPENDENCIES
check_dependencies() {
for cmd in rsync rclone gpg tar pacman; do
command -v $cmd &>/dev/null || handle_error "$cmd is not installed or not in PATH"
done
}
# CREATE BACKUP DIRECTORY
create_backup_dir() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_dir="$BACKUP_ROOT/$timestamp"
mkdir -p "$backup_dir" || handle_error "Failed to create backup directory"
echo "$backup_dir"
}
# BACK UP DIRECTORIES
backup_directories() {
local backup_dir="$1"
for src_dir in "${!SOURCE_DIRS[@]}"; do
[[ -d "$src_dir" ]] || { log_message "WARNING: Source directory $src_dir not found" "WARNING"; continue; }
log_message "Backing up $src_dir" "INFO"
local exclude_opts=""
if [[ -n "${SOURCE_DIRS[$src_dir]}" ]]; then
IFS=',' read -ra excludes <<< "${SOURCE_DIRS[$src_dir]}"
for item in "${excludes[@]}"; do
exclude_opts+="--exclude=${item} "
done
fi
local rel_path=${src_dir#/}
local target_dir="$backup_dir/$rel_path"
mkdir -p "$(dirname "$target_dir")"
rsync -aAXH --info=progress2 $exclude_opts \
--exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found","*.socket","S.*"} \
"$src_dir/" "$target_dir/" || handle_error "Failed to backup $src_dir"
done
}
# BACK UP SPECIFIC FILES
backup_specific_files() {
local backup_dir="$1"
for file in "${SPECIFIC_FILES[@]}"; do
[[ -f "$file" ]] || { log_message "WARNING: Specific file $file not found" "WARNING"; continue; }
log_message "Backing up $file" "INFO"
local rel_path=${file#/}
local target_dir="$backup_dir/$(dirname "$rel_path")"
mkdir -p "$target_dir"
cp --preserve=all "$file" "$target_dir/" || handle_error "Failed to backup $file"
done
}
# BACK UP PACKAGE LISTS
backup_packages() {
local backup_dir="$1"
log_message "Backing up package information" "INFO"
pacman -Qqen > "$backup_dir/pkglist.txt" && pacman -Qqem > "$backup_dir/foreignpkglist.txt" || handle_error "Failed to backup package lists"
}
# CREATE BACKUP SUMMARY
create_backup_summary() {
local backup_dir="$1"
{
echo "Backup completed at $(date)"
echo "System: $(uname -a)"
echo "Disk usage:"
df -h
} > "$backup_dir/BACKUP_INFO" || handle_error "Failed to create backup summary"
}
# CLEAN UP OLD BACKUPS
cleanup_old_backups() {
log_message "Cleaning up old backups" "INFO"
readarray -t dirs < <(find "$BACKUP_ROOT" -maxdepth 1 -type d -name "2*" | sort -r)
readarray -t archives < <(find "$BACKUP_ROOT" -maxdepth 1 -type f -name "*.tar.gz" | sort -r)
for ((i=KEEP_BACKUPS; i<${#dirs[@]}; i++)); do
rm -rf "${dirs[i]}"
log_message "Removed old backup directory: $(basename "${dirs[i]}")" "INFO"
done
for ((i=KEEP_BACKUPS; i<${#archives[@]}; i++)); do
rm -f "${archives[i]}"
log_message "Removed old archive: $(basename "${archives[i]}")" "INFO"
done
}
# CREATE ENCRYPTED ARCHIVE
create_archive() {
local backup_dir="$1"
local archive_name=$(basename "$backup_dir").tar.gz
local encrypted_archive="$BACKUP_ROOT/$archive_name"
log_message "Creating password-protected archive of backup directory" "INFO"
tar --exclude="*.socket" --exclude="S.*" -czf - -C "$BACKUP_ROOT" "$(basename "$backup_dir")" | \
gpg --pinentry-mode=loopback --passphrase "$PASSWORD" --symmetric --cipher-algo AES256 -o "$encrypted_archive" || handle_error "Failed to create and encrypt archive"
echo "$archive_name"
}
# UPLOAD TO CLOUD
upload_to_cloud() {
local archive_name="$1"
log_message "Uploading backup to cloud storage" "INFO"
sudo -u <user> rclone --verbose --config "<path_to_rclone_config_dir/>rclone.conf" copyto "$BACKUP_ROOT/$archive_name" "$RCLONE_REMOTE:Arch_Backup.tar.gz" --progress || handle_error "Failed to upload backup to cloud"
}
# MAIN FUNCTION
main() {
[[ $EUID -ne 0 ]] && handle_error "Please run as root"
check_dependencies
log_message "Starting system backup" "INFO"
local backup_dir
backup_dir=$(create_backup_dir)
backup_directories "$backup_dir"
backup_packages "$backup_dir"
backup_specific_files "$backup_dir"
create_backup_summary "$backup_dir"
local archive_name
archive_name=$(create_archive "$backup_dir")
cleanup_old_backups
upload_to_cloud "$archive_name"
log_message "Backup completed successfully" "INFO"
}
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment