- 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)
667 lines
24 KiB
Bash
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 "$@" |