- GUI and CLI backup/restore functionality - Auto-detection of internal system drive - Smart drive classification (internal vs external) - Reboot integration for clean backups/restores - Portable tools that survive cloning operations - Tool preservation system for external M.2 SSD - Complete disaster recovery workflow - Safety features and multiple confirmations - Desktop integration and launcher scripts - Comprehensive documentation
339 lines
9.5 KiB
Bash
Executable File
339 lines
9.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# System Backup Script - Command Line Version
|
|
# For use with cron jobs or manual execution
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
SOURCE_DRIVE="" # Will be auto-detected
|
|
TARGET_DRIVE="" # Will be detected or specified
|
|
RESTORE_MODE=false # Restore mode flag
|
|
LOG_FILE="/var/log/system_backup.log"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Logging function
|
|
log() {
|
|
local message="$1"
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
# Log to console
|
|
echo "${timestamp} - ${message}"
|
|
|
|
# Log to file if writable
|
|
if [[ -w "$LOG_FILE" ]] || [[ -w "$(dirname "$LOG_FILE")" ]]; then
|
|
echo "${timestamp} - ${message}" >> "$LOG_FILE" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
# Error handling
|
|
error_exit() {
|
|
log "${RED}ERROR: $1${NC}"
|
|
exit 1
|
|
}
|
|
|
|
# Success message
|
|
success() {
|
|
log "${GREEN}SUCCESS: $1${NC}"
|
|
}
|
|
|
|
# Warning message
|
|
warning() {
|
|
log "${YELLOW}WARNING: $1${NC}"
|
|
}
|
|
|
|
# Check if running as root
|
|
check_root() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
error_exit "This script must be run as root (use sudo)"
|
|
fi
|
|
}
|
|
|
|
# Detect root filesystem drive
|
|
detect_root_drive() {
|
|
log "Detecting root filesystem drive..."
|
|
|
|
# Find the device containing the root filesystem
|
|
local root_device=$(df / | tail -1 | awk '{print $1}')
|
|
|
|
# Remove partition number to get base device
|
|
local base_device=$(echo "$root_device" | sed 's/[0-9]*$//')
|
|
|
|
# Handle nvme drives (e.g., /dev/nvme0n1p1 -> /dev/nvme0n1)
|
|
base_device=$(echo "$base_device" | sed 's/p$//')
|
|
|
|
echo "$base_device"
|
|
}
|
|
|
|
# Detect external drives
|
|
detect_external_drives() {
|
|
log "Detecting external drives..."
|
|
|
|
# Get all block devices
|
|
lsblk -d -n -o NAME,SIZE,TYPE,TRAN | while read -r line; do
|
|
if [[ $line == *"disk"* ]] && [[ $line == *"usb"* ]]; then
|
|
drive_name=$(echo "$line" | awk '{print $1}')
|
|
drive_size=$(echo "$line" | awk '{print $2}')
|
|
echo "/dev/$drive_name ($drive_size)"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Validate drives
|
|
validate_drives() {
|
|
if [[ ! -b "$SOURCE_DRIVE" ]]; then
|
|
error_exit "Source drive $SOURCE_DRIVE does not exist or is not a block device"
|
|
fi
|
|
|
|
if [[ ! -b "$TARGET_DRIVE" ]]; then
|
|
error_exit "Target drive $TARGET_DRIVE does not exist or is not a block device"
|
|
fi
|
|
|
|
if [[ "$SOURCE_DRIVE" == "$TARGET_DRIVE" ]]; then
|
|
error_exit "Source and target drives cannot be the same"
|
|
fi
|
|
|
|
# Check if target drive is mounted
|
|
if mount | grep -q "$TARGET_DRIVE"; then
|
|
warning "Target drive $TARGET_DRIVE is currently mounted. Unmounting..."
|
|
umount "$TARGET_DRIVE"* 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
# Get drive size
|
|
get_drive_size() {
|
|
local drive=$1
|
|
blockdev --getsize64 "$drive"
|
|
}
|
|
|
|
# Clone drive
|
|
clone_drive() {
|
|
local source=$1
|
|
local target=$2
|
|
|
|
log "Starting drive clone operation..."
|
|
log "Source: $source"
|
|
log "Target: $target"
|
|
|
|
# Get sizes
|
|
source_size=$(get_drive_size "$source")
|
|
target_size=$(get_drive_size "$target")
|
|
|
|
log "Source size: $(numfmt --to=iec-i --suffix=B $source_size)"
|
|
log "Target size: $(numfmt --to=iec-i --suffix=B $target_size)"
|
|
|
|
if [[ $target_size -lt $source_size ]]; then
|
|
error_exit "Target drive is smaller than source drive"
|
|
fi
|
|
|
|
# Start cloning
|
|
log "Starting clone operation with dd..."
|
|
|
|
if command -v pv >/dev/null 2>&1; then
|
|
# Use pv for progress if available
|
|
dd if="$source" bs=4M | pv -s "$source_size" | dd of="$target" bs=4M conv=fdatasync
|
|
else
|
|
# Use dd with status=progress
|
|
dd if="$source" of="$target" bs=4M status=progress conv=fdatasync
|
|
fi
|
|
|
|
if [[ $? -eq 0 ]]; then
|
|
success "Drive cloning completed successfully!"
|
|
|
|
# Restore backup tools to external drive if this was a backup operation
|
|
if [[ "$RESTORE_MODE" != true ]]; then
|
|
log "Preserving backup tools on external drive..."
|
|
local restore_script="$(dirname "$0")/restore_tools_after_backup.sh"
|
|
if [[ -f "$restore_script" ]]; then
|
|
"$restore_script" "$target" || log "Warning: Could not preserve backup tools"
|
|
else
|
|
log "Warning: Tool preservation script not found"
|
|
fi
|
|
fi
|
|
|
|
# Sync to ensure all data is written
|
|
log "Syncing data to disk..."
|
|
sync
|
|
|
|
# Verify partition table
|
|
log "Verifying partition table on target drive..."
|
|
fdisk -l "$target" | head -20 | tee -a "$LOG_FILE"
|
|
|
|
success "Backup verification completed!"
|
|
else
|
|
error_exit "Drive cloning failed!"
|
|
fi
|
|
}
|
|
|
|
# Create desktop entry
|
|
create_desktop_entry() {
|
|
local desktop_file="$HOME/Desktop/System-Backup.desktop"
|
|
local script_path=$(realpath "$0")
|
|
|
|
cat > "$desktop_file" << EOF
|
|
[Desktop Entry]
|
|
Version=1.0
|
|
Type=Application
|
|
Name=System Backup
|
|
Comment=Clone internal drive to external M.2 SSD
|
|
Exec=gnome-terminal -- sudo "$script_path" --gui
|
|
Icon=drive-harddisk
|
|
Terminal=false
|
|
Categories=System;Utility;
|
|
EOF
|
|
|
|
chmod +x "$desktop_file"
|
|
log "Desktop entry created: $desktop_file"
|
|
}
|
|
|
|
# Show usage
|
|
show_usage() {
|
|
echo "Usage: $0 [OPTIONS]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " -s, --source DRIVE Source drive (auto-detected if not specified)"
|
|
echo " -t, --target DRIVE Target drive (required)"
|
|
echo " -r, --restore Restore mode (reverse source and target)"
|
|
echo " -l, --list List available drives"
|
|
echo " -d, --desktop Create desktop entry"
|
|
echo " --gui Launch GUI version"
|
|
echo " -h, --help Show this help"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 --list"
|
|
echo " $0 --source /dev/sda --target /dev/sdb"
|
|
echo " $0 --restore --source /dev/sdb --target /dev/sda"
|
|
echo " $0 --desktop"
|
|
echo " $0 --gui"
|
|
}
|
|
|
|
# Main function
|
|
main() {
|
|
# Auto-detect source drive if not specified
|
|
if [[ -z "$SOURCE_DRIVE" ]]; then
|
|
SOURCE_DRIVE=$(detect_root_drive)
|
|
log "Auto-detected root filesystem drive: $SOURCE_DRIVE"
|
|
fi
|
|
|
|
# Parse command line arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-s|--source)
|
|
SOURCE_DRIVE="$2"
|
|
shift 2
|
|
;;
|
|
-t|--target)
|
|
TARGET_DRIVE="$2"
|
|
shift 2
|
|
;;
|
|
-r|--restore)
|
|
RESTORE_MODE=true
|
|
shift
|
|
;;
|
|
-l|--list)
|
|
echo "Available drives:"
|
|
lsblk -d -o NAME,SIZE,TYPE,TRAN
|
|
echo ""
|
|
echo "External drives:"
|
|
detect_external_drives
|
|
exit 0
|
|
;;
|
|
-d|--desktop)
|
|
create_desktop_entry
|
|
exit 0
|
|
;;
|
|
--gui)
|
|
python3 "$(dirname "$0")/backup_manager.py"
|
|
exit 0
|
|
;;
|
|
-h|--help)
|
|
show_usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
error_exit "Unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# In restore mode, swap source and target for user convenience
|
|
if [[ "$RESTORE_MODE" == true ]]; then
|
|
if [[ -n "$SOURCE_DRIVE" && -n "$TARGET_DRIVE" ]]; then
|
|
log "Restore mode: swapping source and target drives"
|
|
TEMP_DRIVE="$SOURCE_DRIVE"
|
|
SOURCE_DRIVE="$TARGET_DRIVE"
|
|
TARGET_DRIVE="$TEMP_DRIVE"
|
|
fi
|
|
fi
|
|
|
|
# Check if target drive is specified
|
|
if [[ -z "$TARGET_DRIVE" ]]; then
|
|
echo "Available external drives:"
|
|
detect_external_drives
|
|
echo ""
|
|
read -p "Enter target drive (e.g., /dev/sdb): " TARGET_DRIVE
|
|
|
|
if [[ -z "$TARGET_DRIVE" ]]; then
|
|
error_exit "Target drive not specified"
|
|
fi
|
|
fi
|
|
|
|
# Check root privileges
|
|
check_root
|
|
|
|
# Validate drives
|
|
validate_drives
|
|
|
|
# Confirm operation
|
|
echo ""
|
|
if [[ "$RESTORE_MODE" == true ]]; then
|
|
echo "🚨 RESTORE CONFIGURATION 🚨"
|
|
echo "This will RESTORE (overwrite target with source data):"
|
|
else
|
|
echo "BACKUP CONFIGURATION:"
|
|
echo "This will BACKUP (copy source to target):"
|
|
fi
|
|
echo "Source: $SOURCE_DRIVE"
|
|
echo "Target: $TARGET_DRIVE"
|
|
echo ""
|
|
echo "WARNING: All data on $TARGET_DRIVE will be DESTROYED!"
|
|
|
|
if [[ "$RESTORE_MODE" == true ]]; then
|
|
echo ""
|
|
echo "⚠️ CRITICAL WARNING FOR RESTORE MODE ⚠️"
|
|
echo "You are about to OVERWRITE $TARGET_DRIVE"
|
|
echo "Make sure this is what you intend to do!"
|
|
echo ""
|
|
read -p "Type 'RESTORE' to confirm or anything else to cancel: " confirm
|
|
if [[ "$confirm" != "RESTORE" ]]; then
|
|
log "Restore operation cancelled by user"
|
|
exit 0
|
|
fi
|
|
else
|
|
read -p "Are you sure you want to continue? (yes/no): " confirm
|
|
if [[ "$confirm" != "yes" ]]; then
|
|
log "Operation cancelled by user"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# Start operation
|
|
if [[ "$RESTORE_MODE" == true ]]; then
|
|
log "Starting system restore operation..."
|
|
else
|
|
log "Starting system backup operation..."
|
|
fi
|
|
clone_drive "$SOURCE_DRIVE" "$TARGET_DRIVE"
|
|
|
|
log "System backup completed successfully!"
|
|
echo ""
|
|
echo "Backup completed! You can now safely remove the external drive."
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|