From 25c41769aaeff2eeec8535c49a218b380a9d9c02 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 3 Jan 2026 21:01:40 +0100 Subject: [PATCH 1/6] Bootloader changes: -> GRUB: Support for UKI - Disable 10_linux or breaks at kernel updates - Create 09_custom entry -> rEFInd: - Remove fallback entry similar to other bootloaders - With UKI still one dead entry can be hidden with DEL key -> All bootloaders: - Default to UKI on if supported, if using no UKI and /efi Causes systemd boot to not load, because it needs a XTLDRBOOT part Safer default for modern setups and simpler sec boot compat --- archinstall/lib/installer.py | 80 ++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index da1941834e..22314e7f0f 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1325,6 +1325,7 @@ def _add_grub_bootloader( boot_partition: PartitionModification, root: PartitionModification | LvmVolume, efi_partition: PartitionModification | None, + uki_enabled: bool = False, bootloader_removable: bool = False, ) -> None: debug('Installing grub bootloader') @@ -1396,6 +1397,44 @@ def _add_grub_bootloader( except SysCallError as err: raise DiskError(f'Failed to install GRUB boot on {boot_partition.dev_path}: {err}') + # Add custom UKI entries if enabled + if uki_enabled and SysInfo.has_uefi() and efi_partition: + custom_entries = self.target / 'etc/grub.d/09_custom' + # Initialize with proper shebang header and 3 comment lines + entries_content = ( + '#!/bin/sh\n' + 'exec tail -n +3 $0\n' + '# This file provides UKI (Unified Kernel Image) boot entries.\n' + '# Generated by archinstall. Do not modify the exec tail line above.\n' + '# Custom entries can be added below.\n\n' + ) + + # Generate UKI menu entries for each kernel + uki_entries = [] + for kernel in self.kernels: + uki_file = f'/EFI/Linux/arch-{kernel}.efi' + entry = textwrap.dedent( + f""" + menuentry "Arch Linux ({kernel}) UKI" {{ + insmod fat + insmod chain + search --no-floppy --set=root --fs-uuid {efi_partition.uuid} + chainloader {uki_file} + }} + """ + ) + uki_entries.append(entry) + + # Write UKI entries to 09_custom + entries_content += '\n'.join(uki_entries) + custom_entries.write_text(entries_content) + custom_entries.chmod(0o755) + + # Disable 10_linux permanently to prevent broken entries on kernel updates + linux_script = self.target / 'etc/grub.d/10_linux' + if linux_script.exists(): + linux_script.chmod(0o644) + try: self.arch_chroot( f'grub-mkconfig -o {boot_dir}/grub/grub.cfg', @@ -1669,29 +1708,28 @@ def _add_refind_bootloader( 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' + if uki_enabled: + entry = f'"Arch Linux ({kernel}) 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}.img' else: - # Root without btrfs subvolume - initrd_path = f'initrd=\\boot\\initramfs-{kernel}{variant}.img' + initrd_path = f'initrd=\\boot\\initramfs-{kernel}.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}"' + # Root without btrfs subvolume + initrd_path = f'initrd=\\boot\\initramfs-{kernel}.img' + else: + # Kernels are at root of their partition (ESP or separate boot partition) + initrd_path = f'initrd=\\initramfs-{kernel}.img' + entry = f'"Arch Linux ({kernel})" "{kernel_params} {initrd_path}"' - config_contents.append(entry) + config_contents.append(entry) config_path.write_text('\n'.join(config_contents) + '\n') @@ -1823,7 +1861,7 @@ def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False, boot case Bootloader.Systemd: self._add_systemd_bootloader(boot_partition, root, efi_partition, uki_enabled) case Bootloader.Grub: - self._add_grub_bootloader(boot_partition, root, efi_partition, bootloader_removable) + self._add_grub_bootloader(boot_partition, root, efi_partition, uki_enabled, bootloader_removable) case Bootloader.Efistub: self._add_efistub_bootloader(boot_partition, root, uki_enabled) case Bootloader.Limine: From 60995ea91f4869fd0cc71521db3a74d41e2a5d51 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 3 Jan 2026 21:23:59 +0100 Subject: [PATCH 2/6] Add new models --- archinstall/lib/models/bootloader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 0a004ae83f..b8f4b97135 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -21,7 +21,7 @@ class Bootloader(Enum): def has_uki_support(self) -> bool: match self: - case Bootloader.Efistub | Bootloader.Limine | Bootloader.Systemd | Bootloader.Refind: + case Bootloader.Efistub | Bootloader.Limine | Bootloader.Systemd | Bootloader.Refind | Bootloader.Grub: return True case _: return False @@ -82,7 +82,8 @@ def parse_arg(cls, config: dict[str, Any], skip_boot: bool) -> BootloaderConfigu def get_default(cls) -> BootloaderConfiguration: bootloader = Bootloader.get_default() removable = SysInfo.has_uefi() and bootloader.has_removable_support() - return cls(bootloader=bootloader, uki=False, removable=removable) + uki = SysInfo.has_uefi() and bootloader.has_uki_support() + return cls(bootloader=bootloader, uki=uki, removable=removable) def preview(self) -> str: text = f'{tr("Bootloader")}: {self.bootloader.value}' From 4c0c6f8059b8d649c98eab2fba740e72d45d9f60 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sun, 4 Jan 2026 00:27:50 +0100 Subject: [PATCH 3/6] Modify based on grub-2.14-rc1 -> No need to use chainload Thanks to codefiles for the heads-up --- archinstall/lib/installer.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 22314e7f0f..fe6d0935b4 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1400,7 +1400,6 @@ def _add_grub_bootloader( # Add custom UKI entries if enabled if uki_enabled and SysInfo.has_uefi() and efi_partition: custom_entries = self.target / 'etc/grub.d/09_custom' - # Initialize with proper shebang header and 3 comment lines entries_content = ( '#!/bin/sh\n' 'exec tail -n +3 $0\n' @@ -1409,28 +1408,22 @@ def _add_grub_bootloader( '# Custom entries can be added below.\n\n' ) - # Generate UKI menu entries for each kernel uki_entries = [] for kernel in self.kernels: - uki_file = f'/EFI/Linux/arch-{kernel}.efi' entry = textwrap.dedent( f""" menuentry "Arch Linux ({kernel}) UKI" {{ - insmod fat - insmod chain - search --no-floppy --set=root --fs-uuid {efi_partition.uuid} - chainloader {uki_file} + uki /EFI/Linux/arch-{kernel}.efi }} """ ) uki_entries.append(entry) - # Write UKI entries to 09_custom entries_content += '\n'.join(uki_entries) custom_entries.write_text(entries_content) custom_entries.chmod(0o755) - # Disable 10_linux permanently to prevent broken entries on kernel updates + # Disable 10_linux to prevent broken entries on kernel updates linux_script = self.target / 'etc/grub.d/10_linux' if linux_script.exists(): linux_script.chmod(0o644) From 72c8e3aee77a7065945c397c4752fa6e4a32258d Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sun, 4 Jan 2026 00:44:54 +0100 Subject: [PATCH 4/6] Simplify has_uki_support --- archinstall/lib/models/bootloader.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index b8f4b97135..0e2a51dff0 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -20,11 +20,7 @@ class Bootloader(Enum): Refind = 'Refind' def has_uki_support(self) -> bool: - match self: - case Bootloader.Efistub | Bootloader.Limine | Bootloader.Systemd | Bootloader.Refind | Bootloader.Grub: - return True - case _: - return False + return self != Bootloader.NO_BOOTLOADER def has_removable_support(self) -> bool: match self: From 64910971c3c71932cba3bf3a35d6f33599db7eba Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sun, 4 Jan 2026 00:46:32 +0100 Subject: [PATCH 5/6] Tab --- archinstall/lib/models/bootloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 0e2a51dff0..7cfc04b675 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -20,7 +20,7 @@ class Bootloader(Enum): Refind = 'Refind' def has_uki_support(self) -> bool: - return self != Bootloader.NO_BOOTLOADER + return self != Bootloader.NO_BOOTLOADER def has_removable_support(self) -> bool: match self: From f3781bfdae7b99b12ef2b2ab3718793cf034d598 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sun, 4 Jan 2026 00:48:47 +0100 Subject: [PATCH 6/6] checks --- archinstall/lib/models/bootloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index 7cfc04b675..22fa2c3106 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -20,7 +20,7 @@ class Bootloader(Enum): Refind = 'Refind' def has_uki_support(self) -> bool: - return self != Bootloader.NO_BOOTLOADER + return self != Bootloader.NO_BOOTLOADER def has_removable_support(self) -> bool: match self: