From da19fcdff76d5eaea0f6d3d500063448f2fa1a2a Mon Sep 17 00:00:00 2001 From: Softer Date: Fri, 24 Apr 2026 14:21:13 +0300 Subject: [PATCH 1/3] Extend validate_bootloader_layout with UEFI-dependent checks Add is_uefi parameter and three new validations: Systemd-boot, Efistub and rEFInd require UEFI; Efistub additionally requires a FAT boot partition. Move the rEFInd UEFI-only check out of GlobalMenu so guided.py and Installer silent-install paths get the same coverage. --- archinstall/lib/bootloader/utils.py | 34 +++++++++++++++++++++++++---- archinstall/lib/global_menu.py | 7 +----- archinstall/lib/installer.py | 1 + archinstall/scripts/guided.py | 2 ++ 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/archinstall/lib/bootloader/utils.py b/archinstall/lib/bootloader/utils.py index a732f4e117..2d0b5a8198 100644 --- a/archinstall/lib/bootloader/utils.py +++ b/archinstall/lib/bootloader/utils.py @@ -9,6 +9,8 @@ class BootloaderValidationFailureKind(Enum): LimineNonFatBoot = auto() LimineLayout = auto() + BootloaderRequiresUefi = auto() + EfistubNonFatBoot = auto() @dataclass(frozen=True) @@ -17,9 +19,13 @@ class BootloaderValidationFailure: description: str +_UEFI_ONLY_BOOTLOADERS = (Bootloader.Systemd, Bootloader.Efistub, Bootloader.Refind) + + def validate_bootloader_layout( bootloader_config: BootloaderConfiguration | None, disk_config: DiskLayoutConfiguration | None, + is_uefi: bool, ) -> BootloaderValidationFailure | None: """Validate bootloader configuration against disk layout. @@ -29,12 +35,32 @@ def validate_bootloader_layout( if not (bootloader_config and disk_config): return None - if bootloader_config.bootloader == Bootloader.Limine: - boot_part = next( - (p for m in disk_config.device_modifications if (p := m.get_boot_partition())), - None, + bootloader = bootloader_config.bootloader + + if bootloader == Bootloader.NO_BOOTLOADER: + return None + + if bootloader in _UEFI_ONLY_BOOTLOADERS and not is_uefi: + return BootloaderValidationFailure( + kind=BootloaderValidationFailureKind.BootloaderRequiresUefi, + description=f'{bootloader.value} requires a UEFI system.', ) + boot_part = next( + (p for m in disk_config.device_modifications if (p := m.get_boot_partition())), + None, + ) + + if bootloader == Bootloader.Efistub: + # The UEFI firmware reads the kernel directly from the boot partition, + # which must be FAT. + if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()): + return BootloaderValidationFailure( + kind=BootloaderValidationFailureKind.EfistubNonFatBoot, + description='Efistub does not support booting with a non-FAT boot partition.', + ) + + if bootloader == Bootloader.Limine: # Limine reads its config and kernels from the boot partition, which # must be FAT. if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()): diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 3d94f31e2b..e961b5e71e 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -461,8 +461,6 @@ def _validate_bootloader(self) -> str | None: if not bootloader_config or bootloader_config.bootloader == Bootloader.NO_BOOTLOADER: return None - bootloader = bootloader_config.bootloader - if disk_config := self._item_group.find_by_key('disk_config').value: for layout in disk_config.device_modifications: if root_partition := layout.get_root_partition(): @@ -490,10 +488,7 @@ def _validate_bootloader(self) -> str | None: if efi_partition.fs_type is None or not efi_partition.fs_type.is_fat(): return 'ESP must be formatted as a FAT filesystem' - if bootloader == Bootloader.Refind and not self._uefi: - return 'rEFInd can only be used on UEFI systems' - - if failure := validate_bootloader_layout(bootloader_config, disk_config): + if failure := validate_bootloader_layout(bootloader_config, disk_config, self._uefi): return failure.description return None diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 19560d75a9..c65c084310 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1472,6 +1472,7 @@ def _add_limine_bootloader( if failure := validate_bootloader_layout( BootloaderConfiguration(bootloader=Bootloader.Limine, uki=uki_enabled), self._disk_config, + SysInfo.has_uefi(), ): raise DiskError(failure.description) diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 5cdcdad9d2..32198c9b66 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -11,6 +11,7 @@ from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.general.general_menu import PostInstallationAction, select_post_installation from archinstall.lib.global_menu import GlobalMenu +from archinstall.lib.hardware import SysInfo from archinstall.lib.installer import Installer, accessibility_tools_in_use, run_custom_user_commands from archinstall.lib.menu.util import delayed_warning from archinstall.lib.mirror.mirror_handler import MirrorListHandler @@ -217,6 +218,7 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None: if failure := validate_bootloader_layout( arch_config_handler.config.bootloader_config, arch_config_handler.config.disk_config, + SysInfo.has_uefi(), ): error(failure.description) return From 6818f35ebbb519bcbc16635acbd621b4132b3999 Mon Sep 17 00:00:00 2001 From: Softer Date: Sun, 26 Apr 2026 11:46:32 +0300 Subject: [PATCH 2/3] Encapsulate UEFI-only flag in Bootloader enum Replace module-level _UEFI_ONLY_BOOTLOADERS tuple with an is_uefi_only() method on the Bootloader enum, mirroring the existing has_uki_support() / has_removable_support() pattern. --- archinstall/lib/bootloader/utils.py | 5 +---- archinstall/lib/models/bootloader.py | 7 +++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/archinstall/lib/bootloader/utils.py b/archinstall/lib/bootloader/utils.py index 2d0b5a8198..e40b0b0f64 100644 --- a/archinstall/lib/bootloader/utils.py +++ b/archinstall/lib/bootloader/utils.py @@ -19,9 +19,6 @@ class BootloaderValidationFailure: description: str -_UEFI_ONLY_BOOTLOADERS = (Bootloader.Systemd, Bootloader.Efistub, Bootloader.Refind) - - def validate_bootloader_layout( bootloader_config: BootloaderConfiguration | None, disk_config: DiskLayoutConfiguration | None, @@ -40,7 +37,7 @@ def validate_bootloader_layout( if bootloader == Bootloader.NO_BOOTLOADER: return None - if bootloader in _UEFI_ONLY_BOOTLOADERS and not is_uefi: + if bootloader.is_uefi_only() and not is_uefi: return BootloaderValidationFailure( kind=BootloaderValidationFailureKind.BootloaderRequiresUefi, description=f'{bootloader.value} requires a UEFI system.', diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 68c0bf9878..5d8d0d809a 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -25,6 +25,13 @@ def has_removable_support(self) -> bool: case _: return False + def is_uefi_only(self) -> bool: + match self: + case Bootloader.Systemd | Bootloader.Efistub | Bootloader.Refind: + return True + case _: + return False + def json(self) -> str: return self.value From f4bcf6d2530b9525b49205d8a9b42a527ea23794 Mon Sep 17 00:00:00 2001 From: Softer Date: Sun, 26 Apr 2026 12:03:06 +0300 Subject: [PATCH 3/3] Drop is_uefi parameter from validate_bootloader_layout The UEFI flag is a constant system fact for the run, so the validator retrieves it via SysInfo.has_uefi() directly instead of having every caller pass it in. Updates all three call sites in global_menu.py, installer.py and guided.py, and removes the now-unused SysInfo import from guided.py. --- archinstall/lib/bootloader/utils.py | 4 ++-- archinstall/lib/global_menu.py | 2 +- archinstall/lib/installer.py | 1 - archinstall/scripts/guided.py | 2 -- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/archinstall/lib/bootloader/utils.py b/archinstall/lib/bootloader/utils.py index e40b0b0f64..ed76172269 100644 --- a/archinstall/lib/bootloader/utils.py +++ b/archinstall/lib/bootloader/utils.py @@ -2,6 +2,7 @@ from enum import Enum, auto from pathlib import Path +from archinstall.lib.hardware import SysInfo from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration from archinstall.lib.models.device import DiskLayoutConfiguration @@ -22,7 +23,6 @@ class BootloaderValidationFailure: def validate_bootloader_layout( bootloader_config: BootloaderConfiguration | None, disk_config: DiskLayoutConfiguration | None, - is_uefi: bool, ) -> BootloaderValidationFailure | None: """Validate bootloader configuration against disk layout. @@ -37,7 +37,7 @@ def validate_bootloader_layout( if bootloader == Bootloader.NO_BOOTLOADER: return None - if bootloader.is_uefi_only() and not is_uefi: + if bootloader.is_uefi_only() and not SysInfo.has_uefi(): return BootloaderValidationFailure( kind=BootloaderValidationFailureKind.BootloaderRequiresUefi, description=f'{bootloader.value} requires a UEFI system.', diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index e961b5e71e..e327b38858 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -488,7 +488,7 @@ def _validate_bootloader(self) -> str | None: if efi_partition.fs_type is None or not efi_partition.fs_type.is_fat(): return 'ESP must be formatted as a FAT filesystem' - if failure := validate_bootloader_layout(bootloader_config, disk_config, self._uefi): + if failure := validate_bootloader_layout(bootloader_config, disk_config): return failure.description return None diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index c65c084310..19560d75a9 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1472,7 +1472,6 @@ def _add_limine_bootloader( if failure := validate_bootloader_layout( BootloaderConfiguration(bootloader=Bootloader.Limine, uki=uki_enabled), self._disk_config, - SysInfo.has_uefi(), ): raise DiskError(failure.description) diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 32198c9b66..5cdcdad9d2 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -11,7 +11,6 @@ from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.general.general_menu import PostInstallationAction, select_post_installation from archinstall.lib.global_menu import GlobalMenu -from archinstall.lib.hardware import SysInfo from archinstall.lib.installer import Installer, accessibility_tools_in_use, run_custom_user_commands from archinstall.lib.menu.util import delayed_warning from archinstall.lib.mirror.mirror_handler import MirrorListHandler @@ -218,7 +217,6 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None: if failure := validate_bootloader_layout( arch_config_handler.config.bootloader_config, arch_config_handler.config.disk_config, - SysInfo.has_uefi(), ): error(failure.description) return