Add dialog to install EFI bootloader to removable location (#3932)
* Add dialog to install EFI bootloader to removable location This is just for GRUB and Limine for now. * Move bootloader removable and UKI selections to bootloader submenu * Update ask_for_bootloader_removable() prompt for ease of translation * Fix issue where removable and UKI options were always enabled at first * Minor cosmetic fixes to bootloader removable code * Add has_removable_support to Bootloader * Validate UKI and removable options in installer * Use has_removable_support() where appropriate * Fix potential AttributeError when bootloader_config is None * Set default value for bootloader configuration menu item * Update documentation after EFI removable/Limine changes * Update limine.conf and non-removable location paths (as per Wiki) * Do not create fallback boot menu entries when using UKIs on Limine * Remove useless ask_* wrappers in bootloader_menu * Improve bootloader menu previews * Make bootloader menu __init__.py empty
This commit is contained in:
parent
840c1c0f7e
commit
eb815d817f
|
|
@ -15,7 +15,7 @@ from pydantic.dataclasses import dataclass as p_dataclass
|
|||
from archinstall.lib.crypt import decrypt
|
||||
from archinstall.lib.models.application import ApplicationConfiguration
|
||||
from archinstall.lib.models.authentication import AuthenticationConfiguration
|
||||
from archinstall.lib.models.bootloader import Bootloader
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.models.device import DiskEncryption, DiskLayoutConfiguration
|
||||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.mirrors import MirrorConfiguration
|
||||
|
|
@ -64,8 +64,7 @@ class ArchConfig:
|
|||
profile_config: ProfileConfiguration | None = None
|
||||
mirror_config: MirrorConfiguration | None = None
|
||||
network_config: NetworkConfiguration | None = None
|
||||
bootloader: Bootloader | None = None
|
||||
uki: bool = False
|
||||
bootloader_config: BootloaderConfiguration | None = None
|
||||
app_config: ApplicationConfiguration | None = None
|
||||
auth_config: AuthenticationConfiguration | None = None
|
||||
hostname: str = 'archlinux'
|
||||
|
|
@ -102,7 +101,6 @@ class ArchConfig:
|
|||
'archinstall-language': self.archinstall_language.json(),
|
||||
'hostname': self.hostname,
|
||||
'kernels': self.kernels,
|
||||
'uki': self.uki,
|
||||
'ntp': self.ntp,
|
||||
'packages': self.packages,
|
||||
'parallel_downloads': self.parallel_downloads,
|
||||
|
|
@ -110,7 +108,7 @@ class ArchConfig:
|
|||
'timezone': self.timezone,
|
||||
'services': self.services,
|
||||
'custom_commands': self.custom_commands,
|
||||
'bootloader': self.bootloader.json() if self.bootloader else None,
|
||||
'bootloader_config': self.bootloader_config.json() if self.bootloader_config else None,
|
||||
'app_config': self.app_config.json() if self.app_config else None,
|
||||
'auth_config': self.auth_config.json() if self.auth_config else None,
|
||||
}
|
||||
|
|
@ -179,13 +177,15 @@ class ArchConfig:
|
|||
if net_config := args_config.get('network_config', None):
|
||||
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
|
||||
|
||||
if bootloader_config := args_config.get('bootloader', None):
|
||||
arch_config.bootloader = Bootloader.from_arg(bootloader_config, args.skip_boot)
|
||||
|
||||
arch_config.uki = args_config.get('uki', False)
|
||||
|
||||
if args_config.get('uki') and (arch_config.bootloader is None or not arch_config.bootloader.has_uki_support()):
|
||||
arch_config.uki = False
|
||||
if bootloader_config_dict := args_config.get('bootloader_config', None):
|
||||
arch_config.bootloader_config = BootloaderConfiguration.parse_arg(bootloader_config_dict, args.skip_boot)
|
||||
# DEPRECATED: separate bootloader and uki fields (backward compatibility)
|
||||
elif bootloader_str := args_config.get('bootloader', None):
|
||||
bootloader = Bootloader.from_arg(bootloader_str, args.skip_boot)
|
||||
uki = args_config.get('uki', False)
|
||||
if uki and not bootloader.has_uki_support():
|
||||
uki = False
|
||||
arch_config.bootloader_config = BootloaderConfiguration(bootloader=bootloader, uki=uki, removable=False)
|
||||
|
||||
# deprecated: backwards compatibility
|
||||
audio_config_args = args_config.get('audio_config', None)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
import textwrap
|
||||
from typing import override
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.curses_menu import SelectMenu
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
from archinstall.tui.types import Alignment, FrameProperties, Orientation
|
||||
|
||||
from ..args import arch_config_handler
|
||||
from ..hardware import SysInfo
|
||||
from ..menu.abstract_menu import AbstractSubMenu
|
||||
from ..models.bootloader import Bootloader, BootloaderConfiguration
|
||||
|
||||
|
||||
class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
|
||||
def __init__(
|
||||
self,
|
||||
bootloader_conf: BootloaderConfiguration,
|
||||
):
|
||||
self._bootloader_conf = bootloader_conf
|
||||
menu_options = self._define_menu_options()
|
||||
|
||||
self._item_group = MenuItemGroup(menu_options, sort_items=False, checkmarks=True)
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._bootloader_conf,
|
||||
allow_reset=False,
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
bootloader = self._bootloader_conf.bootloader
|
||||
has_uefi = SysInfo.has_uefi()
|
||||
|
||||
# UKI availability
|
||||
uki_enabled = has_uefi and bootloader.has_uki_support()
|
||||
if not uki_enabled:
|
||||
self._bootloader_conf.uki = False
|
||||
|
||||
# Removable availability
|
||||
removable_enabled = has_uefi and bootloader.has_removable_support()
|
||||
if not removable_enabled:
|
||||
self._bootloader_conf.removable = False
|
||||
|
||||
return [
|
||||
MenuItem(
|
||||
text=tr('Bootloader'),
|
||||
action=self._select_bootloader,
|
||||
value=self._bootloader_conf.bootloader,
|
||||
preview_action=self._prev_bootloader,
|
||||
mandatory=True,
|
||||
key='bootloader',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Unified kernel images'),
|
||||
action=self._select_uki,
|
||||
value=self._bootloader_conf.uki,
|
||||
preview_action=self._prev_uki,
|
||||
key='uki',
|
||||
enabled=uki_enabled,
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Install to removable location'),
|
||||
action=self._select_removable,
|
||||
value=self._bootloader_conf.removable,
|
||||
preview_action=self._prev_removable,
|
||||
key='removable',
|
||||
enabled=removable_enabled,
|
||||
),
|
||||
]
|
||||
|
||||
def _prev_bootloader(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
return f'{tr("Bootloader")}: {item.value.value}'
|
||||
return None
|
||||
|
||||
def _prev_uki(self, item: MenuItem) -> str | None:
|
||||
uki_text = f'{tr("Unified kernel images")}'
|
||||
if item.value:
|
||||
return f'{uki_text}: {tr("Enabled")}'
|
||||
else:
|
||||
return f'{uki_text}: {tr("Disabled")}'
|
||||
|
||||
def _prev_removable(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
return tr('Will install to /EFI/BOOT/ (removable location)')
|
||||
return tr('Will install to standard location with NVRAM entry')
|
||||
|
||||
@override
|
||||
def run(
|
||||
self,
|
||||
additional_title: str | None = None,
|
||||
) -> BootloaderConfiguration:
|
||||
super().run(additional_title=additional_title)
|
||||
return self._bootloader_conf
|
||||
|
||||
def _select_bootloader(self, preset: Bootloader | None) -> Bootloader | None:
|
||||
bootloader = ask_for_bootloader(preset)
|
||||
|
||||
if bootloader:
|
||||
# Update UKI option based on bootloader
|
||||
uki_item = self._menu_item_group.find_by_key('uki')
|
||||
if not SysInfo.has_uefi() or not bootloader.has_uki_support():
|
||||
uki_item.enabled = False
|
||||
uki_item.value = False
|
||||
self._bootloader_conf.uki = False
|
||||
else:
|
||||
uki_item.enabled = True
|
||||
|
||||
# Update removable option based on bootloader
|
||||
removable_item = self._menu_item_group.find_by_key('removable')
|
||||
if not SysInfo.has_uefi() or not bootloader.has_removable_support():
|
||||
removable_item.enabled = False
|
||||
removable_item.value = False
|
||||
self._bootloader_conf.removable = False
|
||||
else:
|
||||
removable_item.enabled = True
|
||||
|
||||
return bootloader
|
||||
|
||||
def _select_uki(self, preset: bool) -> bool:
|
||||
prompt = tr('Would you like to use unified kernel images?') + '\n'
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
result = SelectMenu[bool](
|
||||
group,
|
||||
header=prompt,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.item() == MenuItem.yes()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
def _select_removable(self, preset: bool) -> bool:
|
||||
prompt = (
|
||||
tr('Would you like to install the bootloader to the default removable media search location?')
|
||||
+ '\n\n'
|
||||
+ tr('This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:')
|
||||
+ '\n\n • '
|
||||
+ tr('USB drives or other portable external media.')
|
||||
+ '\n • '
|
||||
+ tr('Systems where you want the disk to be bootable on any computer.')
|
||||
+ '\n • '
|
||||
+ tr('Firmware that does not properly support NVRAM boot entries.')
|
||||
+ '\n\n'
|
||||
+ tr(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
This is NOT recommended if none of the above apply, as it makes installing multiple
|
||||
EFI bootloaders on the same disk more challenging, and it overwrites whatever bootloader
|
||||
was previously installed on the default removable media search location, if any.
|
||||
"""
|
||||
)
|
||||
)
|
||||
+ '\n'
|
||||
)
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
result = SelectMenu[bool](
|
||||
group,
|
||||
header=prompt,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.item() == MenuItem.yes()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
||||
options = []
|
||||
hidden_options = []
|
||||
default = None
|
||||
header = None
|
||||
|
||||
if arch_config_handler.args.skip_boot:
|
||||
default = Bootloader.NO_BOOTLOADER
|
||||
else:
|
||||
hidden_options += [Bootloader.NO_BOOTLOADER]
|
||||
|
||||
if not SysInfo.has_uefi():
|
||||
options += [Bootloader.Grub, Bootloader.Limine]
|
||||
if not default:
|
||||
default = Bootloader.Grub
|
||||
header = tr('UEFI is not detected and some options are disabled')
|
||||
else:
|
||||
options += [b for b in Bootloader if b not in hidden_options]
|
||||
if not default:
|
||||
default = Bootloader.Systemd
|
||||
|
||||
items = [MenuItem(o.value, value=o) for o in options]
|
||||
group = MenuItemGroup(items)
|
||||
group.set_default_by_value(default)
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
result = SelectMenu[Bootloader](
|
||||
group,
|
||||
header=header,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(tr('Bootloader')),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
|
@ -12,6 +12,7 @@ from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
|||
from .applications.application_menu import ApplicationMenu
|
||||
from .args import ArchConfig
|
||||
from .authentication.authentication_menu import AuthenticationMenu
|
||||
from .bootloader.bootloader_menu import BootloaderMenu
|
||||
from .configuration import save_config
|
||||
from .hardware import SysInfo
|
||||
from .interactions.general_conf import (
|
||||
|
|
@ -22,11 +23,11 @@ from .interactions.general_conf import (
|
|||
ask_ntp,
|
||||
)
|
||||
from .interactions.network_menu import ask_to_configure_network
|
||||
from .interactions.system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_kernel
|
||||
from .interactions.system_conf import ask_for_swap, select_kernel
|
||||
from .locale.locale_menu import LocaleMenu
|
||||
from .menu.abstract_menu import CONFIG_KEY, AbstractMenu
|
||||
from .mirrors import MirrorMenu
|
||||
from .models.bootloader import Bootloader
|
||||
from .models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from .models.locale import LocaleConfiguration
|
||||
from .models.mirrors import MirrorConfiguration
|
||||
from .models.network import NetworkConfiguration, NicType
|
||||
|
|
@ -86,19 +87,10 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
),
|
||||
MenuItem(
|
||||
text=tr('Bootloader'),
|
||||
value=Bootloader.get_default(),
|
||||
action=self._select_bootloader,
|
||||
preview_action=self._prev_bootloader,
|
||||
mandatory=True,
|
||||
key='bootloader',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Unified kernel images'),
|
||||
value=False,
|
||||
enabled=SysInfo.has_uefi(),
|
||||
action=ask_for_uki,
|
||||
preview_action=self._prev_uki,
|
||||
key='uki',
|
||||
value=BootloaderConfiguration.get_default(),
|
||||
action=self._select_bootloader_config,
|
||||
preview_action=self._prev_bootloader_config,
|
||||
key='bootloader_config',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Hostname'),
|
||||
|
|
@ -379,13 +371,6 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
return output
|
||||
return None
|
||||
|
||||
def _prev_uki(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
output = f'{tr("Unified kernel images")}: '
|
||||
output += tr('Enabled') if item.value else tr('Disabled')
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_hostname(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
return f'{tr("Hostname")}: {item.value}'
|
||||
|
|
@ -402,9 +387,10 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
return f'{tr("Kernel")}: {kernel}'
|
||||
return None
|
||||
|
||||
def _prev_bootloader(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
return f'{tr("Bootloader")}: {item.value.value}'
|
||||
def _prev_bootloader_config(self, item: MenuItem) -> str | None:
|
||||
bootloader_config: BootloaderConfiguration | None = item.value
|
||||
if bootloader_config:
|
||||
return bootloader_config.preview()
|
||||
return None
|
||||
|
||||
def _validate_bootloader(self) -> str | None:
|
||||
|
|
@ -418,16 +404,18 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
XXX: The caller is responsible for wrapping the string with the translation
|
||||
shim if necessary.
|
||||
"""
|
||||
bootloader: Bootloader | None = None
|
||||
bootloader_config: BootloaderConfiguration | None = None
|
||||
root_partition: PartitionModification | None = None
|
||||
boot_partition: PartitionModification | None = None
|
||||
efi_partition: PartitionModification | None = None
|
||||
|
||||
bootloader = self._item_group.find_by_key('bootloader').value
|
||||
bootloader_config = self._item_group.find_by_key('bootloader_config').value
|
||||
|
||||
if bootloader == Bootloader.NO_BOOTLOADER:
|
||||
if not bootloader_config or bootloader_config.bootloader == Bootloader.NO_BOOTLOADER:
|
||||
return None
|
||||
|
||||
bootloader = bootloader_config.bootloader
|
||||
|
||||
if disk_config := self._item_group.find_by_key('disk_config').value:
|
||||
for layout in disk_config.device_modifications:
|
||||
if root_partition := layout.get_root_partition():
|
||||
|
|
@ -501,18 +489,16 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
return disk_config
|
||||
|
||||
def _select_bootloader(self, preset: Bootloader | None) -> Bootloader | None:
|
||||
bootloader = ask_for_bootloader(preset)
|
||||
def _select_bootloader_config(
|
||||
self,
|
||||
preset: BootloaderConfiguration | None = None,
|
||||
) -> BootloaderConfiguration | None:
|
||||
if preset is None:
|
||||
preset = BootloaderConfiguration.get_default()
|
||||
|
||||
if bootloader:
|
||||
uki = self._item_group.find_by_key('uki')
|
||||
if not SysInfo.has_uefi() or not bootloader.has_uki_support():
|
||||
uki.value = False
|
||||
uki.enabled = False
|
||||
else:
|
||||
uki.enabled = True
|
||||
bootloader_config = BootloaderMenu(preset).run()
|
||||
|
||||
return bootloader
|
||||
return bootloader_config
|
||||
|
||||
def _select_profile(self, current_profile: ProfileConfiguration | None) -> ProfileConfiguration | None:
|
||||
from .profile.profile_menu import ProfileMenu
|
||||
|
|
|
|||
|
|
@ -1268,6 +1268,7 @@ class Installer:
|
|||
boot_partition: PartitionModification,
|
||||
root: PartitionModification | LvmVolume,
|
||||
efi_partition: PartitionModification | None,
|
||||
bootloader_removable: bool = False,
|
||||
) -> None:
|
||||
debug('Installing grub bootloader')
|
||||
|
||||
|
|
@ -1311,9 +1312,11 @@ class Installer:
|
|||
f'--efi-directory={efi_partition.mountpoint}',
|
||||
*boot_dir_arg,
|
||||
'--bootloader-id=GRUB',
|
||||
'--removable',
|
||||
]
|
||||
|
||||
if bootloader_removable:
|
||||
add_options.append('--removable')
|
||||
|
||||
command.extend(add_options)
|
||||
|
||||
try:
|
||||
|
|
@ -1354,6 +1357,7 @@ class Installer:
|
|||
efi_partition: PartitionModification | None,
|
||||
root: PartitionModification | LvmVolume,
|
||||
uki_enabled: bool = False,
|
||||
bootloader_removable: bool = False,
|
||||
) -> None:
|
||||
debug('Installing Limine bootloader')
|
||||
|
||||
|
|
@ -1376,22 +1380,22 @@ class Installer:
|
|||
info(f'Limine EFI partition: {efi_partition.dev_path}')
|
||||
|
||||
parent_dev_path = device_handler.get_parent_device_path(efi_partition.safe_dev_path)
|
||||
is_target_usb = (
|
||||
SysCommand(
|
||||
f'udevadm info --no-pager --query=property --property=ID_BUS --value --name={parent_dev_path}',
|
||||
).decode()
|
||||
== 'usb'
|
||||
)
|
||||
|
||||
try:
|
||||
efi_dir_path = self.target / efi_partition.mountpoint.relative_to('/') / 'EFI'
|
||||
efi_dir_path_target = efi_partition.mountpoint / 'EFI'
|
||||
if is_target_usb:
|
||||
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 / 'limine'
|
||||
efi_dir_path_target = efi_dir_path_target / 'limine'
|
||||
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'
|
||||
|
||||
efi_dir_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
|
@ -1400,13 +1404,11 @@ class Installer:
|
|||
except Exception as err:
|
||||
raise DiskError(f'Failed to install Limine in {self.target}{efi_partition.mountpoint}: {err}')
|
||||
|
||||
config_path = efi_dir_path / 'limine.conf'
|
||||
|
||||
hook_command = (
|
||||
f'/usr/bin/cp /usr/share/limine/BOOTIA32.EFI {efi_dir_path_target}/ && /usr/bin/cp /usr/share/limine/BOOTX64.EFI {efi_dir_path_target}/'
|
||||
)
|
||||
|
||||
if not is_target_usb:
|
||||
if not bootloader_removable:
|
||||
# Create EFI boot menu entry for Limine.
|
||||
try:
|
||||
with open('/sys/firmware/efi/fw_platform_size') as fw_platform_size:
|
||||
|
|
@ -1415,9 +1417,9 @@ class Installer:
|
|||
raise OSError(f'Could not open or read /sys/firmware/efi/fw_platform_size to determine EFI bitness: {err}')
|
||||
|
||||
if efi_bitness == '64':
|
||||
loader_path = '/EFI/limine/BOOTX64.EFI'
|
||||
loader_path = '\\EFI\\arch-limine\\BOOTX64.EFI'
|
||||
elif efi_bitness == '32':
|
||||
loader_path = '/EFI/limine/BOOTIA32.EFI'
|
||||
loader_path = '\\EFI\\arch-limine\\BOOTIA32.EFI'
|
||||
else:
|
||||
raise ValueError(f'EFI bitness is neither 32 nor 64 bits. Found "{efi_bitness}".')
|
||||
|
||||
|
|
@ -1428,7 +1430,7 @@ class Installer:
|
|||
f' --disk {parent_dev_path}'
|
||||
f' --part {efi_partition.partn}'
|
||||
' --label "Arch Linux Limine Bootloader"'
|
||||
f' --loader {loader_path}'
|
||||
f" --loader '{loader_path}'"
|
||||
' --unicode'
|
||||
' --verbose',
|
||||
)
|
||||
|
|
@ -1485,23 +1487,24 @@ class Installer:
|
|||
path_root = f'uuid({boot_partition.partuuid})'
|
||||
|
||||
for kernel in self.kernels:
|
||||
for variant in ('', '-fallback'):
|
||||
if uki_enabled:
|
||||
entry = [
|
||||
'protocol: efi',
|
||||
f'path: boot():/EFI/Linux/arch-{kernel}.efi',
|
||||
f'cmdline: {kernel_params}',
|
||||
]
|
||||
else:
|
||||
if uki_enabled:
|
||||
entry = [
|
||||
'protocol: efi',
|
||||
f'path: boot():/EFI/Linux/arch-{kernel}.efi',
|
||||
f'cmdline: {kernel_params}',
|
||||
]
|
||||
config_contents += f'\n/Arch Linux ({kernel})\n'
|
||||
config_contents += '\n'.join([f' {it}' for it in entry]) + '\n'
|
||||
else:
|
||||
for variant in ('', '-fallback'):
|
||||
entry = [
|
||||
'protocol: linux',
|
||||
f'path: {path_root}:/vmlinuz-{kernel}',
|
||||
f'cmdline: {kernel_params}',
|
||||
f'module_path: {path_root}:/initramfs-{kernel}{variant}.img',
|
||||
]
|
||||
|
||||
config_contents += f'\n/Arch Linux ({kernel}{variant})\n'
|
||||
config_contents += '\n'.join([f' {it}' for it in entry]) + '\n'
|
||||
config_contents += f'\n/Arch Linux ({kernel}{variant})\n'
|
||||
config_contents += '\n'.join([f' {it}' for it in entry]) + '\n'
|
||||
|
||||
config_path.write_text(config_contents)
|
||||
|
||||
|
|
@ -1612,16 +1615,18 @@ class Installer:
|
|||
if not self.mkinitcpio(['-P']):
|
||||
error('Error generating initramfs (continuing anyway)')
|
||||
|
||||
def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False) -> None:
|
||||
def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False) -> None:
|
||||
"""
|
||||
Adds a bootloader to the installation instance.
|
||||
Archinstall supports one of three types:
|
||||
* systemd-bootctl
|
||||
* grub
|
||||
* limine (beta)
|
||||
* limine
|
||||
* efistub (beta)
|
||||
|
||||
:param bootloader: Type of bootloader to be added
|
||||
:param uki_enabled: Whether to use unified kernel images
|
||||
:param bootloader_removable: Whether to install to removable media location (UEFI only, for GRUB and Limine)
|
||||
"""
|
||||
|
||||
for plugin in plugins.values():
|
||||
|
|
@ -1643,6 +1648,20 @@ class Installer:
|
|||
|
||||
info(f'Adding bootloader {bootloader.value} to {boot_partition.dev_path}')
|
||||
|
||||
# validate UKI support
|
||||
if uki_enabled and not bootloader.has_uki_support():
|
||||
warn(f'Bootloader {bootloader.value} does not support UKI; disabling.')
|
||||
uki_enabled = False
|
||||
|
||||
# validate removable bootloader option
|
||||
if bootloader_removable:
|
||||
if not SysInfo.has_uefi():
|
||||
warn('Removable install requested but system is not UEFI; disabling.')
|
||||
bootloader_removable = False
|
||||
elif not bootloader.has_removable_support():
|
||||
warn(f'Bootloader {bootloader.value} lacks removable support; disabling.')
|
||||
bootloader_removable = False
|
||||
|
||||
if uki_enabled:
|
||||
self._config_uki(root, efi_partition)
|
||||
|
||||
|
|
@ -1650,11 +1669,11 @@ class Installer:
|
|||
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)
|
||||
self._add_grub_bootloader(boot_partition, root, efi_partition, bootloader_removable)
|
||||
case Bootloader.Efistub:
|
||||
self._add_efistub_bootloader(boot_partition, root, uki_enabled)
|
||||
case Bootloader.Limine:
|
||||
self._add_limine_bootloader(boot_partition, efi_partition, root, uki_enabled)
|
||||
self._add_limine_bootloader(boot_partition, efi_partition, root, uki_enabled, bootloader_removable)
|
||||
|
||||
def add_additional_packages(self, packages: str | list[str]) -> None:
|
||||
return self.pacman.strap(packages)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from .general_conf import (
|
|||
)
|
||||
from .manage_users_conf import UserList, ask_for_additional_users
|
||||
from .network_menu import ManualNetworkConfig, ask_to_configure_network
|
||||
from .system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_driver, select_kernel
|
||||
from .system_conf import ask_for_swap, select_driver, select_kernel
|
||||
|
||||
__all__ = [
|
||||
'ManualNetworkConfig',
|
||||
|
|
@ -25,9 +25,7 @@ __all__ = [
|
|||
'ask_additional_packages_to_install',
|
||||
'ask_for_a_timezone',
|
||||
'ask_for_additional_users',
|
||||
'ask_for_bootloader',
|
||||
'ask_for_swap',
|
||||
'ask_for_uki',
|
||||
'ask_hostname',
|
||||
'ask_ntp',
|
||||
'ask_to_configure_network',
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
|||
from archinstall.tui.result import ResultType
|
||||
from archinstall.tui.types import Alignment, FrameProperties, FrameStyle, Orientation, PreviewStyle
|
||||
|
||||
from ..args import arch_config_handler
|
||||
from ..hardware import GfxDriver, SysInfo
|
||||
from ..models.bootloader import Bootloader
|
||||
|
||||
|
||||
def select_kernel(preset: list[str] = []) -> list[str]:
|
||||
|
|
@ -46,74 +44,6 @@ def select_kernel(preset: list[str] = []) -> list[str]:
|
|||
return result.get_values()
|
||||
|
||||
|
||||
def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
|
||||
# Systemd is UEFI only
|
||||
options = []
|
||||
hidden_options = []
|
||||
default = None
|
||||
header = None
|
||||
|
||||
if arch_config_handler.args.skip_boot:
|
||||
default = Bootloader.NO_BOOTLOADER
|
||||
else:
|
||||
hidden_options += [Bootloader.NO_BOOTLOADER]
|
||||
|
||||
if not SysInfo.has_uefi():
|
||||
options += [Bootloader.Grub, Bootloader.Limine]
|
||||
if not default:
|
||||
default = Bootloader.Grub
|
||||
header = tr('UEFI is not detected and some options are disabled')
|
||||
else:
|
||||
options += [b for b in Bootloader if b not in hidden_options]
|
||||
if not default:
|
||||
default = Bootloader.Systemd
|
||||
|
||||
items = [MenuItem(o.value, value=o) for o in options]
|
||||
group = MenuItemGroup(items)
|
||||
group.set_default_by_value(default)
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
result = SelectMenu[Bootloader](
|
||||
group,
|
||||
header=header,
|
||||
alignment=Alignment.CENTER,
|
||||
frame=FrameProperties.min(tr('Bootloader')),
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
def ask_for_uki(preset: bool = True) -> bool:
|
||||
prompt = tr('Would you like to use unified kernel images?') + '\n'
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
result = SelectMenu[bool](
|
||||
group,
|
||||
header=prompt,
|
||||
columns=2,
|
||||
orientation=Orientation.HORIZONTAL,
|
||||
alignment=Alignment.CENTER,
|
||||
allow_skip=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.item() == MenuItem.yes()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None) -> GfxDriver | None:
|
||||
"""
|
||||
Some what convoluted function, whose job is simple.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
from ..hardware import SysInfo
|
||||
from ..output import warn
|
||||
|
|
@ -21,6 +25,13 @@ class Bootloader(Enum):
|
|||
case _:
|
||||
return False
|
||||
|
||||
def has_removable_support(self) -> bool:
|
||||
match self:
|
||||
case Bootloader.Grub | Bootloader.Limine:
|
||||
return True
|
||||
case _:
|
||||
return False
|
||||
|
||||
def json(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
|
@ -48,3 +59,43 @@ class Bootloader(Enum):
|
|||
sys.exit(1)
|
||||
|
||||
return Bootloader(bootloader)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BootloaderConfiguration:
|
||||
bootloader: Bootloader
|
||||
uki: bool = False
|
||||
removable: bool = False
|
||||
|
||||
def json(self) -> dict[str, Any]:
|
||||
return {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable}
|
||||
|
||||
@classmethod
|
||||
def parse_arg(cls, config: dict[str, Any], skip_boot: bool) -> BootloaderConfiguration:
|
||||
bootloader = Bootloader.from_arg(config.get('bootloader', ''), skip_boot)
|
||||
uki = config.get('uki', False)
|
||||
removable = config.get('removable', False)
|
||||
return cls(bootloader=bootloader, uki=uki, removable=removable)
|
||||
|
||||
@classmethod
|
||||
def get_default(cls) -> BootloaderConfiguration:
|
||||
return cls(bootloader=Bootloader.get_default(), uki=False, removable=False)
|
||||
|
||||
def preview(self) -> str:
|
||||
text = f'{tr("Bootloader")}: {self.bootloader.value}'
|
||||
text += '\n'
|
||||
if SysInfo.has_uefi() and self.bootloader.has_uki_support():
|
||||
if self.uki:
|
||||
uki_string = tr('Enabled')
|
||||
else:
|
||||
uki_string = tr('Disabled')
|
||||
text += f'UKI: {uki_string}'
|
||||
text += '\n'
|
||||
if SysInfo.has_uefi() and self.bootloader.has_removable_support():
|
||||
if self.removable:
|
||||
removable_string = tr('Enabled')
|
||||
else:
|
||||
removable_string = tr('Disabled')
|
||||
text += f'{tr("Removable")}: {removable_string}'
|
||||
text += '\n'
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
return
|
||||
|
||||
disk_config = config.disk_config
|
||||
run_mkinitcpio = not config.uki
|
||||
run_mkinitcpio = not config.bootloader_config or not config.bootloader_config.uki
|
||||
locale_config = config.locale_config
|
||||
optional_repositories = config.mirror_config.optional_repositories if config.mirror_config else []
|
||||
mountpoint = disk_config.mountpoint if disk_config.mountpoint else mountpoint
|
||||
|
|
@ -99,11 +99,11 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
if config.swap:
|
||||
installation.setup_swap('zram')
|
||||
|
||||
if config.bootloader and config.bootloader != Bootloader.NO_BOOTLOADER:
|
||||
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
|
||||
if config.bootloader_config and config.bootloader_config.bootloader != Bootloader.NO_BOOTLOADER:
|
||||
if config.bootloader_config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
|
||||
installation.add_additional_packages('grub')
|
||||
|
||||
installation.add_bootloader(config.bootloader, config.uki)
|
||||
installation.add_bootloader(config.bootloader_config.bootloader, config.bootloader_config.uki, config.bootloader_config.removable)
|
||||
|
||||
# If user selected to copy the current ISO network configuration
|
||||
# Perform a copy of the config
|
||||
|
|
@ -155,7 +155,8 @@ def perform_installation(mountpoint: Path) -> None:
|
|||
snapshot_config = btrfs_options.snapshot_config if btrfs_options else None
|
||||
snapshot_type = snapshot_config.snapshot_type if snapshot_config else None
|
||||
if snapshot_type:
|
||||
installation.setup_btrfs_snapshot(snapshot_type, config.bootloader)
|
||||
bootloader = config.bootloader_config.bootloader if config.bootloader_config else None
|
||||
installation.setup_btrfs_snapshot(snapshot_type, bootloader)
|
||||
|
||||
# If the user provided custom commands to be run post-installation, execute them now.
|
||||
if cc := config.custom_commands:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ Key,Value(s),Description,Required
|
|||
additional-repositories,[ `multilib <https://wiki.archlinux.org/title/Official_repositories#multilib>`_!, `testing <https://wiki.archlinux.org/title/Official_repositories#Testing_repositories>`_ ],Enables one or more of the testing and multilib repositories before proceeding with installation,No
|
||||
archinstall-language,`lang <https://github.com/archlinux/archinstall/blob/master/archinstall/locales/languages.json>`__,Sets the TUI language used *(make sure to use the ``lang`` value not the ``abbr``)*,No
|
||||
audio_config,`pipewire <https://wiki.archlinux.org/title/PipeWire>`_!, `pulseaudio <https://wiki.archlinux.org/title/PulseAudio>`_,Audioserver to be installed,No
|
||||
bootloader,`Systemd-boot <https://wiki.archlinux.org/title/Systemd-boot>`_!, `grub <https://wiki.archlinux.org/title/GRUB>`_,Bootloader to be installed *(grub being mandatory on BIOS machines)*,Yes
|
||||
bootloader_config,"{ bootloader: `Systemd-boot <https://wiki.archlinux.org/title/Systemd-boot>`_!, `grub <https://wiki.archlinux.org/title/GRUB>`_!, `limine <https://wiki.archlinux.org/title/Limine>`_!, uki: ``true``/``false``!, removable: ``true``/``false`` }","Bootloader configuration. ``bootloader`` selects which bootloader to install *(grub/limine mandatory on BIOS)*. ``uki`` enables unified kernel images *(UEFI only!, systemd-boot/limine only)*. ``removable`` installs to default removable media path /EFI/BOOT/ instead of NVRAM *(UEFI only!, grub/limine only)*",Yes
|
||||
debug,``true``!, ``false``,Enables debug output,No
|
||||
disk_config,*Read more under* :ref:`disk config`,Contains the desired disk setup to be used during installation,No
|
||||
disk_encryption,*Read more about under* :ref:`disk encryption`,Parameters for disk encryption applied on top of ``disk_config``,No
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 2.
|
|
|
@ -66,6 +66,11 @@ The contents of :code:`https://domain.lan/config.json`:
|
|||
"additional-repositories": [],
|
||||
"archinstall-language": "English",
|
||||
"audio_config": null,
|
||||
"bootloader_config": {
|
||||
"bootloader": "Systemd-boot",
|
||||
"uki": false,
|
||||
"removable": false
|
||||
},
|
||||
"bootloader": "Systemd-boot",
|
||||
"debug": false,
|
||||
"disk_config": {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@
|
|||
"audio_config": {
|
||||
"audio": "pipewire"
|
||||
},
|
||||
"bootloader": "Systemd-boot",
|
||||
"bootloader_config": {
|
||||
"bootloader": "Systemd-boot",
|
||||
"uki": false,
|
||||
"removable": false
|
||||
},
|
||||
"debug": false,
|
||||
"disk_config": {
|
||||
"config_type": "default_layout",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@
|
|||
"audio_config": {
|
||||
"audio": "pipewire"
|
||||
},
|
||||
"bootloader": "Systemd-boot",
|
||||
"bootloader_config": {
|
||||
"bootloader": "Systemd-boot",
|
||||
"uki": false,
|
||||
"removable": false
|
||||
},
|
||||
"services": [
|
||||
"service_1",
|
||||
"service_2"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments
|
|||
from archinstall.lib.hardware import GfxDriver
|
||||
from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration
|
||||
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
|
||||
from archinstall.lib.models.bootloader import Bootloader
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType
|
||||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.mirrors import CustomRepository, CustomServer, MirrorConfiguration, MirrorRegion, SignCheck, SignOption
|
||||
|
|
@ -214,8 +214,11 @@ def test_config_file_parsing(
|
|||
),
|
||||
],
|
||||
),
|
||||
bootloader=Bootloader.Systemd,
|
||||
uki=False,
|
||||
bootloader_config=BootloaderConfiguration(
|
||||
bootloader=Bootloader.Systemd,
|
||||
uki=False,
|
||||
removable=False,
|
||||
),
|
||||
hostname='archy',
|
||||
kernels=['linux-zen'],
|
||||
ntp=True,
|
||||
|
|
|
|||
Loading…
Reference in New Issue