installer: add Limine bootloader (#1815)

* installer: add Limine bootloader

Limine is a modern, advanced, portable, multiprotocol bootloader.

[Limine GitHub](https://github.com/limine-bootloader/limine)
[Limine Arch Wiki](https://wiki.archlinux.org/title/Limine)

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* limine: add UEFI support

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* global_menu: check filesystem and bootloader compatibility

Before on install, only missing configurations were checked. This commit
introduces bootloader validatity checks on install which verify if the
selected filesystem is compatiable with the selected bootloader (for
example, it is not possible to boot limine from BTRFS).

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* misc: fix the return value of `_validate_bootloader`

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* global_menu: make `mypy` happy

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* misc: make `flake8` happy

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* limine: upgrade to v5

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* limine: install packman hooks

Create the BIOS and UEFI pacman hooks so limine gets auto deployed on
update.

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* installer::limine: fix broken root UUID

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* docs: add a note saying its in beta

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

* install_limine: use `safe_fs_type`

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>

---------

Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>
This commit is contained in:
Anhad Singh 2023-06-30 17:53:53 +10:00 committed by GitHub
parent ffb9366280
commit a0e4e6ee76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 156 additions and 5 deletions

View File

@ -169,8 +169,8 @@ class GlobalMenu(AbstractMenu):
self._menu_options['install'] = \
Selector(
self._install_text(),
exec_func=lambda n, v: True if len(self._missing_configs()) == 0 else False,
preview_func=self._prev_install_missing_config,
exec_func=lambda n, v: self._is_config_valid(),
preview_func=self._prev_install_invalid_config,
no_store=True)
self._menu_options['abort'] = Selector(_('Abort'), exec_func=lambda n,v:exit(1))
@ -200,6 +200,14 @@ class GlobalMenu(AbstractMenu):
return list(missing)
def _is_config_valid(self) -> bool:
"""
Checks the validity of the current configuration.
"""
if len(self._missing_configs()) != 0:
return False
return self._validate_bootloader() is None
def _update_install_text(self, name: str, value: str):
text = self._install_text()
self._menu_options['install'].update_description(text)
@ -321,12 +329,42 @@ class GlobalMenu(AbstractMenu):
return disk.EncryptionType.type_to_text(current_value.encryption_type)
return ''
def _prev_install_missing_config(self) -> Optional[str]:
def _validate_bootloader(self) -> Optional[str]:
"""
Checks the selected bootloader is valid for the selected filesystem
type of the boot partition.
Returns [`None`] if the bootloader is valid, otherwise returns a
string with the error message.
"""
bootloader = self._menu_options['bootloader'].current_selection
boot_partition: Optional[disk.PartitionModification] = None
if disk_config := self._menu_options['disk_config'].current_selection:
for layout in disk_config.device_modifications:
if boot_partition := layout.get_boot_partition():
break
else:
return "No disk layout selected"
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"
return None
def _prev_install_invalid_config(self) -> Optional[str]:
if missing := self._missing_configs():
text = str(_('Missing configurations:\n'))
for m in missing:
text += f'- {m}\n'
return text[:-1] # remove last new line
if error := self._validate_bootloader():
return f"Invalid configuration: {error}"
return None
def _prev_users(self) -> Optional[str]:

View File

@ -8,6 +8,8 @@ import time
from pathlib import Path
from typing import Any, List, Optional, TYPE_CHECKING, Union, Dict, Callable
from ..lib.disk.device_model import get_lsblk_info
from . import disk
from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError
from .general import SysCommand
@ -850,6 +852,113 @@ class Installer:
self.helper_flags['bootloader'] = "grub"
def _add_limine_bootloader(
self,
boot_partition: disk.PartitionModification,
root_partition: disk.PartitionModification
):
self.pacman.strap('limine')
info(f"Limine boot partition: {boot_partition.dev_path}")
# XXX: We cannot use `root_partition.uuid` since corresponds to the UUID of the root
# partition before the format.
root_uuid = get_lsblk_info(root_partition.safe_dev_path).uuid
device = disk.device_handler.get_device_by_partition_path(boot_partition.safe_dev_path)
if not device:
raise ValueError(f'Can not find block device: {boot_partition.safe_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\"")
if SysInfo.has_uefi():
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/'
except SysCallError as err:
raise DiskError(f"Failed to install Limine BOOTX64.EFI on {boot_partition.dev_path}: {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/
""")
else:
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'
SysCommand(cmd, peek_output=True)
# `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' {device.device_info.path}'
SysCommand(cmd, peek_output=True)
except SysCallError as err:
raise DiskError(f"Failed to install Limine on {boot_partition.dev_path}: {err}")
create_pacman_hook(f"""
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
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/\\"
""")
# Limine does not ship with a default configuation 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}")
self.helper_flags['bootloader'] = "limine"
def _add_efistub_bootloader(
self,
boot_partition: disk.PartitionModification,
@ -918,6 +1027,7 @@ class Installer:
Archinstall supports one of three types:
* systemd-bootctl
* grub
* limine (beta)
* efistub (beta)
:param bootloader: Type of bootloader to be added
@ -948,6 +1058,8 @@ class Installer:
self._add_grub_bootloader(boot_partition, root_partition)
case Bootloader.Efistub:
self._add_efistub_bootloader(boot_partition, root_partition)
case Bootloader.Limine:
self._add_limine_bootloader(boot_partition, root_partition)
def add_additional_packages(self, packages: Union[str, List[str]]) -> bool:
return self.pacman.strap(packages)

View File

@ -40,9 +40,9 @@ def select_kernel(preset: List[str] = []) -> List[str]:
def ask_for_bootloader(preset: Bootloader) -> Bootloader:
# when the system only supports grub
# Systemd is UEFI only
if not SysInfo.has_uefi():
options = [Bootloader.Grub.value]
options = [Bootloader.Grub.value, Bootloader.Limine.value]
default = Bootloader.Grub.value
else:
options = Bootloader.values()

View File

@ -12,6 +12,7 @@ class Bootloader(Enum):
Systemd = 'Systemd-boot'
Grub = 'Grub'
Efistub = 'Efistub'
Limine = 'Limine'
def json(self):
return self.value