diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index fa2e51764b..223b0cf5ed 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -447,6 +447,10 @@ def _validate_bootloader(self) -> str | None: if boot_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]: return 'Limine does not support booting with a non-FAT boot partition' + elif bootloader == Bootloader.Refind: + if not SysInfo.has_uefi(): + return 'rEFInd can only be used on UEFI systems' + return None def _prev_install_invalid_config(self, item: MenuItem) -> str | None: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 4bc467ebf7..895568d836 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1565,6 +1565,106 @@ def _add_efistub_bootloader( self._helper_flags['bootloader'] = 'efistub' + def _add_refind_bootloader( + self, + boot_partition: PartitionModification, + efi_partition: PartitionModification | None, + root: PartitionModification | LvmVolume, + uki_enabled: bool = False, + ) -> None: + debug('Installing rEFInd bootloader') + + self.pacman.strap('refind') + + if not SysInfo.has_uefi(): + raise HardwareIncompatibilityError + + info(f'rEFInd boot partition: {boot_partition.dev_path}') + + if not efi_partition: + raise ValueError('Could not detect EFI system partition') + elif not efi_partition.mountpoint: + raise ValueError('EFI system partition is not mounted') + + info(f'rEFInd EFI partition: {efi_partition.dev_path}') + + try: + self.arch_chroot('refind-install') + except SysCallError as err: + raise DiskError(f'Could not install rEFInd to {self.target}{efi_partition.mountpoint}: {err}') + + if not boot_partition.mountpoint: + raise ValueError('Boot partition is not mounted, cannot write rEFInd config') + + boot_is_separate = boot_partition != efi_partition and boot_partition.dev_path != efi_partition.dev_path + + if boot_is_separate: + # Separate boot partition (not ESP, not root) + config_path = self.target / boot_partition.mountpoint.relative_to('/') / 'refind_linux.conf' + boot_on_root = False + elif efi_partition.mountpoint == Path('/boot'): + # ESP is mounted at /boot, kernels are on ESP + config_path = self.target / 'boot' / 'refind_linux.conf' + boot_on_root = False + else: + # ESP is elsewhere (/efi, /boot/efi, etc.), kernels are on root filesystem at /boot + config_path = self.target / 'boot' / 'refind_linux.conf' + boot_on_root = True + + config_contents = [] + + kernel_params = ' '.join(self._get_kernel_params(root)) + + for kernel in self.kernels: + for variant in ('', '-fallback'): + if uki_enabled: + entry = f'"Arch Linux ({kernel}{variant}) UKI" "{kernel_params}"' + else: + if boot_on_root: + # Kernels are in /boot subdirectory of root filesystem + if hasattr(root, 'btrfs_subvols') and root.btrfs_subvols: + # Root is btrfs with subvolume, find the root subvolume + root_subvol = next((sv for sv in root.btrfs_subvols if sv.is_root()), None) + if root_subvol: + subvol_name = root_subvol.name + initrd_path = f'initrd={subvol_name}\\boot\\initramfs-{kernel}{variant}.img' + else: + initrd_path = f'initrd=\\boot\\initramfs-{kernel}{variant}.img' + else: + # Root without btrfs subvolume + initrd_path = f'initrd=\\boot\\initramfs-{kernel}{variant}.img' + else: + # Kernels are at root of their partition (ESP or separate boot partition) + initrd_path = f'initrd=\\initramfs-{kernel}{variant}.img' + entry = f'"Arch Linux ({kernel}{variant})" "{kernel_params} {initrd_path}"' + + config_contents.append(entry) + + config_path.write_text('\n'.join(config_contents) + '\n') + + hook_contents = textwrap.dedent( + """\ + [Trigger] + Operation = Install + Operation = Upgrade + Type = Package + Target = refind + + [Action] + Description = Updating rEFInd on ESP + When = PostTransaction + Exec = /usr/bin/refind-install + """ + ) + + hooks_dir = self.target / 'etc' / 'pacman.d' / 'hooks' + hooks_dir.mkdir(parents=True, exist_ok=True) + + hook_path = hooks_dir / '99-refind.hook' + hook_path.write_text(hook_contents) + + self._helper_flags['bootloader'] = 'refind' + def _config_uki( self, root: PartitionModification | LvmVolume, @@ -1618,11 +1718,12 @@ def _config_uki( def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False) -> None: """ Adds a bootloader to the installation instance. - Archinstall supports one of three types: + Archinstall supports one of five types: * systemd-bootctl * grub * limine * efistub (beta) + * refnd (beta) :param bootloader: Type of bootloader to be added :param uki_enabled: Whether to use unified kernel images @@ -1674,6 +1775,8 @@ def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False, boot self._add_efistub_bootloader(boot_partition, root, uki_enabled) case Bootloader.Limine: self._add_limine_bootloader(boot_partition, efi_partition, root, uki_enabled, bootloader_removable) + case Bootloader.Refind: + self._add_refind_bootloader(boot_partition, efi_partition, root, uki_enabled) def add_additional_packages(self, packages: str | list[str]) -> None: return self.pacman.strap(packages) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 457d2b3b34..05c01413e4 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -17,10 +17,11 @@ class Bootloader(Enum): Grub = 'Grub' Efistub = 'Efistub' Limine = 'Limine' + Refind = 'Refind' def has_uki_support(self) -> bool: match self: - case Bootloader.Efistub | Bootloader.Limine | Bootloader.Systemd: + case Bootloader.Efistub | Bootloader.Limine | Bootloader.Systemd | Bootloader.Refind: return True case _: return False