Skip to content

Commit

Permalink
Fix Limine bootloader deployment (archlinux#2216)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
48cf authored Nov 21, 2023
1 parent f16af43 commit e6344f9
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 75 deletions.
14 changes: 14 additions & 0 deletions archinstall/lib/disk/device_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,20 @@ def get_parent_device_path(self, dev_path: Path) -> Path:
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
Expand Down
7 changes: 5 additions & 2 deletions archinstall/lib/global_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,11 @@ def _validate_bootloader(self) -> Optional[str]:
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

Expand Down
135 changes: 62 additions & 73 deletions archinstall/lib/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,70 +972,54 @@ def _add_grub_bootloader(
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
info(f"Limine boot partition: {boot_partition.dev_path}")

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/'

SysCommand(cmd, peek_output=True)
except SysCallError as err:
raise DiskError(f"Failed to install Limine BOOTX64.EFI on {boot_partition.dev_path}: {err}")
efi_dir_path = self.target / efi_partition.mountpoint.relative_to('/') / 'EFI' / 'BOOT'
efi_dir_path.mkdir(parents=True, exist_ok=True)

# Create the EFI limine pacman hook.
create_pacman_hook("""
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = limine
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}')

[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
Expand All @@ -1044,33 +1028,38 @@ def create_pacman_hook(contents: str):
[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
: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
: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
"""

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}")
hooks_dir = self.target / 'etc' / 'pacman.d' / 'hooks'
hooks_dir.mkdir(parents=True, exist_ok=True)

hook_path = hooks_dir / '99-limine.hook'
hook_path.write_text(hook_contents)

microcode = []

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"

Expand Down Expand Up @@ -1227,7 +1216,7 @@ def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False):
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)
Expand Down

0 comments on commit e6344f9

Please sign in to comment.