diff --git a/START_LVM_MIGRATION.sh b/START_LVM_MIGRATION.sh index aa1bf9b..7314817 100755 --- a/START_LVM_MIGRATION.sh +++ b/START_LVM_MIGRATION.sh @@ -15,13 +15,13 @@ NC='\033[0m' # No Color SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TOOLS_DIR="$SCRIPT_DIR/lvm-migration-tools" -echo -e "${GREEN}=== LVM Migration System (Updated) ===${NC}" -echo "Welcome to the LVM Migration Tool with Enhanced Drive Selection!" +echo -e "${GREEN}=== Complete LVM Migration System ===${NC}" +echo "πŸš€ One-Button Migration with Automatic GRUB Repair!" echo echo "This USB stick contains:" echo "β€’ Debian live system (bootable)" -echo "β€’ Complete LVM migration toolkit" -echo "β€’ Enhanced drive selection with user confirmation" +echo "β€’ Complete LVM migration toolkit with GRUB repair" +echo "β€’ Interactive drive selection with user confirmation" echo "β€’ All necessary scripts and documentation" echo @@ -39,23 +39,25 @@ ls -la *.sh | grep -E "(prepare|migrate|validate|emergency|check)" | while read done echo -echo -e "${YELLOW}Migration Process:${NC}" +echo -e "${YELLOW}🎯 One-Button Migration Process:${NC}" echo "0. Check packages -> ./check_packages.sh (verify package availability)" echo "1. Emergency install -> ./emergency_install.sh (if packages missing)" echo "2. Prepare live system -> ./prepare_live_system.sh" -echo "3. Run migration -> ./migrate_to_lvm.sh (now with drive selection!)" +echo "3. πŸš€ Complete Migration -> ./migrate_to_lvm.sh (EVERYTHING in one script!)" echo "4. Validate migration -> ./validate_lvm_migration.sh" echo -echo -e "${GREEN}NEW FEATURES:${NC}" -echo "β€’ Interactive drive selection with confirmation" -echo "β€’ Better auto-detection of internal/external drives" -echo "β€’ Safety checks to prevent wrong drive selection" -echo "β€’ Package compatibility checking" +echo -e "${GREEN}✨ INTEGRATED FEATURES:${NC}" +echo "β€’ βœ… Interactive drive selection with safety confirmations" +echo "β€’ βœ… Automatic LVM layout creation and data migration" +echo "β€’ βœ… Built-in GRUB repair (no more boot reset loops!)" +echo "β€’ βœ… Complete bootloader installation and configuration" +echo "β€’ βœ… LVM snapshot backup system setup" +echo "β€’ βœ… Everything automated in one script!" echo echo "For complete documentation: less LIVE_USB_MIGRATION_GUIDE.md" echo -read -p "What would you like to do? [0=Check, 1=Emergency, 2=Prepare, 3=Migrate, 4=Validate, d=Docs, s=Shell]: " choice +read -p "What would you like to do? [0=Check, 1=Emergency, 2=Prepare, 3=πŸš€Complete Migration, 4=Validate, d=Docs, s=Shell]: " choice case $choice in 0) @@ -71,7 +73,14 @@ case $choice in sudo ./prepare_live_system.sh ;; 3) - echo -e "${BLUE}Starting LVM migration with drive selection...${NC}" + echo -e "${GREEN}πŸš€ Starting Complete One-Button Migration...${NC}" + echo "This will:" + echo " βœ… Migrate your system to LVM" + echo " βœ… Install and repair GRUB bootloader" + echo " βœ… Configure boot system" + echo " βœ… Set up snapshot backup system" + echo " βœ… Everything automated!" + echo sudo ./migrate_to_lvm.sh ;; 4) @@ -92,6 +101,6 @@ case $choice in echo "sudo ./check_packages.sh # Check package availability" echo "sudo ./emergency_install.sh # If you see missing package errors" echo "sudo ./prepare_live_system.sh" - echo "sudo ./migrate_to_lvm.sh # Now with interactive drive selection!" + echo "sudo ./migrate_to_lvm.sh # πŸš€ Complete one-button migration!" ;; esac diff --git a/migrate_to_lvm.sh b/migrate_to_lvm.sh new file mode 100755 index 0000000..f45129e --- /dev/null +++ b/migrate_to_lvm.sh @@ -0,0 +1,969 @@ +#!/bin/bash + +# Migration Script: Non-LVM to LVM System Migration +# This script migrates a running system to an external M.2 SSD with LVM support +# MUST BE RUN FROM A LIVE USB SYSTEM - NOT FROM THE SYSTEM BEING MIGRATED + +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 (will be auto-detected) +INTERNAL_DRIVE="" +EXTERNAL_DRIVE="" +VG_NAME="system-vg" +ROOT_LV="root" +HOME_LV="home" +SWAP_LV="swap" +BOOT_LV="boot" + +# Size configurations (will be calculated based on source sizes) +ROOT_SIZE="" +HOME_SIZE="" +SWAP_SIZE="" +BOOT_SIZE="" + +# Detected partitions and filesystems +declare -A INTERNAL_PARTITIONS +declare -A PARTITION_MOUNTS +declare -A PARTITION_FILESYSTEMS +declare -A PARTITION_SIZES + +# Mount points +WORK_DIR="/mnt/migration" +INTERNAL_ROOT_MOUNT="$WORK_DIR/internal_root" +INTERNAL_HOME_MOUNT="$WORK_DIR/internal_home" +INTERNAL_BOOT_MOUNT="$WORK_DIR/internal_boot" +EXTERNAL_ROOT_MOUNT="$WORK_DIR/external_root" +EXTERNAL_HOME_MOUNT="$WORK_DIR/external_home" +EXTERNAL_BOOT_MOUNT="$WORK_DIR/external_boot" + +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.*MIGRATION_TOOLS"; then + log "Excluding live USB drive: $drive" + continue + fi + drives+=("$drive") + done + + if [ ${#drives[@]} -lt 2 ]; then + error "Need at least 2 drives for migration. 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 migration:" + 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" + done + + echo + echo "Please identify your drives:" + echo "- Internal drive: Usually NVMe (like nvme0n1) or first SATA (like sda)" + echo "- External M.2: Usually USB-connected, larger capacity" + echo + + # 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 wiped!):" + 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 + + 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" + + # Additional safety check - confirm the target drive + echo + echo -e "${RED}⚠️ FINAL SAFETY CHECK ⚠️${NC}" + echo "You have selected to COMPLETELY WIPE this drive:" + echo " Device: $EXTERNAL_DRIVE" + echo " Size: $(lsblk -dpno SIZE "$EXTERNAL_DRIVE")" + echo " Model: $(lsblk -dpno MODEL "$EXTERNAL_DRIVE")" + echo + echo "Current partitions on target drive:" + lsblk "$EXTERNAL_DRIVE" || true + echo + echo -e "${RED}This will DESTROY ALL DATA on $EXTERNAL_DRIVE!${NC}" + echo + read -p "Type 'YES' to confirm you want to wipe $EXTERNAL_DRIVE: " confirmation + if [ "$confirmation" != "YES" ]; then + error "Migration cancelled by user for safety" + fi +} + +analyze_internal_system() { + log "Analyzing internal system layout..." + + # Get all partitions on internal drive + local partitions=($(lsblk -pno NAME "$INTERNAL_DRIVE" | grep -v "^$INTERNAL_DRIVE$")) + + echo "Found partitions on $INTERNAL_DRIVE:" + for part in "${partitions[@]}"; do + local size=$(lsblk -no SIZE "$part") + local fstype=$(lsblk -no FSTYPE "$part") + local mountpoint=$(lsblk -no MOUNTPOINT "$part") + local label=$(lsblk -no LABEL "$part") + + echo " $part: $size, $fstype, mounted at: ${mountpoint:-'not mounted'}, label: ${label:-'no label'}" + + # Store partition information + PARTITION_FILESYSTEMS["$part"]="$fstype" + PARTITION_SIZES["$part"]="$size" + + # Try to identify partition purpose + if [[ "$fstype" == "vfat" ]] && [[ "$part" == *"1" ]]; then + INTERNAL_PARTITIONS["efi"]="$part" + BOOT_SIZE="1G" # Default EFI size + elif [[ "$mountpoint" == "/" ]] || [[ "$label" == "root"* ]]; then + INTERNAL_PARTITIONS["root"]="$part" + # Parse size more carefully, handle G/M/K suffixes + local size_num=$(echo "$size" | sed 's/[^0-9.]//g') + if [[ "$size" == *"G"* ]]; then + ROOT_SIZE="$(echo "$size_num + 10" | bc)G" # Add some extra space + elif [[ "$size" == *"M"* ]]; then + ROOT_SIZE="$(echo "scale=1; $size_num / 1024 + 1" | bc)G" + else + ROOT_SIZE="${size_num}G" + fi + elif [[ "$mountpoint" == "/home" ]] || [[ "$label" == "home"* ]]; then + INTERNAL_PARTITIONS["home"]="$part" + # Parse size more carefully + local size_num=$(echo "$size" | sed 's/[^0-9.]//g') + if [[ "$size" == *"G"* ]]; then + HOME_SIZE="$(echo "$size_num + 5" | bc)G" # Add some extra space + elif [[ "$size" == *"M"* ]]; then + HOME_SIZE="$(echo "scale=1; $size_num / 1024 + 1" | bc)G" + else + HOME_SIZE="${size_num}G" + fi + elif [[ "$mountpoint" == "/boot" ]] || [[ "$label" == "boot"* ]]; then + INTERNAL_PARTITIONS["boot"]="$part" + BOOT_SIZE="2G" # Standard boot size + elif [[ "$fstype" == "swap" ]]; then + INTERNAL_PARTITIONS["swap"]="$part" + local size_num=$(echo "$size" | sed 's/[^0-9.]//g') + if [[ "$size" == *"G"* ]]; then + SWAP_SIZE="${size_num}G" + elif [[ "$size" == *"M"* ]]; then + SWAP_SIZE="$(echo "scale=1; $size_num / 1024" | bc)G" + else + SWAP_SIZE="${size_num}G" + fi + elif [[ "$fstype" == "crypto_LUKS" ]]; then + log "Found encrypted partition: $part" + # This might be encrypted home or root + fi + done + + # If we didn't find a separate home partition, it's probably in root + if [ -z "${INTERNAL_PARTITIONS[home]}" ]; then + log "No separate /home partition found - assuming /home is in root partition" + # Increase root size to accommodate home + if [ -n "$ROOT_SIZE" ]; then + local root_num=$(echo "$ROOT_SIZE" | sed 's/G//') + ROOT_SIZE="$((root_num + 50))G" # Add extra space + fi + HOME_SIZE="50G" # Create separate home in LVM + fi + + # Set default swap size if not found + if [ -z "$SWAP_SIZE" ]; then + local mem_gb=$(free -g | awk '/^Mem:/ {print $2}') + SWAP_SIZE="$((mem_gb + 2))G" + log "No swap partition found, creating ${SWAP_SIZE} swap based on system memory" + fi + + # Set sensible defaults if sizes are missing + [ -z "$ROOT_SIZE" ] && ROOT_SIZE="70G" + [ -z "$HOME_SIZE" ] && HOME_SIZE="50G" + [ -z "$BOOT_SIZE" ] && BOOT_SIZE="2G" + [ -z "$SWAP_SIZE" ] && SWAP_SIZE="8G" + + # Ensure sizes are properly formatted (remove any extra decimals) + ROOT_SIZE=$(echo "$ROOT_SIZE" | sed 's/\.[0-9]*G/G/') + HOME_SIZE=$(echo "$HOME_SIZE" | sed 's/\.[0-9]*G/G/') + BOOT_SIZE=$(echo "$BOOT_SIZE" | sed 's/\.[0-9]*G/G/') + SWAP_SIZE=$(echo "$SWAP_SIZE" | sed 's/\.[0-9]*G/G/') + + echo + echo "System analysis summary:" + echo " EFI partition: ${INTERNAL_PARTITIONS[efi]:-'not found'}" + echo " Root partition: ${INTERNAL_PARTITIONS[root]:-'not found'}" + echo " Home partition: ${INTERNAL_PARTITIONS[home]:-'integrated in root'}" + echo " Boot partition: ${INTERNAL_PARTITIONS[boot]:-'integrated in root/efi'}" + echo " Swap partition: ${INTERNAL_PARTITIONS[swap]:-'not found'}" + + success "System 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. Please ensure you're running from live USB!" + confirm_action "Continue anyway? (Not recommended for production systems)" + fi + + # Check if tools are available and install if missing + local missing_tools=() + for tool in lvm cryptsetup rsync parted pv grub-install mkfs.ext4 mkfs.fat bc wipefs; 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 "Attempting to install missing tools automatically..." + + # Try to run preparation script if it exists + local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + if [ -f "$script_dir/prepare_live_system.sh" ]; then + log "Running prepare_live_system.sh to install missing tools..." + bash "$script_dir/prepare_live_system.sh" || { + error "Failed to prepare live system. Please run: sudo ./prepare_live_system.sh" + } + else + # Fallback: try to install directly + log "Attempting direct package installation..." + if command -v apt >/dev/null 2>&1; then + apt update && apt install -y lvm2 cryptsetup rsync parted pv grub-efi-amd64 grub-common e2fsprogs dosfstools bc util-linux initramfs-tools || { + error "Failed to install required packages. Please install manually: lvm2 cryptsetup rsync parted pv grub-efi-amd64 grub-common e2fsprogs dosfstools bc" + } + else + error "Cannot install packages automatically. Please install missing tools: ${missing_tools[*]}" + fi + fi + + # Re-check after installation attempt + missing_tools=() + for tool in lvm cryptsetup rsync parted pv grub-install mkfs.ext4 mkfs.fat bc wipefs; do + if ! command -v $tool >/dev/null 2>&1; then + missing_tools+=("$tool") + fi + done + + if [ ${#missing_tools[@]} -gt 0 ]; then + error "Still missing required tools after installation attempt: ${missing_tools[*]}" + fi + fi + + success "Prerequisites check passed" +} + +setup_work_directories() { + log "Setting up work directories..." + mkdir -p "$WORK_DIR"/{internal_root,internal_home,internal_boot,external_root,external_home,external_boot} + success "Work directories created" +} + +backup_existing_external_data() { + log "Backing up any existing data on external drive..." + + # Check if external LVM volumes are already mounted + if [ -d "/dev/external-vg" ]; then + warning "Found existing external-vg volume group" + confirm_action "This will DESTROY all data on the external M.2 drive!" + + # Deactivate existing LVM volumes + vgchange -an external-vg || true + vgremove -f external-vg || true + pvremove -f ${EXTERNAL_DRIVE}2 || true + fi + + success "External drive prepared for migration" +} + +create_lvm_layout() { + log "Creating new LVM layout on external drive..." + + # Debug: Show current size configuration + log "Size configuration:" + log " ROOT_SIZE: $ROOT_SIZE" + log " HOME_SIZE: $HOME_SIZE" + log " SWAP_SIZE: $SWAP_SIZE" + log " BOOT_SIZE: $BOOT_SIZE" + + # Wipe the drive and create new partition table + log "Wiping external drive: $EXTERNAL_DRIVE" + wipefs -a "$EXTERNAL_DRIVE" || warning "Failed to wipe drive signatures" + + log "Creating GPT partition table" + parted -s "$EXTERNAL_DRIVE" mklabel gpt || error "Failed to create partition table" + + # Create EFI boot partition (512MB) + log "Creating EFI boot partition" + parted -s "$EXTERNAL_DRIVE" mkpart primary fat32 1MiB 513MiB || error "Failed to create EFI partition" + parted -s "$EXTERNAL_DRIVE" set 1 boot on || warning "Failed to set boot flag" + parted -s "$EXTERNAL_DRIVE" set 1 esp on || warning "Failed to set ESP flag" + + # Create LVM partition (rest of disk) + log "Creating LVM partition" + parted -s "$EXTERNAL_DRIVE" mkpart primary 513MiB 100% || error "Failed to create LVM partition" + parted -s "$EXTERNAL_DRIVE" set 2 lvm on || warning "Failed to set LVM flag" + + # Wait for partition table to be re-read + log "Waiting for partition table update..." + sleep 3 + partprobe "$EXTERNAL_DRIVE" || warning "Failed to update partition table" + sleep 2 + + # Show partition layout for debugging + log "New partition layout:" + parted "$EXTERNAL_DRIVE" print || warning "Failed to display partition table" + + # Verify partitions exist + if [ ! -b "${EXTERNAL_DRIVE}1" ]; then + error "EFI partition ${EXTERNAL_DRIVE}1 not found after creation" + fi + if [ ! -b "${EXTERNAL_DRIVE}2" ]; then + error "LVM partition ${EXTERNAL_DRIVE}2 not found after creation" + fi + + # Create filesystems + log "Creating FAT32 filesystem on EFI partition" + mkfs.fat -F32 "${EXTERNAL_DRIVE}1" || error "Failed to create EFI filesystem" + + # Setup LVM + log "Creating LVM physical volume on ${EXTERNAL_DRIVE}2" + pvcreate "${EXTERNAL_DRIVE}2" || error "Failed to create physical volume" + + log "Creating volume group: $VG_NAME" + vgcreate "$VG_NAME" "${EXTERNAL_DRIVE}2" || error "Failed to create volume group" + + # Validate sizes before creating logical volumes + log "Validating LVM sizes..." + + # Convert sizes to MB for calculation + local root_mb=$(echo "$ROOT_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + local home_mb=$(echo "$HOME_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + local swap_mb=$(echo "$SWAP_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + local boot_mb=$(echo "$BOOT_SIZE" | sed 's/G$//' | awk '{print $1 * 1024}') + + local total_mb=$((root_mb + home_mb + swap_mb + boot_mb)) + log "Total space required: ${total_mb}MB" + + # Get available space in VG + local vg_free_mb=$(vgs --noheadings -o vg_free --units m "$VG_NAME" | tr -d ' m') + local vg_free_mb_int=${vg_free_mb%.*} + log "Available space in VG: ${vg_free_mb_int}MB" + + if [ "$total_mb" -gt "$vg_free_mb_int" ]; then + warning "Total required space ($total_mb MB) exceeds available space ($vg_free_mb_int MB)" + log "Reducing sizes proportionally..." + + # Reduce all sizes by 10% to leave space for snapshots + ROOT_SIZE=$((root_mb * 85 / 100 / 1024))"G" + HOME_SIZE=$((home_mb * 85 / 100 / 1024))"G" + SWAP_SIZE=$((swap_mb * 85 / 100 / 1024))"G" + BOOT_SIZE=$((boot_mb * 85 / 100 / 1024))"G" + + log "Adjusted sizes:" + log " ROOT_SIZE: $ROOT_SIZE" + log " HOME_SIZE: $HOME_SIZE" + log " SWAP_SIZE: $SWAP_SIZE" + log " BOOT_SIZE: $BOOT_SIZE" + fi + + # Create logical volumes with space for snapshots + log "Creating logical volume: root ($ROOT_SIZE)" + lvcreate -L "$ROOT_SIZE" -n "$ROOT_LV" "$VG_NAME" || error "Failed to create root LV" + + log "Creating logical volume: home ($HOME_SIZE)" + lvcreate -L "$HOME_SIZE" -n "$HOME_LV" "$VG_NAME" || error "Failed to create home LV" + + log "Creating logical volume: swap ($SWAP_SIZE)" + lvcreate -L "$SWAP_SIZE" -n "$SWAP_LV" "$VG_NAME" || error "Failed to create swap LV" + + log "Creating logical volume: boot ($BOOT_SIZE)" + lvcreate -L "$BOOT_SIZE" -n "$BOOT_LV" "$VG_NAME" || error "Failed to create boot LV" + + # Show LVM layout + log "LVM layout created:" + lvs "$VG_NAME" || warning "Failed to display LV layout" + + # Create filesystems on LVM volumes + log "Creating ext4 filesystem on root LV" + mkfs.ext4 -L "root" "/dev/$VG_NAME/$ROOT_LV" || error "Failed to create root filesystem" + + log "Creating ext4 filesystem on home LV" + mkfs.ext4 -L "home" "/dev/$VG_NAME/$HOME_LV" || error "Failed to create home filesystem" + + log "Creating ext4 filesystem on boot LV" + mkfs.ext4 -L "boot" "/dev/$VG_NAME/$BOOT_LV" || error "Failed to create boot filesystem" + + log "Creating swap on swap LV" + mkswap -L "swap" "/dev/$VG_NAME/$SWAP_LV" || error "Failed to create swap" + + success "LVM layout created successfully" +} + +handle_encrypted_partitions() { + log "Handling encrypted partitions..." + + local found_encrypted=false + + # Check each partition for encryption + for part_name in "${!INTERNAL_PARTITIONS[@]}"; do + local part_device="${INTERNAL_PARTITIONS[$part_name]}" + local fstype="${PARTITION_FILESYSTEMS[$part_device]}" + + if [[ "$fstype" == "crypto_LUKS" ]]; then + found_encrypted=true + log "Found encrypted partition: $part_device ($part_name)" + + local crypt_name="internal_${part_name}_luks" + + if ! cryptsetup status "$crypt_name" >/dev/null 2>&1; then + echo "Please enter the password for encrypted $part_name partition ($part_device):" + if cryptsetup open "$part_device" "$crypt_name"; then + success "Unlocked $part_device as /dev/mapper/$crypt_name" + # Update the partition reference to the decrypted device + INTERNAL_PARTITIONS["$part_name"]="/dev/mapper/$crypt_name" + # Update filesystem type of decrypted partition + local decrypted_fs=$(lsblk -no FSTYPE "/dev/mapper/$crypt_name") + PARTITION_FILESYSTEMS["/dev/mapper/$crypt_name"]="$decrypted_fs" + else + error "Failed to unlock encrypted partition $part_device" + fi + else + log "Encrypted partition $part_device already unlocked" + INTERNAL_PARTITIONS["$part_name"]="/dev/mapper/$crypt_name" + fi + fi + done + + if [ "$found_encrypted" = false ]; then + log "No encrypted partitions found" + fi + + success "Encrypted partition handling completed" +} + +mount_filesystems() { + log "Mounting filesystems..." + + # Mount internal filesystems + for part_name in "${!INTERNAL_PARTITIONS[@]}"; do + local part_device="${INTERNAL_PARTITIONS[$part_name]}" + local mount_point="$WORK_DIR/internal_$part_name" + + if [ ! -d "$mount_point" ]; then + mkdir -p "$mount_point" + fi + + log "Mounting $part_device to $mount_point" + if mount "$part_device" "$mount_point"; then + success "Mounted $part_name partition" + PARTITION_MOUNTS["$part_name"]="$mount_point" + else + error "Failed to mount $part_device" + fi + done + + # Mount external LVM filesystems + mount "/dev/$VG_NAME/$ROOT_LV" "$EXTERNAL_ROOT_MOUNT" + mount "/dev/$VG_NAME/$HOME_LV" "$EXTERNAL_HOME_MOUNT" + mount "/dev/$VG_NAME/$BOOT_LV" "$EXTERNAL_BOOT_MOUNT" + + # Mount EFI partition + mkdir -p "$EXTERNAL_ROOT_MOUNT/boot/efi" + mount "${EXTERNAL_DRIVE}1" "$EXTERNAL_ROOT_MOUNT/boot/efi" + + success "All filesystems mounted" +} + +copy_system_data() { + log "Copying system data (this will take a while)..." + + # Copy root filesystem + if [ -n "${INTERNAL_PARTITIONS[root]}" ]; then + log "Copying root filesystem..." + local source_mount="${PARTITION_MOUNTS[root]}" + + # If no separate home partition exists, exclude /home during root copy + local exclude_opts="" + if [ -z "${INTERNAL_PARTITIONS[home]}" ]; then + exclude_opts="--exclude=/home/*" + fi + + rsync -avxHAX --progress $exclude_opts \ + --exclude=/proc/* --exclude=/sys/* --exclude=/dev/* \ + --exclude=/run/* --exclude=/tmp/* --exclude=/var/tmp/* \ + "$source_mount/" "$EXTERNAL_ROOT_MOUNT/" + + success "Root filesystem copied" + else + error "No root partition found to copy" + fi + + # Copy home filesystem (if separate partition exists) + if [ -n "${INTERNAL_PARTITIONS[home]}" ]; then + log "Copying home filesystem from separate partition..." + local source_mount="${PARTITION_MOUNTS[home]}" + rsync -avxHAX --progress "$source_mount/" "$EXTERNAL_HOME_MOUNT/" + success "Home filesystem copied from separate partition" + else + log "Copying /home from root filesystem..." + # Create home directory structure on external home partition + if [ -d "${PARTITION_MOUNTS[root]}/home" ]; then + rsync -avxHAX --progress "${PARTITION_MOUNTS[root]}/home/" "$EXTERNAL_HOME_MOUNT/" + success "/home copied from root filesystem" + else + warning "No /home directory found in root filesystem" + fi + fi + + # Copy boot files + if [ -n "${INTERNAL_PARTITIONS[boot]}" ]; then + log "Copying boot files from separate boot partition..." + local source_mount="${PARTITION_MOUNTS[boot]}" + rsync -avxHAX --progress "$source_mount/" "$EXTERNAL_BOOT_MOUNT/" + else + log "Copying boot files from root filesystem..." + if [ -d "${PARTITION_MOUNTS[root]}/boot" ]; then + rsync -avxHAX --progress "${PARTITION_MOUNTS[root]}/boot/" "$EXTERNAL_BOOT_MOUNT/" + else + warning "No /boot directory found" + fi + fi + + success "System data copied successfully" +} + +update_system_configuration() { + log "Updating system configuration..." + + # Update fstab + local root_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$ROOT_LV") + local home_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$HOME_LV") + local boot_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$BOOT_LV") + local efi_uuid=$(blkid -s UUID -o value "${EXTERNAL_DRIVE}1") + local swap_uuid=$(blkid -s UUID -o value "/dev/$VG_NAME/$SWAP_LV") + + cat > "$EXTERNAL_ROOT_MOUNT/etc/fstab" << EOF +# /etc/fstab: static file system information. +# +# Use 'blkid' to print the universally unique identifier for a device; this may +# be used with UUID= as a more robust way to name devices that works even if +# disks are added and removed. See fstab(5). +# +# +UUID=$root_uuid / ext4 defaults 0 1 +UUID=$efi_uuid /boot/efi vfat defaults 0 2 +UUID=$boot_uuid /boot ext4 defaults 0 2 +UUID=$home_uuid /home ext4 defaults 0 2 +UUID=$swap_uuid none swap sw 0 0 +tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0 +EOF + + # Update crypttab (remove old encrypted home entry since we're using LVM now) + echo "# No encrypted partitions in LVM setup" > "$EXTERNAL_ROOT_MOUNT/etc/crypttab" + + success "System configuration updated" +} + +install_bootloader() { + log "Installing bootloader..." + + # Bind mount necessary filesystems for chroot + mount --bind /dev "$EXTERNAL_ROOT_MOUNT/dev" + mount --bind /proc "$EXTERNAL_ROOT_MOUNT/proc" + mount --bind /sys "$EXTERNAL_ROOT_MOUNT/sys" + mount --bind /run "$EXTERNAL_ROOT_MOUNT/run" + + # Update initramfs to include LVM support + chroot "$EXTERNAL_ROOT_MOUNT" /bin/bash -c " + echo 'GRUB_ENABLE_CRYPTODISK=y' >> /etc/default/grub + update-initramfs -u -k all + grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck + update-grub + " + + # Unmount bind mounts + umount "$EXTERNAL_ROOT_MOUNT/dev" + umount "$EXTERNAL_ROOT_MOUNT/proc" + umount "$EXTERNAL_ROOT_MOUNT/sys" + umount "$EXTERNAL_ROOT_MOUNT/run" + + success "Bootloader installed successfully" +} + +create_lvm_snapshot_script() { + log "Creating LVM snapshot backup script..." + + cat > "$EXTERNAL_ROOT_MOUNT/usr/local/bin/lvm-snapshot-backup.sh" << 'EOF' +#!/bin/bash + +# LVM Snapshot Backup Script +# Creates snapshots of LVM volumes for backup purposes + +set -e + +VG_NAME="system-vg" +SNAPSHOT_SIZE="10G" +BACKUP_DIR="/mnt/backup" + +create_snapshots() { + echo "Creating LVM snapshots..." + + # Create snapshots + lvcreate -L "$SNAPSHOT_SIZE" -s -n root-snapshot "/dev/$VG_NAME/root" + lvcreate -L "$SNAPSHOT_SIZE" -s -n home-snapshot "/dev/$VG_NAME/home" + + echo "Snapshots created successfully" + echo "root-snapshot: /dev/$VG_NAME/root-snapshot" + echo "home-snapshot: /dev/$VG_NAME/home-snapshot" +} + +mount_snapshots() { + echo "Mounting snapshots..." + mkdir -p "$BACKUP_DIR"/{root,home} + mount "/dev/$VG_NAME/root-snapshot" "$BACKUP_DIR/root" + mount "/dev/$VG_NAME/home-snapshot" "$BACKUP_DIR/home" + echo "Snapshots mounted at $BACKUP_DIR" +} + +remove_snapshots() { + echo "Cleaning up snapshots..." + umount "$BACKUP_DIR/root" 2>/dev/null || true + umount "$BACKUP_DIR/home" 2>/dev/null || true + lvremove -f "/dev/$VG_NAME/root-snapshot" 2>/dev/null || true + lvremove -f "/dev/$VG_NAME/home-snapshot" 2>/dev/null || true + echo "Snapshots removed" +} + +case "$1" in + create) + create_snapshots + ;; + mount) + mount_snapshots + ;; + remove) + remove_snapshots + ;; + backup) + create_snapshots + mount_snapshots + echo "Snapshots ready for backup at $BACKUP_DIR" + echo "Run 'lvm-snapshot-backup.sh remove' when backup is complete" + ;; + *) + echo "Usage: $0 {create|mount|remove|backup}" + echo " create - Create snapshots only" + echo " mount - Mount existing snapshots" + echo " remove - Remove snapshots and unmount" + echo " backup - Create and mount snapshots ready for backup" + exit 1 + ;; +esac +EOF + + chmod +x "$EXTERNAL_ROOT_MOUNT/usr/local/bin/lvm-snapshot-backup.sh" + + success "LVM snapshot backup script created" +} + +repair_grub_bootloader() { + log "Performing final GRUB repair to ensure bootability..." + + # This is based on the working simple_grub_repair.sh + local temp_boot="/tmp/grub-repair-boot" + local temp_efi="/tmp/grub-repair-efi" + + mkdir -p "$temp_boot" "$temp_efi" + + log "Mounting boot partitions for GRUB repair..." + mount "/dev/$VG_NAME/boot" "$temp_boot" || { + warning "Could not mount boot partition for GRUB repair" + return 0 + } + + mount "${EXTERNAL_DRIVE}1" "$temp_efi" || { + warning "Could not mount EFI partition for GRUB repair" + umount "$temp_boot" 2>/dev/null || true + return 0 + } + + log "Installing GRUB bootloader directly to external drive..." + if grub-install --target=x86_64-efi \ + --efi-directory="$temp_efi" \ + --bootloader-id=debian \ + --boot-directory="$temp_boot" \ + --recheck \ + "$EXTERNAL_DRIVE"; then + success "GRUB bootloader installed successfully" + + # Verify installation + if [ -f "$temp_efi/EFI/debian/grubx64.efi" ]; then + success "GRUB EFI bootloader verified" + else + warning "GRUB EFI bootloader not found after installation" + fi + else + warning "GRUB installation encountered issues" + fi + + # Cleanup + umount "$temp_boot" 2>/dev/null || true + umount "$temp_efi" 2>/dev/null || true + rmdir "$temp_boot" "$temp_efi" 2>/dev/null || true + + success "GRUB repair completed" +} + +cleanup() { + log "Cleaning up..." + + # Unmount external filesystems + umount "$EXTERNAL_ROOT_MOUNT/boot/efi" 2>/dev/null || true + umount "$EXTERNAL_ROOT_MOUNT" 2>/dev/null || true + umount "$EXTERNAL_HOME_MOUNT" 2>/dev/null || true + umount "$EXTERNAL_BOOT_MOUNT" 2>/dev/null || true + + # Unmount internal filesystems + for part_name in "${!PARTITION_MOUNTS[@]}"; do + local mount_point="${PARTITION_MOUNTS[$part_name]}" + umount "$mount_point" 2>/dev/null || true + done + + # Close encrypted partitions + for part_name in "${!INTERNAL_PARTITIONS[@]}"; do + local part_device="${INTERNAL_PARTITIONS[$part_name]}" + if [[ "$part_device" == *"/dev/mapper/"* ]]; then + local crypt_name=$(basename "$part_device") + cryptsetup close "$crypt_name" 2>/dev/null || true + fi + done + + success "Cleanup completed" +} + +main() { + echo -e "${GREEN}=== LVM Migration Script ===${NC}" + echo "This script will migrate your non-LVM system to LVM on an external M.2 drive" + echo "Run this from a live USB system for best results" + echo + + check_prerequisites + detect_drives + analyze_internal_system + + echo + echo "Migration Summary:" + echo " Source: $INTERNAL_DRIVE (non-LVM system)" + echo " Target: $EXTERNAL_DRIVE (will become LVM system)" + echo " Root size: $ROOT_SIZE" + echo " Home size: $HOME_SIZE" + echo " Swap size: $SWAP_SIZE" + echo " Boot size: $BOOT_SIZE" + echo + + confirm_action "WARNING: This will DESTROY all data on $EXTERNAL_DRIVE!" + + setup_work_directories + backup_existing_external_data + create_lvm_layout + handle_encrypted_partitions + mount_filesystems + copy_system_data + update_system_configuration + install_bootloader + create_lvm_snapshot_script + repair_grub_bootloader + cleanup + + success "Complete LVM migration with GRUB repair finished successfully!" + echo + echo -e "${GREEN}=== MIGRATION COMPLETE ===${NC}" + echo "βœ… System migrated to LVM on external M.2 drive" + echo "βœ… GRUB bootloader installed and repaired" + echo "βœ… Boot configuration updated" + echo "βœ… LVM snapshot backup system ready" + echo + echo -e "${BLUE}Next steps:${NC}" + echo "1. Reboot your system" + echo "2. Enter BIOS/UEFI settings and configure:" + echo " β€’ Set external M.2 as first boot device" + echo " β€’ Disable Secure Boot (if enabled)" + echo " β€’ Ensure UEFI mode is enabled" + echo "3. Look for 'debian' entry in boot menu" + echo "4. Boot from external drive - should work without reset loops!" + echo + echo -e "${GREEN}Your new LVM system features:${NC}" + echo "β€’ Instant snapshots: sudo /usr/local/bin/lvm-snapshot-backup.sh backup" + echo "β€’ Dynamic volume resizing" + echo "β€’ Advanced backup strategies with rollback" + echo "β€’ Original internal drive unchanged as fallback" + echo + echo -e "${YELLOW}πŸŽ‰ One-button migration completed successfully!${NC}" + echo "Everything is configured and ready to boot!" +} + +# Trap to ensure cleanup on exit +trap cleanup EXIT + +main "$@" \ No newline at end of file