From cb6fe6b34b2ce73560764ed03a5b08a8f474760c Mon Sep 17 00:00:00 2001 From: justbispo <38961424+justbispo@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:24:22 +0000 Subject: [PATCH] Add support for rEFInd boot manager (#3707) * Add support for rEFInd boot manager * Fix ruff formatting complaints * Added support for different mountpoints for /efi and /boot Also fixed issue where if /boot is located in a BTRFS root partition, the initrd path wasn't including the subvol name. * Fix ruff formatting complaints * Replace SysCommand with self.arch_chroot call * Fix ruff formatting complaints --------- Co-authored-by: Diogo Bispo --- archinstall/lib/global_menu.py | 4 + archinstall/lib/installer.py | 105 ++++++++++++++++++++++++++- archinstall/lib/models/bootloader.py | 3 +- 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 965023ba..e8a58843 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -466,6 +466,10 @@ class GlobalMenu(AbstractMenu[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 2afe9064..9e42651d 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1604,6 +1604,106 @@ class Installer: 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, @@ -1657,11 +1757,12 @@ class Installer: 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 @@ -1713,6 +1814,8 @@ class Installer: 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 5494ea22..0a004ae8 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