#!/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 "# " >> "$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 "$@"