diff --git a/grub-repair.sh b/grub-repair.sh index ef92b8c..67d6aa1 100755 --- a/grub-repair.sh +++ b/grub-repair.sh @@ -21,6 +21,7 @@ EFI_MOUNT_POINT="/mnt/boot/efi" GRUB_CFG_PATH="/mnt/boot/grub/grub.cfg" BACKUP_DIR="/tmp/grub-backup-$(date +%Y%m%d-%H%M%S)" LOG_FILE=$(mktemp "/tmp/grub-repair-$(date +%Y%m%d-%H%M%S).XXXXXX.log" 2>/dev/null || echo "/tmp/grub-repair-$(date +%Y%m%d-%H%M%S).log") +EFI_PARTITION_PATH="" # Store detected EFI partition for reuse # Function to print colored output print_status() { @@ -36,6 +37,11 @@ Usage: $SCRIPT_NAME [OPTIONS] COMMAND GRUB and EFI Repair Script for Live ISO Recovery +Features: + - Automatically detects UEFI vs BIOS boot mode + - Installs appropriate GRUB target (x86_64-efi or i386-pc) + - Supports modern partition layouts (Fedora-style: /boot/efi, /boot, /) + OPTIONS: -h, --help Show this help message -v, --verbose Enable verbose output @@ -76,6 +82,51 @@ EXAMPLES: EOF } +# Function to validate partition number +validate_partition() { + local partition="$1" + + # Check if partition is numeric + if [[ ! "$partition" =~ ^[0-9]+$ ]]; then + print_status "$RED" "Error: Partition number must be a positive integer: $partition" + exit 1 + fi + + # Check if partition number is reasonable (1-128) + if [[ "$partition" -lt 1 || "$partition" -gt 128 ]]; then + print_status "$RED" "Error: Partition number must be between 1 and 128: $partition" + exit 1 + fi + + echo "$partition" +} + +# Function to sanitize user input paths +sanitize_path() { + local path="$1" + local description="$2" + + # Check if path is absolute + if [[ "$path" != /* ]]; then + print_status "$RED" "Error: $description must be an absolute path: $path" + exit 1 + fi + + # Check for dangerous path components + if [[ "$path" == *".."* ]]; then + print_status "$RED" "Error: $description contains dangerous path components: $path" + exit 1 + fi + + # Check for null bytes or other dangerous characters + if [[ "$path" == *$'\0'* ]]; then + print_status "$RED" "Error: $description contains null bytes: $path" + exit 1 + fi + + echo "$path" +} + # Function to check if running as root check_root() { if [[ $EUID -ne 0 ]]; then @@ -101,10 +152,27 @@ check_live_iso() { fi } +# Function to detect boot mode +detect_boot_mode() { + # Check if we're running in UEFI mode + if [[ -d "/sys/firmware/efi" ]]; then + echo "uefi" + return 0 + else + echo "bios" + return 0 + fi +} + # Function to detect available disks and partitions detect_systems() { print_status "$BLUE" "Detecting available disks and partitions..." + # Show boot mode + local boot_mode=$(detect_boot_mode) + echo "Boot mode: $boot_mode" + echo "" + echo "Available disks:" lsblk -d -o NAME,SIZE,TYPE,MOUNTPOINT @@ -214,7 +282,7 @@ detect_root_partition() { local full_path="/dev/$partition" if [[ -b "$full_path" ]]; then local fstype=$(echo "$blkid_output" | grep "$full_path:" | grep -o 'TYPE="[^"]*"' | cut -d'"' -f2) - if [[ "$fstype" == "ext4" || "$fstype" == "xfs" || "$fstype" == "btrfs" ]]; then + if [[ "$fstype" == "ext4" || "$fstype" == "xfs" || "$fstype" == "btrfs" || "$fstype" == "zfs" || "$fstype" == "f2fs" ]]; then root_partitions="$root_partitions $full_path" fi fi @@ -264,16 +332,16 @@ detect_root_partition() { fi # Fallback: try common partition numbers (2, 3, 4) - for part_num in 2 3 4; do - local test_partition="${disk}${part_num}" - if [[ -b "$test_partition" ]]; then - local fstype=$(echo "$blkid_output" | grep "$test_partition:" | grep -o 'TYPE="[^"]*"' | cut -d'"' -f2) - if [[ "$fstype" == "ext4" || "$fstype" == "xfs" || "$fstype" == "btrfs" ]]; then - echo "$test_partition" - return 0 + for part_num in 2 3 4; do + local test_partition="${disk}${part_num}" + if [[ -b "$test_partition" ]]; then + local fstype=$(echo "$blkid_output" | grep "$test_partition:" | grep -o 'TYPE="[^"]*"' | cut -d'"' -f2) + if [[ "$fstype" == "ext4" || "$fstype" == "xfs" || "$fstype" == "btrfs" || "$fstype" == "zfs" || "$fstype" == "f2fs" ]]; then + echo "$test_partition" + return 0 + fi fi - fi - done + done # If still no root partition found, return empty return 1 @@ -380,7 +448,7 @@ mount_system() { # (e.g., Fedora-style: EFI=1, Boot=2, Root=3), so we need to be smart # about detecting which partition is actually the root filesystem local fstype=$(blkid -s TYPE -o value "$partition_device" 2>/dev/null) - if [[ "$fstype" != "ext4" && "$fstype" != "xfs" && "$fstype" != "btrfs" ]]; then + if [[ "$fstype" != "ext4" && "$fstype" != "xfs" && "$fstype" != "btrfs" && "$fstype" != "zfs" && "$fstype" != "f2fs" ]]; then print_status "$YELLOW" "Warning: Partition $partition_device has filesystem type '$fstype', which may not be a root partition" print_status "$BLUE" "Attempting to detect the actual root partition..." @@ -440,6 +508,18 @@ mount_system() { exit 1 fi + # Mount /dev/shm for shared memory access + if ! mount --bind /dev/shm "$MOUNT_POINT/dev/shm"; then + print_status "$YELLOW" "Warning: Failed to mount /dev/shm, continuing without it" + # Note: /dev/shm is optional but recommended for full chroot functionality + fi + + # Mount /run for runtime data + if ! mount --bind /run "$MOUNT_POINT/run"; then + print_status "$YELLOW" "Warning: Failed to mount /run, continuing without it" + # Note: /run is optional but recommended for full chroot functionality + fi + # Mount EFI partition if it exists local efi_partition=$(detect_efi_partition "$device") if [[ -n "$efi_partition" && -b "$efi_partition" ]]; then @@ -456,6 +536,8 @@ mount_system() { umount "$MOUNT_POINT" 2>/dev/null || true exit 1 fi + # Store EFI partition path for potential reuse by other functions + EFI_PARTITION_PATH="$efi_partition" else print_status "$YELLOW" "Warning: EFI partition not found, some operations may fail" fi @@ -527,6 +609,14 @@ unmount_system() { print_status "$GREEN" "Boot partition unmounted" fi + if mountpoint -q "$MOUNT_POINT/run"; then + umount "$MOUNT_POINT/run" + fi + + if mountpoint -q "$MOUNT_POINT/dev/shm"; then + umount "$MOUNT_POINT/dev/shm" + fi + if mountpoint -q "$MOUNT_POINT/dev/pts"; then umount "$MOUNT_POINT/dev/pts" fi @@ -581,15 +671,98 @@ install_grub() { exit 1 fi + # Detect boot mode + local boot_mode=$(detect_boot_mode) + print_status "$BLUE" "Detected boot mode: $boot_mode" + + # Detect distribution for appropriate bootloader ID + local bootloader_id="grub" + local bootloader_label="GRUB" + if [[ -d "$MOUNT_POINT/etc" ]]; then + local detected_distro=$(detect_distribution "$MOUNT_POINT") + case "$detected_distro" in + *ubuntu*) + bootloader_id="ubuntu" + bootloader_label="Ubuntu" + ;; + *fedora*|*redhat*|*centos*) + bootloader_id="fedora" + bootloader_label="Fedora/RHEL" + ;; + *arch*) + bootloader_id="arch" + bootloader_label="Arch Linux" + ;; + *debian*) + bootloader_id="debian" + bootloader_label="Debian" + ;; + *) + # Use generic GRUB ID for other distributions + bootloader_id="grub" + bootloader_label="GRUB" + ;; + esac + print_status "$BLUE" "Detected distribution: $detected_distro, using bootloader ID: $bootloader_id" + fi + # Chroot into the system print_status "$BLUE" "Chrooting into system to install GRUB..." - # Install GRUB to the mounted system - if chroot "$MOUNT_POINT" grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=grub --recheck; then - print_status "$GREEN" "GRUB installed successfully" + # Install GRUB based on boot mode + if [[ "$boot_mode" == "uefi" ]]; then + # Check if EFI partition is mounted + if [[ ! -d "$EFI_MOUNT_POINT" ]] || ! mountpoint -q "$EFI_MOUNT_POINT"; then + print_status "$RED" "Error: EFI partition not mounted. Required for UEFI GRUB installation." + exit 1 + fi + + print_status "$BLUE" "Installing GRUB for UEFI mode with bootloader ID: $bootloader_id" + if chroot "$MOUNT_POINT" grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id="$bootloader_id" --recheck 2>&1; then + print_status "$GREEN" "GRUB installed successfully for UEFI mode" + else + print_status "$RED" "Error: UEFI GRUB installation failed" + print_status "$YELLOW" "Run 'chroot $MOUNT_POINT grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=$bootloader_id --recheck' manually for detailed error output" + exit 1 + fi else - print_status "$RED" "Error: GRUB installation failed" - exit 1 + # BIOS mode - install to MBR + print_status "$BLUE" "Installing GRUB for BIOS mode..." + + # Get the disk device (remove partition number) + local disk_device="" + if command -v lsblk >/dev/null 2>&1; then + # Find the root partition and get its parent disk + local root_partition=$(findmnt -n -o SOURCE "$MOUNT_POINT" 2>/dev/null | head -1) + if [[ -n "$root_partition" ]]; then + disk_device=$(lsblk -rno PKNAME "$root_partition" 2>/dev/null | head -1) + if [[ -n "$disk_device" ]]; then + disk_device="/dev/$disk_device" + fi + fi + fi + + # Fallback: extract disk from partition path string + if [[ -z "$disk_device" ]]; then + local root_partition=$(findmnt -n -o SOURCE "$MOUNT_POINT" 2>/dev/null | head -1) + if [[ -n "$root_partition" ]]; then + disk_device="${root_partition%[0-9]*}" + fi + fi + + if [[ -z "$disk_device" ]]; then + print_status "$RED" "Error: Could not determine disk device for BIOS GRUB installation" + exit 1 + fi + + print_status "$BLUE" "Installing GRUB to disk: $disk_device" + if chroot "$MOUNT_POINT" grub-install --target=i386-pc --recheck "$disk_device" 2>&1; then + print_status "$GREEN" "GRUB installed successfully for BIOS mode" + else + print_status "$RED" "Error: BIOS GRUB installation failed" + print_status "$YELLOW" "Run 'chroot $MOUNT_POINT grub-install --target=i386-pc --recheck $disk_device' manually for detailed error output" + exit 1 + fi fi } @@ -605,10 +778,11 @@ update_grub() { fi # Update GRUB configuration - if chroot "$MOUNT_POINT" grub-mkconfig -o /boot/grub/grub.cfg; then + if chroot "$MOUNT_POINT" grub-mkconfig -o /boot/grub/grub.cfg 2>&1; then print_status "$GREEN" "GRUB configuration updated successfully" else print_status "$RED" "Error: GRUB configuration update failed" + print_status "$YELLOW" "Run 'chroot $MOUNT_POINT grub-mkconfig -o /boot/grub/grub.cfg' manually for detailed error output" exit 1 fi } @@ -616,6 +790,7 @@ update_grub() { # Function to check EFI partition check_efi() { local device="$1" + local temp_mount="" # Initialize temp_mount variable print_status "$BLUE" "Checking EFI partition..." @@ -624,26 +799,49 @@ check_efi() { exit 1 fi - # Detect EFI partition - local efi_partition=$(detect_efi_partition "$device") - if [[ -z "$efi_partition" ]]; then - print_status "$RED" "Error: No EFI partition found on device $device" - exit 1 + # Check boot mode first + local boot_mode=$(detect_boot_mode) + if [[ "$boot_mode" == "bios" ]]; then + print_status "$YELLOW" "System is running in BIOS mode - EFI operations not applicable" + print_status "$BLUE" "Skipping EFI partition check for BIOS system" + return 0 fi - # Mount EFI partition temporarily - local temp_mount="/tmp/efi-temp" - mkdir -p "$temp_mount" - mount "$efi_partition" "$temp_mount" - - # Check EFI contents - print_status "$BLUE" "EFI partition contents:" - ls -la "$temp_mount" - - # Check for existing boot entries - if [[ -d "$temp_mount/EFI" ]]; then - print_status "$BLUE" "EFI directory contents:" - find "$temp_mount/EFI" -type f -name "*.efi" 2>/dev/null || echo "No EFI files found" + # Check if EFI partition is already mounted (from mount_system) + if [[ -d "$EFI_MOUNT_POINT" ]] && mountpoint -q "$EFI_MOUNT_POINT"; then + print_status "$BLUE" "Using already mounted EFI partition at: $EFI_MOUNT_POINT" + + # Check EFI contents + print_status "$BLUE" "EFI partition contents:" + ls -la "$EFI_MOUNT_POINT" + + # Check for existing boot entries + if [[ -d "$EFI_MOUNT_POINT/EFI" ]]; then + print_status "$BLUE" "EFI directory contents:" + find "$EFI_MOUNT_POINT/EFI" -type f -name "*.efi" 2>/dev/null || echo "No EFI files found" + fi + else + # EFI partition not mounted, detect and mount temporarily + local efi_partition=$(detect_efi_partition "$device") + if [[ -z "$efi_partition" ]]; then + print_status "$RED" "Error: No EFI partition found on device $device" + exit 1 + fi + + print_status "$BLUE" "Mounting EFI partition temporarily for inspection..." + temp_mount="/tmp/efi-temp" + mkdir -p "$temp_mount" + mount "$efi_partition" "$temp_mount" + + # Check EFI contents + print_status "$BLUE" "EFI partition contents:" + ls -la "$temp_mount" + + # Check for existing boot entries + if [[ -d "$temp_mount/EFI" ]]; then + print_status "$BLUE" "EFI directory contents:" + find "$temp_mount/EFI" -type f -name "*.efi" 2>/dev/null || echo "No EFI files found" + fi fi # Attempt to repair boot entries if efibootmgr is available @@ -657,10 +855,18 @@ check_efi() { # Try to create a new boot entry for GRUB local grub_efi_path="" local grub_efi_name="" + local efi_base_path="" + + # Determine the base path for EFI operations + if [[ -d "$EFI_MOUNT_POINT" ]] && mountpoint -q "$EFI_MOUNT_POINT"; then + efi_base_path="$EFI_MOUNT_POINT" + else + efi_base_path="$temp_mount" + fi # Search for GRUB EFI binaries in common locations for grub_path in "EFI/grub/grubx64.efi" "EFI/grub/grub.efi" "EFI/ubuntu/grubx64.efi" "EFI/fedora/grubx64.efi" "EFI/arch/grubx64.efi"; do - if [[ -f "$temp_mount/$grub_path" ]]; then + if [[ -f "$efi_base_path/$grub_path" ]]; then grub_efi_path="$grub_path" grub_efi_name=$(basename "$grub_path") break @@ -745,9 +951,11 @@ check_efi() { print_status "$YELLOW" "efibootmgr not available, skipping boot entry repair" fi - # Unmount temporary mount - umount "$temp_mount" - rmdir "$temp_mount" + # Unmount temporary mount if we created one + if [[ -n "$temp_mount" ]] && mountpoint -q "$temp_mount"; then + umount "$temp_mount" + rmdir "$temp_mount" + fi print_status "$GREEN" "EFI partition check and repair completed" } @@ -800,11 +1008,49 @@ cleanup() { # Remove temporary mount points rmdir "$MOUNT_POINT" 2>/dev/null || true - rmdir "$EFI_MOUNT_POINT" 2>/dev/null || true + rmdir "$EFI_MOINT_POINT" 2>/dev/null || true + + # Remove temporary mount directories created during detection + find /tmp -maxdepth 1 -name "root-check-*" -type d 2>/dev/null | xargs -r rm -rf + find /tmp -maxdepth 1 -name "boot-check-*" -type d 2>/dev/null | xargs -r rm -rf + find /tmp -maxdepth 1 -name "efi-temp" -type d 2>/dev/null | xargs -r rm -rf + + # Remove backup directory if it exists + if [[ -d "$BACKUP_DIR" ]]; then + rm -rf "$BACKUP_DIR" + print_status "$BLUE" "Removed backup directory: $BACKUP_DIR" + fi print_status "$GREEN" "Cleanup completed" } +# Function to perform comprehensive cleanup (clean command) +comprehensive_clean() { + print_status "$BLUE" "Performing comprehensive cleanup..." + + # First, run the standard cleanup + cleanup + + # Additional cleanup operations for the clean command + + # Remove old log files (older than 7 days) + print_status "$BLUE" "Removing old log files..." + find /tmp -maxdepth 1 -name "grub-repair-*.log" -mtime +7 2>/dev/null | xargs -r rm -f + + # Remove old backup directories (older than 7 days) + print_status "$BLUE" "Removing old backup directories..." + find /tmp -maxdepth 1 -name "grub-backup-*" -mtime +7 -type d 2>/dev/null | xargs -r rm -rf + + # Clean up any remaining temporary files + print_status "$BLUE" "Removing remaining temporary files..." + find /tmp -maxdepth 1 -name "grub-repair-*" -type f 2>/dev/null | xargs -r rm -f + + # Reset global variables to clean state + EFI_PARTITION_PATH="" + + print_status "$GREEN" "Comprehensive cleanup completed" +} + # Function to complete boot repair fix_boot() { local device="$1" @@ -812,6 +1058,39 @@ fix_boot() { print_status "$BLUE" "Starting complete boot repair..." + # Show boot mode + local boot_mode=$(detect_boot_mode) + print_status "$BLUE" "Detected boot mode: $boot_mode" + + # Show detected partitions for confirmation + print_status "$BLUE" "Detected partitions:" + local efi_partition=$(detect_efi_partition "$device") + if [[ -n "$efi_partition" ]]; then + print_status "$BLUE" " EFI partition: $efi_partition" + fi + + local root_partition=$(detect_root_partition "$device") + if [[ -n "$root_partition" ]]; then + print_status "$BLUE" " Root partition: $root_partition" + fi + + # Final confirmation before proceeding + print_status "$YELLOW" "About to perform the following operations:" + print_status "$BLUE" " 1. Mount system partitions" + print_status "$BLUE" " 2. Install GRUB for $boot_mode mode" + print_status "$BLUE" " 3. Update GRUB configuration" + if [[ "$boot_mode" == "uefi" ]]; then + print_status "$BLUE" " 4. Check and repair EFI boot entries" + fi + print_status "$BLUE" " 5. Unmount all partitions" + + print_status "$YELLOW" "Continue with boot repair? (y/N)" + read -r response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + print_status "$BLUE" "Boot repair cancelled by user" + exit 0 + fi + # Create backup if requested if [[ "$CREATE_BACKUP" == "true" ]]; then create_backup @@ -826,8 +1105,12 @@ fix_boot() { # Update GRUB configuration update_grub - # Check and repair EFI - check_efi "$device" + # Check and repair EFI (only for UEFI systems) + if [[ "$boot_mode" == "uefi" ]]; then + check_efi "$device" + else + print_status "$BLUE" "Skipping EFI operations for BIOS system" + fi print_status "$GREEN" "Boot repair completed successfully!" print_status "$BLUE" "You can now reboot your system." @@ -835,7 +1118,7 @@ fix_boot() { # Main script logic main() { - # Parse command line arguments + # Initialize variables local device="" local partition="1" local command="" @@ -843,6 +1126,8 @@ main() { local FORCE="false" local VERBOSE="false" + # Parse command line arguments + while [[ $# -gt 0 ]]; do case $1 in -h|--help) @@ -854,15 +1139,15 @@ main() { shift ;; -d|--device) - device="$2" + device=$(sanitize_path "$2" "Device path") shift 2 ;; -p|--partition) - partition="$2" + partition=$(validate_partition "$2") shift 2 ;; -m|--mount) - MOUNT_POINT="$2" + MOUNT_POINT=$(sanitize_path "$2" "Mount point") EFI_MOUNT_POINT="$MOUNT_POINT/boot/efi" shift 2 ;; @@ -875,7 +1160,7 @@ main() { shift ;; -l|--log) - LOG_FILE="$2" + LOG_FILE=$(sanitize_path "$2" "Log file path") shift 2 ;; -*) @@ -909,7 +1194,7 @@ main() { set -x fi - # Execute command + # Validate and execute command case "$command" in detect) detect_systems @@ -939,7 +1224,7 @@ main() { show_status ;; clean) - cleanup + comprehensive_clean ;; *) print_status "$RED" "Error: Unknown command: $command"