From 71cd6d46f862e6cc48be1111300507952de00112 Mon Sep 17 00:00:00 2001 From: Softer Date: Sat, 18 Apr 2026 08:49:19 +0300 Subject: [PATCH] Fix Limine install with ESP mounted outside /boot Place limine.conf next to the EFI binary on the ESP so it is found regardless of ESP mountpoint, and block unbootable layouts (non-UKI Limine with ESP not at /boot and no separate /boot partition) in GlobalMenu validation, guided.main() and _add_limine_bootloader(). Fixes #4333 --- archinstall/lib/global_menu.py | 6 ++++++ archinstall/lib/installer.py | 16 ++++++++++----- archinstall/scripts/guided.py | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 2baef93d..2fe28cef 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import override from archinstall.default_profiles.profile import GreeterType @@ -492,6 +493,11 @@ class GlobalMenu(AbstractMenu[None]): if bootloader == Bootloader.Limine: 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' + if self._uefi and efi_partition and boot_partition == efi_partition and efi_partition.mountpoint != Path('/boot') and not bootloader_config.uki: + return ( + f'Limine requires kernels on a FAT partition. The ESP is mounted at {efi_partition.mountpoint}, ' + 'enable UKI or add a separate /boot partition' + ) elif bootloader == Bootloader.Refind: if not self._uefi: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 9f8c51b2..9978d647 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1466,6 +1466,16 @@ class Installer: elif not efi_partition.mountpoint: raise ValueError('EFI partition is not mounted') + # Limine can only read FAT filesystems. When the ESP doubles as + # the boot partition but is mounted outside /boot (e.g. /efi, + # /boot/efi) and UKI is disabled, kernels end up on the root + # filesystem under /boot/ which Limine cannot access. + if boot_partition == efi_partition and efi_partition.mountpoint != Path('/boot') and not uki_enabled: + raise DiskError( + f'Limine requires kernels on a FAT partition. The ESP is mounted at {efi_partition.mountpoint}, ' + 'so enable UKI or add a separate /boot partition to install Limine.', + ) + info(f'Limine EFI partition: {efi_partition.dev_path}') parent_dev_path = get_parent_device_path(efi_partition.safe_dev_path) @@ -1476,15 +1486,11 @@ class Installer: if bootloader_removable: efi_dir_path = efi_dir_path / 'BOOT' efi_dir_path_target = efi_dir_path_target / 'BOOT' - - boot_limine_path = self.target / 'boot' / 'limine' - boot_limine_path.mkdir(parents=True, exist_ok=True) - config_path = boot_limine_path / 'limine.conf' else: efi_dir_path = efi_dir_path / 'arch-limine' efi_dir_path_target = efi_dir_path_target / 'arch-limine' - config_path = efi_dir_path / 'limine.conf' + config_path = efi_dir_path / 'limine.conf' efi_dir_path.mkdir(parents=True, exist_ok=True) diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 34133722..882806dc 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -1,6 +1,7 @@ import os import sys import time +from pathlib import Path from archinstall.lib.applications.application_handler import ApplicationHandler from archinstall.lib.args import ArchConfig, ArchConfigHandler @@ -195,6 +196,35 @@ def perform_installation( pass +def _check_bootloader_layout(config: ArchConfig) -> str | None: + """Validate bootloader configuration against disk layout. + + Returns an error message if the configuration would produce an + unbootable system, or None if it is valid. + """ + # Limine can only read FAT. When the ESP is the boot partition but + # mounted outside /boot and UKI is disabled, the kernel ends up on the + # root filesystem which Limine cannot access. + if not (config.bootloader_config and config.bootloader_config.bootloader == Bootloader.Limine and not config.bootloader_config.uki and config.disk_config): + return None + + efi_part = next( + (p for m in config.disk_config.device_modifications if (p := m.get_efi_partition())), + None, + ) + boot_part = next( + (p for m in config.disk_config.device_modifications if (p := m.get_boot_partition())), + None, + ) + + if efi_part and boot_part == efi_part and efi_part.mountpoint != Path('/boot'): + return ( + f'Limine requires kernels on a FAT partition. The ESP is mounted at {efi_part.mountpoint}, ' + 'enable UKI or add a separate /boot partition to install Limine.' + ) + return None + + def main(arch_config_handler: ArchConfigHandler | None = None) -> None: if arch_config_handler is None: arch_config_handler = ArchConfigHandler() @@ -211,6 +241,12 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None: config.write_debug() config.save() + # Safety net for silent/config-file flow. The TUI menu blocks Install via + # GlobalMenu._validate_bootloader() before reaching this point. + if err_msg := _check_bootloader_layout(arch_config_handler.config): + error(err_msg) + return + if arch_config_handler.args.dry_run: return