Skip to content

Commit f5af338

Browse files
committed
Add dialog to install EFI bootloader to removable location
This is just for GRUB and Limine for now.
1 parent 803ff42 commit f5af338

5 files changed

Lines changed: 61 additions & 14 deletions

File tree

archinstall/lib/args.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class ArchConfig:
6565
mirror_config: MirrorConfiguration | None = None
6666
network_config: NetworkConfiguration | None = None
6767
bootloader: Bootloader | None = None
68+
bootloader_removable: bool = False
6869
uki: bool = False
6970
app_config: ApplicationConfiguration | None = None
7071
auth_config: AuthenticationConfiguration | None = None
@@ -103,6 +104,7 @@ def safe_json(self) -> dict[str, Any]:
103104
'hostname': self.hostname,
104105
'kernels': self.kernels,
105106
'uki': self.uki,
107+
'bootloader_removable': self.bootloader_removable,
106108
'ntp': self.ntp,
107109
'packages': self.packages,
108110
'parallel_downloads': self.parallel_downloads,
@@ -183,6 +185,7 @@ def from_config(cls, args_config: dict[str, Any], args: Arguments) -> 'ArchConfi
183185
arch_config.bootloader = Bootloader.from_arg(bootloader_config, args.skip_boot)
184186

185187
arch_config.uki = args_config.get('uki', False)
188+
arch_config.bootloader_removable = args_config.get('bootloader_removable', False)
186189

187190
if args_config.get('uki') and (arch_config.bootloader is None or not arch_config.bootloader.has_uki_support()):
188191
arch_config.uki = False

archinstall/lib/global_menu.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
ask_ntp,
2323
)
2424
from .interactions.network_menu import ask_to_configure_network
25-
from .interactions.system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_kernel
25+
from .interactions.system_conf import ask_for_bootloader, ask_for_bootloader_removable, ask_for_swap, ask_for_uki, select_kernel
2626
from .locale.locale_menu import LocaleMenu
2727
from .menu.abstract_menu import CONFIG_KEY, AbstractMenu
2828
from .mirrors import MirrorMenu
@@ -512,6 +512,15 @@ def _select_bootloader(self, preset: Bootloader | None) -> Bootloader | None:
512512
else:
513513
uki.enabled = True
514514

515+
# If GRUB or Limine is selected on UEFI, immediately ask about removable installation
516+
if bootloader in [Bootloader.Grub, Bootloader.Limine] and SysInfo.has_uefi():
517+
current_removable = self._arch_config.bootloader_removable
518+
removable = ask_for_bootloader_removable(current_removable)
519+
self._arch_config.bootloader_removable = removable
520+
else:
521+
# Reset removable flag for other bootloaders
522+
self._arch_config.bootloader_removable = False
523+
515524
return bootloader
516525

517526
def _select_profile(self, current_profile: ProfileConfiguration | None) -> ProfileConfiguration | None:

archinstall/lib/installer.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,7 @@ def _add_grub_bootloader(
12601260
boot_partition: PartitionModification,
12611261
root: PartitionModification | LvmVolume,
12621262
efi_partition: PartitionModification | None,
1263+
bootloader_removable: bool = False,
12631264
) -> None:
12641265
debug('Installing grub bootloader')
12651266

@@ -1303,9 +1304,11 @@ def _add_grub_bootloader(
13031304
f'--efi-directory={efi_partition.mountpoint}',
13041305
*boot_dir_arg,
13051306
'--bootloader-id=GRUB',
1306-
'--removable',
13071307
]
13081308

1309+
if bootloader_removable:
1310+
add_options.append('--removable')
1311+
13091312
command.extend(add_options)
13101313

13111314
try:
@@ -1346,6 +1349,7 @@ def _add_limine_bootloader(
13461349
efi_partition: PartitionModification | None,
13471350
root: PartitionModification | LvmVolume,
13481351
uki_enabled: bool = False,
1352+
bootloader_removable: bool = False,
13491353
) -> None:
13501354
debug('Installing Limine bootloader')
13511355

