Fix Limine bootloader deployment (#2216)

* Add `get_unique_path_for_device` to `DeviceHandler`

* Fix Limine bootloader deployment

* Fail if UKI is enabled with Limine

* Support more configuration options with Limine

* Fix linter errors

* Fix boot partition fs_type check for Limine
This commit is contained in:
czapek 2023-11-21 10:19:17 +01:00 committed by GitHub
parent f16af43949
commit e6344f93f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 72 deletions

View File

@ -133,6 +133,20 @@ class DeviceHandler(object):
lsblk = get_lsblk_info(dev_path)
return Path(f'/dev/{lsblk.pkname}')
def get_unique_path_for_device(self, dev_path: Path) -> Optional[Path]:
paths = Path('/dev/disk/by-id').glob('*')
linked_targets = {p.resolve(): p for p in paths}
linked_wwn_targets = {p: linked_targets[p] for p in linked_targets
if p.name.startswith('wwn-') or p.name.startswith('nvme-eui.')}
if dev_path in linked_wwn_targets:
return linked_wwn_targets[dev_path]
if dev_path in linked_targets:
return linked_targets[dev_path]
return None
def get_uuid_for_path(self, path: Path) -> Optional[str]:
partition = self.find_partition(path)
return partition.partuuid if partition else None

View File

@ -363,8 +363,11 @@ class GlobalMenu(AbstractMenu):
if boot_partition is None:
return "Boot partition not found"
if bootloader == Bootloader.Limine and boot_partition.fs_type == disk.FilesystemType.Btrfs:
return "Limine bootloader does not support booting from BTRFS filesystem"
if bootloader == Bootloader.Limine:
if boot_partition.fs_type != disk.FilesystemType.Fat32:
return "Limine does not support booting from filesystems other than FAT32"
elif self._menu_options['uki'].current_selection:
return "Limine does not support booting UKIs"
return None

View File

@ -972,70 +972,54 @@ class Installer:
def _add_limine_bootloader(
self,
boot_partition: disk.PartitionModification,
efi_partition: Optional[disk.PartitionModification],
root_partition: disk.PartitionModification
):
self.pacman.strap('limine')
info(f"Limine boot partition: {boot_partition.dev_path}")
root_uuid = root_partition.uuid
def create_pacman_hook(contents: str):
HOOK_DIR = "/etc/pacman.d/hooks"
SysCommand(f"/usr/bin/arch-chroot {self.target} mkdir -p {HOOK_DIR}")
SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"echo '{contents}' > {HOOK_DIR}/liminedeploy.hook\"")
limine_path = self.target / 'usr' / 'share' / 'limine'
hook_command = None
if SysInfo.has_uefi():
if not efi_partition:
raise ValueError('Could not detect efi partition')
elif not efi_partition.mountpoint:
raise ValueError('EFI partition is not mounted')
info(f"Limine EFI partition: {efi_partition.dev_path}")
try:
# The `limine.sys` file, contains stage 3 code.
cmd = f'/usr/bin/arch-chroot' \
f' {self.target}' \
f' cp' \
f' /usr/share/limine/BOOTX64.EFI' \
f' /boot/EFI/BOOT/'
efi_dir_path = self.target / efi_partition.mountpoint.relative_to('/') / 'EFI' / 'BOOT'
efi_dir_path.mkdir(parents=True, exist_ok=True)
SysCommand(cmd, peek_output=True)
except SysCallError as err:
raise DiskError(f"Failed to install Limine BOOTX64.EFI on {boot_partition.dev_path}: {err}")
for file in ('BOOTIA32.EFI', 'BOOTX64.EFI'):
shutil.copy(limine_path / file, efi_dir_path)
except Exception as err:
raise DiskError(f'Failed to install Limine in {self.target}{efi_partition.mountpoint}: {err}')
# Create the EFI limine pacman hook.
create_pacman_hook("""
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = limine
[Action]
Description = Deploying Limine after upgrade...
When = PostTransaction
Exec = /usr/bin/cp /usr/share/limine/BOOTX64.EFI /boot/EFI/BOOT/
""")
hook_command = f'/usr/bin/cp /usr/share/limine/BOOTIA32.EFI {efi_partition.mountpoint}/EFI/BOOT/' \
f' && /usr/bin/cp /usr/share/limine/BOOTX64.EFI {efi_partition.mountpoint}/EFI/BOOT/'
else:
parent_dev_path = disk.device_handler.get_parent_device_path(boot_partition.safe_dev_path)
try:
# The `limine.sys` file, contains stage 3 code.
cmd = f'/usr/bin/arch-chroot' \
f' {self.target}' \
f' cp' \
f' /usr/share/limine/limine-bios.sys' \
f' /boot/limine-bios.sys'
if unique_path := disk.device_handler.get_unique_path_for_device(parent_dev_path):
parent_dev_path = unique_path
SysCommand(cmd, peek_output=True)
try:
# The `limine-bios.sys` file contains stage 3 code.
shutil.copy(limine_path / 'limine-bios.sys', self.target / 'boot')
# `limine bios-install` deploys the stage 1 and 2 to the disk.
cmd = f'/usr/bin/arch-chroot' \
f' {self.target}' \
f' limine' \
f' bios-install' \
f' {parent_dev_path}'
SysCommand(f'/usr/bin/arch-chroot {self.target} limine bios-install {parent_dev_path}', peek_output=True)
except Exception as err:
raise DiskError(f'Failed to install Limine on {parent_dev_path}: {err}')
SysCommand(cmd, peek_output=True)
except SysCallError as err:
raise DiskError(f"Failed to install Limine on {boot_partition.dev_path}: {err}")
hook_command = f'/usr/bin/limine bios-install {parent_dev_path}' \
f' && /usr/bin/cp /usr/share/limine/limine-bios.sys /boot/'
create_pacman_hook(f"""
[Trigger]
hook_contents = f'''[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
@ -1044,33 +1028,38 @@ Target = limine
[Action]
Description = Deploying Limine after upgrade...
When = PostTransaction
# XXX: Kernel name descriptors cannot be used since they are not persistent and
# can change after each boot.
Exec = /bin/sh -c \\"/usr/bin/limine bios-install /dev/disk/by-uuid/{root_uuid} && /usr/bin/cp /usr/share/limine/limine-bios.sys /boot/\\"
""")
Exec = /bin/sh -c "{hook_command}"
'''
# Limine does not ship with a default configuration file. We are going to
# create a basic one that is similar to the one GRUB generates.
try:
config = f"""
TIMEOUT=5
hooks_dir = self.target / 'etc' / 'pacman.d' / 'hooks'
hooks_dir.mkdir(parents=True, exist_ok=True)
:Arch Linux
PROTOCOL=linux
KERNEL_PATH=boot:///vmlinuz-linux
CMDLINE=root=UUID={root_uuid} rw rootfstype={root_partition.safe_fs_type.value} loglevel=3
MODULE_PATH=boot:///initramfs-linux.img
hook_path = hooks_dir / '99-limine.hook'
hook_path.write_text(hook_contents)
:Arch Linux (fallback)
PROTOCOL=linux
KERNEL_PATH=boot:///vmlinuz-linux
CMDLINE=root=UUID={root_uuid} rw rootfstype={root_partition.safe_fs_type.value} loglevel=3
MODULE_PATH=boot:///initramfs-linux-fallback.img
"""
microcode = []
SysCommand(f"/usr/bin/arch-chroot {self.target} sh -c \"echo '{config}' > /boot/limine.cfg\"")
except SysCallError as err:
raise DiskError(f"Could not configure Limine: {err}")
if ucode := self._get_microcode():
microcode = [f'MODULE_PATH=boot:///{ucode}']
kernel_params = ' '.join(self._get_kernel_params(root_partition))
config_contents = 'TIMEOUT=5\n'
for kernel in self.kernels:
for variant in ('', '-fallback'):
entry = [
f'PROTOCOL=linux',
f'KERNEL_PATH=boot:///vmlinuz-{kernel}',
*microcode,
f'MODULE_PATH=boot:///initramfs-{kernel}{variant}.img',
f'CMDLINE={kernel_params}',
]
config_contents += f'\n:Arch Linux ({kernel}{variant})\n'
config_contents += '\n'.join([f' {it}' for it in entry]) + '\n'
config_path = self.target / 'boot' / 'limine.cfg'
config_path.write_text(config_contents)
self.helper_flags['bootloader'] = "limine"
@ -1227,7 +1216,7 @@ TIMEOUT=5
case Bootloader.Efistub:
self._add_efistub_bootloader(boot_partition, root_partition, uki_enabled)
case Bootloader.Limine:
self._add_limine_bootloader(boot_partition, root_partition)
self._add_limine_bootloader(boot_partition, efi_partition, root_partition)
def add_additional_packages(self, packages: Union[str, List[str]]) -> bool:
return self.pacman.strap(packages)