Files
backup_to_external_m.2/old_scripts/direct_clone_backup.sh
root 72f9838f55 cleanup: Archive old complex scripts and documentation
- Move all old complex backup scripts to old_scripts/
- Archive previous documentation versions
- Clean up temporary files and debian packages
- Update README to focus on new simple system
- Keep only the enhanced simple backup system in main directory

Main directory now contains only:
- simple_backup_gui.py (GUI interface)
- enhanced_simple_backup.sh (CLI interface)
- list_drives.sh (helper)
- simple_backup.sh (basic CLI)
- SIMPLE_BACKUP_README.md (detailed docs)
- README.md (project overview)
2025-10-09 00:30:03 +02:00

667 lines
24 KiB
Bash

#!/bin/bash
# Direct 1-to-1 Clone Script
# Creates an exact copy of internal drive to external drive without LVM conversion
# Preserves all partitions, LUKS encryption, and boot structures exactly as they are
# MUST BE RUN FROM A LIVE USB SYSTEM
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration variables
INTERNAL_DRIVE=""
EXTERNAL_DRIVE=""
WORK_DIR="/mnt/clone_work"
# Partition mapping
declare -A PARTITION_MAP
declare -A PARTITION_FILESYSTEMS
declare -A PARTITION_LABELS
declare -A PARTITION_UUIDS
log() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
confirm_action() {
echo -e "${YELLOW}$1${NC}"
read -p "Do you want to continue? [y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
error "Operation aborted by user"
fi
}
detect_drives() {
log "Detecting available drives..."
# Find all block devices that are disks (not partitions), excluding the live USB
local all_drives=($(lsblk -dpno NAME,TYPE,SIZE,MODEL | grep "disk" | awk '{print $1}'))
local drives=()
# Filter out the USB stick we're running from (if running from live system)
for drive in "${all_drives[@]}"; do
# Check if this drive contains the live system
if mount | grep -q "$drive" && mount | grep -q "/lib/live\|/media.*live\|overlay"; then
log "Excluding live USB drive: $drive"
continue
fi
drives+=("$drive")
done
if [ ${#drives[@]} -lt 2 ]; then
error "Need at least 2 drives for cloning. Found only ${#drives[@]} suitable drives"
echo "Available drives:"
for drive in "${all_drives[@]}"; do
local info=$(lsblk -dpno NAME,SIZE,MODEL "$drive" | awk '{print $2 " " $3}')
echo " $drive - $info"
done
exit 1
fi
echo
echo "Available drives for cloning:"
for i in "${!drives[@]}"; do
local drive="${drives[$i]}"
local info=$(lsblk -dpno NAME,SIZE,MODEL "$drive" | awk '{print $2 " " $3}' | xargs)
local is_usb=""
# Check if it's a USB drive
if udevadm info --query=property --name="$drive" 2>/dev/null | grep -q "ID_BUS=usb"; then
is_usb=" (USB)"
fi
echo "$((i+1)). $drive - $info$is_usb"
# Show partition layout
echo " Partitions:"
lsblk "$drive" | tail -n +2 | sed 's/^/ /'
echo
done
# Auto-detection with user confirmation
local suggested_internal=""
local suggested_external=""
# Try to suggest internal drive (prefer NVMe, then non-USB drives)
for drive in "${drives[@]}"; do
if [[ "$drive" == *"nvme"* ]]; then
suggested_internal="$drive"
break
fi
done
if [ -z "$suggested_internal" ]; then
# If no NVMe, prefer non-USB drives
for drive in "${drives[@]}"; do
if ! udevadm info --query=property --name="$drive" 2>/dev/null | grep -q "ID_BUS=usb"; then
suggested_internal="$drive"
break
fi
done
fi
# Try to suggest external drive (prefer USB, larger capacity)
for drive in "${drives[@]}"; do
if [ "$drive" != "$suggested_internal" ]; then
if udevadm info --query=property --name="$drive" 2>/dev/null | grep -q "ID_BUS=usb"; then
suggested_external="$drive"
break
fi
fi
done
if [ -z "$suggested_external" ]; then
# If no USB found, use the other drive
for drive in "${drives[@]}"; do
if [ "$drive" != "$suggested_internal" ]; then
suggested_external="$drive"
break
fi
done
fi
# Show suggestions and get user confirmation
if [ -n "$suggested_internal" ] && [ -n "$suggested_external" ]; then
echo "Suggested configuration:"
local internal_info=$(lsblk -dpno SIZE,MODEL "$suggested_internal" | xargs)
local external_info=$(lsblk -dpno SIZE,MODEL "$suggested_external" | xargs)
echo " Internal (source): $suggested_internal - $internal_info"
echo " External (target): $suggested_external - $external_info"
echo
read -p "Use this configuration? [Y/n]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
# Manual selection
INTERNAL_DRIVE=""
EXTERNAL_DRIVE=""
else
INTERNAL_DRIVE="$suggested_internal"
EXTERNAL_DRIVE="$suggested_external"
fi
fi
# Manual selection if auto-detection failed or user declined
if [ -z "$INTERNAL_DRIVE" ]; then
echo "Select INTERNAL drive (source - your current system):"
for i in "${!drives[@]}"; do
local drive="${drives[$i]}"
local info=$(lsblk -dpno SIZE,MODEL "$drive" | xargs)
echo "$((i+1)). $drive - $info"
done
read -p "Enter number [1-${#drives[@]}]: " choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#drives[@]}" ]; then
INTERNAL_DRIVE="${drives[$((choice-1))]}"
else
error "Invalid selection"
fi
fi
if [ -z "$EXTERNAL_DRIVE" ]; then
echo
echo "Select EXTERNAL drive (target - will be completely overwritten!):"
for i in "${!drives[@]}"; do
local drive="${drives[$i]}"
if [ "$drive" != "$INTERNAL_DRIVE" ]; then
local info=$(lsblk -dpno SIZE,MODEL "$drive" | xargs)
echo "$((i+1)). $drive - $info"
fi
done
read -p "Enter number [1-${#drives[@]}]: " choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#drives[@]}" ]; then
local selected_drive="${drives[$((choice-1))]}"
if [ "$selected_drive" != "$INTERNAL_DRIVE" ]; then
EXTERNAL_DRIVE="$selected_drive"
else
error "Cannot use the same drive as both source and target!"
fi
else
error "Invalid selection"
fi
fi
# Final validation
if [ "$INTERNAL_DRIVE" = "$EXTERNAL_DRIVE" ]; then
error "Internal and external drives cannot be the same!"
fi
# Check drive sizes
local internal_size_bytes=$(lsblk -bno SIZE "$INTERNAL_DRIVE")
local external_size_bytes=$(lsblk -bno SIZE "$EXTERNAL_DRIVE")
if [ "$external_size_bytes" -lt "$internal_size_bytes" ]; then
error "External drive ($EXTERNAL_DRIVE) is smaller than internal drive ($INTERNAL_DRIVE). Cannot clone."
fi
echo
echo "Final drive selection:"
echo " Internal (source): $INTERNAL_DRIVE ($(lsblk -dpno SIZE,MODEL "$INTERNAL_DRIVE" | xargs))"
echo " External (target): $EXTERNAL_DRIVE ($(lsblk -dpno SIZE,MODEL "$EXTERNAL_DRIVE" | xargs))"
success "Drive detection completed"
# Final safety check
echo
echo -e "${RED}⚠️ FINAL SAFETY CHECK ⚠️${NC}"
echo "You are about to COMPLETELY CLONE this drive:"
echo " Source: $INTERNAL_DRIVE"
echo " Target: $EXTERNAL_DRIVE (will be completely overwritten!)"
echo
echo "Current partitions on target drive that will be DESTROYED:"
lsblk "$EXTERNAL_DRIVE" || true
echo
echo -e "${RED}This will DESTROY ALL DATA on $EXTERNAL_DRIVE!${NC}"
echo "The entire drive will be overwritten with an exact copy of $INTERNAL_DRIVE"
echo
read -p "Type 'CLONE' to confirm you want to overwrite $EXTERNAL_DRIVE: " confirmation
if [ "$confirmation" != "CLONE" ]; then
error "Clone operation cancelled by user for safety"
fi
}
analyze_source_drive() {
log "Analyzing source drive structure..."
# Get partition information
local partitions=($(lsblk -pno NAME "$INTERNAL_DRIVE" | grep -v "^$INTERNAL_DRIVE$"))
echo "Source drive structure:"
lsblk "$INTERNAL_DRIVE"
echo
for part in "${partitions[@]}"; do
local size=$(lsblk -no SIZE "$part")
local fstype=$(lsblk -no FSTYPE "$part")
local label=$(lsblk -no LABEL "$part")
local uuid=$(lsblk -no UUID "$part")
local mountpoint=$(lsblk -no MOUNTPOINT "$part")
echo " $part:"
echo " Size: $size"
echo " Filesystem: ${fstype:-'unknown'}"
echo " Label: ${label:-'none'}"
echo " UUID: ${uuid:-'none'}"
echo " Mounted at: ${mountpoint:-'not mounted'}"
# Store information for later use
PARTITION_FILESYSTEMS["$part"]="$fstype"
PARTITION_LABELS["$part"]="$label"
PARTITION_UUIDS["$part"]="$uuid"
# Check if it's encrypted
if [[ "$fstype" == "crypto_LUKS" ]]; then
log "Found LUKS encrypted partition: $part"
fi
echo
done
success "Source drive analysis completed"
}
check_prerequisites() {
log "Checking prerequisites..."
# Check if running from live system
local root_device=$(df / | tail -1 | awk '{print $1}')
if [[ "$root_device" == *"loop"* ]] || [[ "$root_device" == *"overlay"* ]] || [[ "$root_device" == *"tmpfs"* ]]; then
success "Running from live system - good!"
else
warning "This might not be a live system. For safety, this should be run from a live USB!"
confirm_action "Continue anyway? (Not recommended)"
fi
# Check for required tools
local missing_tools=()
for tool in dd pv rsync cryptsetup grub-install lsblk blkid partprobe; do
if ! command -v $tool >/dev/null 2>&1; then
missing_tools+=("$tool")
fi
done
if [ ${#missing_tools[@]} -gt 0 ]; then
warning "Missing required tools: ${missing_tools[*]}"
log "Installing missing tools..."
apt update && apt install -y util-linux pv rsync cryptsetup grub-common grub-efi-amd64 || {
error "Failed to install required tools. Please install manually: ${missing_tools[*]}"
}
fi
success "Prerequisites check passed"
}
perform_direct_clone() {
log "Starting direct drive clone..."
log "This will create an exact bit-for-bit copy of $INTERNAL_DRIVE to $EXTERNAL_DRIVE"
# Get drive size for progress tracking
local total_size_bytes=$(lsblk -bno SIZE "$INTERNAL_DRIVE")
local total_size_human=$(lsblk -no SIZE "$INTERNAL_DRIVE")
log "Cloning $total_size_human from $INTERNAL_DRIVE to $EXTERNAL_DRIVE"
log "This will take a while depending on drive size and speed..."
# Use dd with progress monitoring via pv
if dd if="$INTERNAL_DRIVE" bs=64M status=none | pv -s "$total_size_bytes" -w 80 | dd of="$EXTERNAL_DRIVE" bs=64M status=none; then
success "Drive clone completed successfully"
else
error "Drive clone failed!"
fi
# Force kernel to re-read partition table
log "Updating partition table on cloned drive..."
partprobe "$EXTERNAL_DRIVE" || warning "Failed to update partition table"
sync
sleep 3
success "Direct clone operation completed"
}
fix_uuids_and_boot() {
log "Fixing UUIDs and boot configuration on cloned drive..."
mkdir -p "$WORK_DIR"
# Get new partition list from cloned drive
local new_partitions=($(lsblk -pno NAME "$EXTERNAL_DRIVE" | grep -v "^$EXTERNAL_DRIVE$"))
# Create new UUIDs for all partitions to avoid conflicts
for part in "${new_partitions[@]}"; do
local fstype=$(lsblk -no FSTYPE "$part")
local old_uuid=$(lsblk -no UUID "$part")
log "Processing partition $part (filesystem: ${fstype:-'unknown'})"
# Skip encrypted partitions - they'll keep their UUID
if [[ "$fstype" == "crypto_LUKS" ]]; then
log "Skipping LUKS partition UUID change - encryption handles this"
continue
fi
# Skip swap partitions for now - we'll handle them separately
if [[ "$fstype" == "swap" ]]; then
log "Regenerating swap UUID for $part"
swapoff "$part" 2>/dev/null || true
mkswap -U random "$part" || warning "Failed to regenerate swap UUID"
continue
fi
# Generate new UUID for filesystem partitions
if [[ -n "$fstype" && "$fstype" != "crypto_LUKS" ]]; then
case "$fstype" in
"ext2"|"ext3"|"ext4")
log "Generating new UUID for ext filesystem on $part"
tune2fs -U random "$part" || warning "Failed to change UUID for $part"
;;
"vfat")
log "Generating new UUID for FAT filesystem on $part"
# For FAT32, we'll use mlabel (part of mtools) if available, or skip
if command -v mlabel >/dev/null 2>&1; then
# Generate a random 8-character hex string for FAT32
local new_fat_uuid=$(openssl rand -hex 4 | tr '[:lower:]' '[:upper:]')
echo "mtools_skip_check=1" > ~/.mtoolsrc
mlabel -i "$part" -N "${new_fat_uuid:0:8}" || warning "Failed to change FAT UUID"
rm -f ~/.mtoolsrc
else
warning "Cannot change FAT UUID - mtools not available"
fi
;;
*)
log "Skipping UUID change for unknown filesystem type: $fstype"
;;
esac
fi
done
# Now we need to update /etc/fstab on the cloned system
log "Mounting cloned system to update configuration..."
# Find the root partition (usually the largest ext4 partition or check for typical structure)
local root_partition=""
local boot_partition=""
local efi_partition=""
for part in "${new_partitions[@]}"; do
local fstype=$(lsblk -no FSTYPE "$part")
local size_bytes=$(lsblk -bno SIZE "$part")
# Detect EFI partition (usually small FAT32)
if [[ "$fstype" == "vfat" && "$size_bytes" -lt 1073741824 ]]; then # Less than 1GB
efi_partition="$part"
log "Detected EFI partition: $part"
fi
# Detect boot partition (usually ext4, smaller than root)
if [[ "$fstype" == "ext4" && "$size_bytes" -lt 5368709120 && "$size_bytes" -gt 104857600 ]]; then # Between 100MB and 5GB
boot_partition="$part"
log "Detected boot partition: $part"
fi
# Detect root partition (usually largest ext4 or check for encrypted)
if [[ "$fstype" == "ext4" && "$size_bytes" -gt 5368709120 ]]; then # Larger than 5GB
root_partition="$part"
log "Detected root partition: $part"
fi
# Handle LUKS encrypted partitions
if [[ "$fstype" == "crypto_LUKS" ]]; then
log "Found encrypted partition that might be root: $part"
# We'll try to unlock it if needed
local crypt_name="cloned_root_$(basename "$part")"
echo "This appears to be an encrypted partition. Please enter the password to mount and update configuration:"
if cryptsetup open "$part" "$crypt_name"; then
# Check if the decrypted partition is the root
local decrypted_fs=$(lsblk -no FSTYPE "/dev/mapper/$crypt_name")
if [[ "$decrypted_fs" == "ext4" ]]; then
root_partition="/dev/mapper/$crypt_name"
log "Using decrypted partition as root: $root_partition"
fi
else
warning "Could not unlock encrypted partition. Configuration update may be incomplete."
continue
fi
fi
done
if [ -z "$root_partition" ]; then
warning "Could not automatically detect root partition. Manual configuration may be needed."
return 0
fi
# Mount the cloned root filesystem
log "Mounting cloned root filesystem: $root_partition"
mount "$root_partition" "$WORK_DIR" || {
error "Failed to mount cloned root filesystem"
}
# Mount boot partition if exists
if [ -n "$boot_partition" ]; then
log "Mounting boot partition: $boot_partition"
mkdir -p "$WORK_DIR/boot"
mount "$boot_partition" "$WORK_DIR/boot" || warning "Failed to mount boot partition"
fi
# Mount EFI partition if exists
if [ -n "$efi_partition" ]; then
log "Mounting EFI partition: $efi_partition"
mkdir -p "$WORK_DIR/boot/efi"
mount "$efi_partition" "$WORK_DIR/boot/efi" || warning "Failed to mount EFI partition"
fi
# Update /etc/fstab with new UUIDs
if [ -f "$WORK_DIR/etc/fstab" ]; then
log "Updating /etc/fstab with new UUIDs..."
cp "$WORK_DIR/etc/fstab" "$WORK_DIR/etc/fstab.backup"
# Generate new fstab with current UUIDs
echo "# /etc/fstab: static file system information." > "$WORK_DIR/etc/fstab.new"
echo "# Updated after cloning $(date)" >> "$WORK_DIR/etc/fstab.new"
echo "#" >> "$WORK_DIR/etc/fstab.new"
echo "# <file system> <mount point> <type> <options> <dump> <pass>" >> "$WORK_DIR/etc/fstab.new"
# Add entries for each partition with current UUIDs
for part in "${new_partitions[@]}"; do
local current_uuid=$(lsblk -no UUID "$part")
local fstype=$(lsblk -no FSTYPE "$part")
if [ -n "$current_uuid" ]; then
case "$part" in
*"1")
if [[ "$fstype" == "vfat" ]]; then
echo "UUID=$current_uuid /boot/efi vfat defaults 0 2" >> "$WORK_DIR/etc/fstab.new"
fi
;;
*"2")
if [[ "$fstype" == "ext4" ]]; then
# Could be boot or root - determine by size
local size_bytes=$(lsblk -bno SIZE "$part")
if [ "$size_bytes" -lt 5368709120 ]; then # Less than 5GB = boot
echo "UUID=$current_uuid /boot ext4 defaults 0 2" >> "$WORK_DIR/etc/fstab.new"
else # Root partition
echo "UUID=$current_uuid / ext4 defaults 0 1" >> "$WORK_DIR/etc/fstab.new"
fi
fi
;;
*"3")
if [[ "$fstype" == "ext4" ]]; then
echo "UUID=$current_uuid / ext4 defaults 0 1" >> "$WORK_DIR/etc/fstab.new"
elif [[ "$fstype" == "swap" ]]; then
echo "UUID=$current_uuid none swap sw 0 0" >> "$WORK_DIR/etc/fstab.new"
fi
;;
*)
if [[ "$fstype" == "swap" ]]; then
echo "UUID=$current_uuid none swap sw 0 0" >> "$WORK_DIR/etc/fstab.new"
fi
;;
esac
fi
done
# Add tmpfs entry
echo "tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0" >> "$WORK_DIR/etc/fstab.new"
# Replace old fstab with new one
mv "$WORK_DIR/etc/fstab.new" "$WORK_DIR/etc/fstab"
success "Updated /etc/fstab with new UUIDs"
else
warning "/etc/fstab not found in cloned system"
fi
success "UUID and boot configuration updated"
}
install_bootloader() {
log "Installing/repairing bootloader on cloned drive..."
# Bind mount necessary filesystems for chroot
mount --bind /dev "$WORK_DIR/dev"
mount --bind /proc "$WORK_DIR/proc"
mount --bind /sys "$WORK_DIR/sys"
mount --bind /run "$WORK_DIR/run"
# Update GRUB configuration and reinstall bootloader
chroot "$WORK_DIR" /bin/bash -c "
# Update initramfs to ensure all modules are included
update-initramfs -u -k all
# Reinstall GRUB bootloader
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck $EXTERNAL_DRIVE
# Update GRUB configuration
update-grub
" || warning "Some bootloader operations failed but continuing..."
# Unmount bind mounts
umount "$WORK_DIR/dev" 2>/dev/null || true
umount "$WORK_DIR/proc" 2>/dev/null || true
umount "$WORK_DIR/sys" 2>/dev/null || true
umount "$WORK_DIR/run" 2>/dev/null || true
success "Bootloader installation completed"
}
cleanup_clone() {
log "Cleaning up..."
# Unmount all filesystems
umount "$WORK_DIR/boot/efi" 2>/dev/null || true
umount "$WORK_DIR/boot" 2>/dev/null || true
umount "$WORK_DIR" 2>/dev/null || true
# Close any encrypted volumes we opened
for mapper in /dev/mapper/cloned_*; do
if [ -b "$mapper" ]; then
local crypt_name=$(basename "$mapper")
cryptsetup close "$crypt_name" 2>/dev/null || true
fi
done
# Remove work directory
rmdir "$WORK_DIR" 2>/dev/null || true
success "Cleanup completed"
}
verify_clone() {
log "Performing basic verification of cloned drive..."
# Check if partition table was copied correctly
log "Verifying partition table..."
local internal_partcount=$(lsblk -no NAME "$INTERNAL_DRIVE" | grep -c "^[├└]─")
local external_partcount=$(lsblk -no NAME "$EXTERNAL_DRIVE" | grep -c "^[├└]─")
if [ "$internal_partcount" -eq "$external_partcount" ]; then
success "Partition count matches: $internal_partcount partitions"
else
warning "Partition count mismatch: internal=$internal_partcount, external=$external_partcount"
fi
# Show final layout
echo
echo "Original drive layout:"
lsblk "$INTERNAL_DRIVE"
echo
echo "Cloned drive layout:"
lsblk "$EXTERNAL_DRIVE"
success "Basic verification completed"
}
main() {
echo -e "${GREEN}=== Direct 1-to-1 Clone Script ===${NC}"
echo "This script creates an exact copy of your internal drive to external drive"
echo "WITHOUT any LVM conversion - preserves original structure exactly"
echo "Run this from a live USB system for best results"
echo
check_prerequisites
detect_drives
analyze_source_drive
echo
echo "Clone Summary:"
echo " Source: $INTERNAL_DRIVE"
echo " Target: $EXTERNAL_DRIVE (will be completely overwritten)"
echo " Operation: Bit-perfect clone preserving all partitions and structures"
echo
confirm_action "WARNING: This will COMPLETELY OVERWRITE $EXTERNAL_DRIVE!"
perform_direct_clone
fix_uuids_and_boot
install_bootloader
verify_clone
cleanup_clone
success "Direct 1-to-1 clone completed successfully!"
echo
echo -e "${GREEN}=== CLONE COMPLETE ===${NC}"
echo "✅ Exact copy created on external drive"
echo "✅ UUIDs updated to prevent conflicts"
echo "✅ Bootloader installed and configured"
echo "✅ Original internal drive unchanged"
echo
echo -e "${BLUE}Next steps:${NC}"
echo "1. Reboot your system"
echo "2. Enter BIOS/UEFI settings and configure:"
echo " • Set external drive as first boot device"
echo " • Ensure UEFI mode is enabled (if system uses UEFI)"
echo " • Disable Secure Boot if having issues"
echo "3. Boot from external drive"
echo "4. Should ask for LUKS password (if encrypted) and boot normally"
echo
echo -e "${GREEN}Your cloned system features:${NC}"
echo "• Identical to original - same encryption, same structure"
echo "• Independent UUIDs to avoid conflicts"
echo "• Original internal drive preserved as backup"
echo "• No LVM complexity - simple and reliable"
echo
echo -e "${YELLOW}🎉 Simple 1-to-1 clone completed successfully!${NC}"
echo "The external drive should boot exactly like your original system!"
}
# Trap to ensure cleanup on exit
trap cleanup_clone EXIT
main "$@"