@@ -1368,17 +1372,11 @@ def _add_limine_bootloader(
13681372
info(f'Limine EFI partition: {efi_partition.dev_path}')
13691373

13701374
parent_dev_path = device_handler.get_parent_device_path(efi_partition.safe_dev_path)
1371-
is_target_usb = (
1372-
SysCommand(
1373-
f'udevadm info --no-pager --query=property --property=ID_BUS --value --name={parent_dev_path}',
1374-
).decode()
1375-
== 'usb'
1376-
)
13771375

13781376
try:
13791377
efi_dir_path = self.target / efi_partition.mountpoint.relative_to('/') / 'EFI'
13801378
efi_dir_path_target = efi_partition.mountpoint / 'EFI'
1381-
if is_target_usb:
1379+
if bootloader_removable:
13821380
efi_dir_path = efi_dir_path / 'BOOT'
13831381
efi_dir_path_target = efi_dir_path_target / 'BOOT'
13841382
else:
@@ -1398,7 +1396,7 @@ def _add_limine_bootloader(
13981396
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}/'
13991397
)
14001398

1401-
if not is_target_usb:
1399+
if not bootloader_removable:
14021400
# Create EFI boot menu entry for Limine.
14031401
try:
14041402
with open('/sys/firmware/efi/fw_platform_size') as fw_platform_size:
@@ -1604,7 +1602,7 @@ def _config_uki(
16041602
if not self.mkinitcpio(['-P']):
16051603
error('Error generating initramfs (continuing anyway)')
16061604

1607-
def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False) -> None:
1605+
def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False) -> None:
16081606
"""
16091607
Adds a bootloader to the installation instance.
16101608
Archinstall supports one of three types:
@@ -1614,6 +1612,8 @@ def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False) -> N
16141612
* efistub (beta)
16151613
16161614
:param bootloader: Type of bootloader to be added
1615+
:param uki_enabled: Whether to use unified kernel images
1616+
:param bootloader_removable: Whether to install to removable media location (UEFI only, for GRUB and Limine)
16171617
"""
16181618

16191619
for plugin in plugins.values():
@@ -1642,11 +1642,11 @@ def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False) -> N
16421642
case Bootloader.Systemd:
16431643
self._add_systemd_bootloader(boot_partition, root, efi_partition, uki_enabled)
16441644
case Bootloader.Grub:
1645-
self._add_grub_bootloader(boot_partition, root, efi_partition)
1645+
self._add_grub_bootloader(boot_partition, root, efi_partition, bootloader_removable)
16461646
case Bootloader.Efistub:
16471647
self._add_efistub_bootloader(boot_partition, root, uki_enabled)
16481648
case Bootloader.Limine:
1649-
self._add_limine_bootloader(boot_partition, efi_partition, root, uki_enabled)
1649+
self._add_limine_bootloader(boot_partition, efi_partition, root, uki_enabled, bootloader_removable)
16501650

16511651
def add_additional_packages(self, packages: str | list[str]) -> None:
16521652
return self.pacman.strap(packages)

archinstall/lib/interactions/system_conf.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,41 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
9090
raise ValueError('Unhandled result type')
9191

9292

93+
def ask_for_bootloader_removable(preset: bool = False) -> bool:
94+
prompt = (
95+
tr('Would you like to install the bootloader to a removable media location?')
96+
+ '\n\n'
97+
+ tr('This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:')
98+
+ '\n'
99+
+ tr(' • USB drives and external media')
100+
+ '\n'
101+
+ tr(' • Systems where you want the disk to be bootable on any computer')
102+
+ '\n'
103+
+ tr(' • Firmware that does not properly support NVRAM boot entries')
104+
+ '\n'
105+
)
106+
107+
group = MenuItemGroup.yes_no()
108+
group.set_focus_by_value(preset)
109+
110+
result = SelectMenu[bool](
111+
group,
112+
header=prompt,
113+
columns=2,
114+
orientation=Orientation.HORIZONTAL,
115+
alignment=Alignment.CENTER,
116+
allow_skip=True,
117+
).run()
118+
119+
match result.type_:
120+
case ResultType.Skip:
121+
return preset
122+
case ResultType.Selection:
123+
return result.item() == MenuItem.yes()
124+
case ResultType.Reset:
125+
raise ValueError('Unhandled result type')
126+
127+
93128
def ask_for_uki(preset: bool = True) -> bool:
94129
prompt = tr('Would you like to use unified kernel images?') + '\n'
95130

archinstall/scripts/guided.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def perform_installation(mountpoint: Path) -> None:
103103
if config.bootloader == Bootloader.Grub and SysInfo.has_uefi():
104104
installation.add_additional_packages('grub')
105105

106-
installation.add_bootloader(config.bootloader, config.uki)
106+
installation.add_bootloader(config.bootloader, config.uki, config.bootloader_removable)
107107

108108
# If user selected to copy the current ISO network configuration
109109
# Perform a copy of the config

0 commit comments

Comments
 (0)