From 7cf3527aca8cf0619324faa8113da9da9ccf96a2 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Wed, 12 Aug 2026 14:03:32 +0200 Subject: [PATCH 01/10] feat(#4466): support `optdepends` of `linux-firmware` Signed-off-by: h8d13 --- archinstall/lib/args.py | 6 ++++++ archinstall/lib/general/system_menu.py | 28 ++++++++++++++++++++++++- archinstall/lib/global_menu.py | 15 ++++++++++++- archinstall/lib/installer.py | 5 +++++ archinstall/lib/models/package_types.py | 17 +++++++++++++++ archinstall/scripts/guided.py | 1 + 6 files changed, 70 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index feeaa8431e..eca3b6c899 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -88,6 +88,7 @@ class ArchConfigType(StrEnum): ENCRYPTION_PASSWORD = auto() HOSTNAME = auto() KERNELS = auto() + FIRMWARE_OPTDEPS = auto() NTP = auto() TIMEZONE = auto() SERVICES = auto() @@ -166,6 +167,7 @@ class ArchConfig: swap: ZramConfiguration | None = None hostname: str = 'archlinux' kernels: list[str] = field(default_factory=lambda: [DEFAULT_KERNEL.value]) + firmware_optdeps: list[str] = field(default_factory=list) ntp: bool = True packages: list[str] = field(default_factory=list) pacman_config: PacmanConfiguration = field(default_factory=PacmanConfiguration) @@ -211,6 +213,7 @@ def plain_cfg(self) -> dict[ArchConfigType, str | list[str] | bool]: return { ArchConfigType.HOSTNAME: self.hostname, ArchConfigType.KERNELS: self.kernels, + ArchConfigType.FIRMWARE_OPTDEPS: self.firmware_optdeps, ArchConfigType.NTP: self.ntp, ArchConfigType.TIMEZONE: self.timezone, ArchConfigType.SERVICES: self.services, @@ -326,6 +329,9 @@ def from_config(cls, args_config: dict[str, Any], args: Arguments) -> Self: if kernels := args_config.get('kernels', []): arch_config.kernels = kernels + if firmware_optdeps := args_config.get('firmware_optdeps', []): + arch_config.firmware_optdeps = firmware_optdeps + arch_config.ntp = args_config.get('ntp', True) if packages := args_config.get('packages', []): diff --git a/archinstall/lib/general/system_menu.py b/archinstall/lib/general/system_menu.py index d2de676fe1..bd92d1ea89 100644 --- a/archinstall/lib/general/system_menu.py +++ b/archinstall/lib/general/system_menu.py @@ -3,7 +3,7 @@ from archinstall.lib.hardware import GfxDriver, SysInfo from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.models.application import ZramAlgorithm, ZramConfiguration -from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel +from archinstall.lib.models.package_types import DEFAULT_KERNEL, FirmwareOptdep, Kernel from archinstall.lib.translationhandler import tr from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -40,6 +40,32 @@ async def select_kernel(preset: list[Kernel] | None = None) -> list[Kernel]: return result.get_values() +async def select_firmware_optdeps(preset: list[FirmwareOptdep] = []) -> list[FirmwareOptdep]: + """ + Asks the user which of linux-firmware's optional dependencies to install. + + :return: The selected firmware packages + :rtype: list[FirmwareOptdep] + """ + group = MenuItemGroup.from_enum(FirmwareOptdep, sort_items=True, preset=preset) + + result = await Selection[FirmwareOptdep]( + group, + header=tr('Select optional firmware to install (none are pulled in by linux-firmware)'), + allow_skip=True, + allow_reset=True, + multi=True, + ).show() + + match result.type_: + case ResultType.Skip: + return preset + case ResultType.Reset: + return [] + case ResultType.Selection: + return result.get_values() + + async def select_uki(preset: bool = True) -> bool: prompt = tr('Would you like to use unified kernel images?') + '\n' diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 87352786b4..7e5182cdcb 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -9,7 +9,7 @@ from archinstall.lib.configuration import save_config from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu from archinstall.lib.general.general_menu import select_hostname, select_ntp, select_timezone -from archinstall.lib.general.system_menu import select_kernel, select_swap +from archinstall.lib.general.system_menu import select_firmware_optdeps, select_kernel, select_swap from archinstall.lib.hardware import SysInfo from archinstall.lib.locale.locale_menu import LocaleMenu from archinstall.lib.menu.abstract_menu import AbstractMenu, SpecialMenuKey @@ -110,6 +110,13 @@ def _get_menu_options(self) -> list[MenuItem]: mandatory=True, key='kernels', ), + MenuItem( + text=tr('Optional firmware'), + value=[], + action=select_firmware_optdeps, + preview_action=self._prev_firmware_optdeps, + key='firmware_optdeps', + ), MenuItem( text=tr('Hostname'), value='archlinux', @@ -438,6 +445,12 @@ def _prev_kernel(self, item: MenuItem) -> str | None: return f'{tr("Kernel")}: {kernel}' return None + def _prev_firmware_optdeps(self, item: MenuItem) -> str | None: + if item.value: + firmware = ', '.join(item.value) + return f'{tr("Optional firmware")}: {firmware}' + return None + def _prev_bootloader_config(self, item: MenuItem) -> str | None: bootloader_config: BootloaderConfiguration | None = item.value if bootloader_config: diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index fc3be4f76b..db0884ef55 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -85,6 +85,7 @@ def __init__( disk_config: DiskLayoutConfiguration, base_packages: list[str] | None = None, kernels: list[str] | None = None, + firmware: list[str] | None = None, silent: bool = False, ) -> None: """ @@ -107,6 +108,10 @@ def __init__( for kernel in self.kernels: self._base_packages.append(kernel) + # linux-firmware pulls in its hard deps only, so the optional ones are + # strapped alongside base or their blobs never reach the target + self._base_packages.extend(firmware or []) + # If using accessibility tools in the live environment, append those to the packages list if accessibility_tools_in_use(): self._base_packages.extend(__accessibility_packages__) diff --git a/archinstall/lib/models/package_types.py b/archinstall/lib/models/package_types.py index fa44668b06..bfbb6ffee3 100644 --- a/archinstall/lib/models/package_types.py +++ b/archinstall/lib/models/package_types.py @@ -12,3 +12,20 @@ class Kernel(StrEnum): DEFAULT_KERNEL: Final = Kernel.LINUX + + +class FirmwareOptdep(StrEnum): + """linux-firmware's optional dependencies. + + The metapackage pulls in its hard deps only, so these blobs never reach the + target: hardware that needs one (a Marvell wifi card, a Mellanox NIC) comes + up without firmware and there is nothing in the installer that says so. + Mirrors `pacman -Si linux-firmware` optdepends. + """ + + LIQUIDIO = 'linux-firmware-liquidio' + MARVELL = 'linux-firmware-marvell' + MELLANOX = 'linux-firmware-mellanox' + NFP = 'linux-firmware-nfp' + QCOM = 'linux-firmware-qcom' + QLOGIC = 'linux-firmware-qlogic' diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 9e04d27e61..6a4e6185d1 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -80,6 +80,7 @@ def perform_installation( mountpoint, disk_config, kernels=config.kernels, + firmware=config.firmware_optdeps, silent=arch_config_handler.args.silent, ) as installation: # Mount all the drives to the desired mountpoint From 69488e69e0a3388c3e3173e0d5ee9892721b4c0d Mon Sep 17 00:00:00 2001 From: h8d13 Date: Wed, 12 Aug 2026 14:06:58 +0200 Subject: [PATCH 02/10] chore(fmt): shorten comments Signed-off-by: h8d13 --- archinstall/lib/installer.py | 3 +-- archinstall/lib/models/package_types.py | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index db0884ef55..7ba3522800 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -108,8 +108,7 @@ def __init__( for kernel in self.kernels: self._base_packages.append(kernel) - # linux-firmware pulls in its hard deps only, so the optional ones are - # strapped alongside base or their blobs never reach the target + # Optional firmware is strapped with base so the blobs are in place before the initramfs is generated self._base_packages.extend(firmware or []) # If using accessibility tools in the live environment, append those to the packages list diff --git a/archinstall/lib/models/package_types.py b/archinstall/lib/models/package_types.py index bfbb6ffee3..30921ffbf0 100644 --- a/archinstall/lib/models/package_types.py +++ b/archinstall/lib/models/package_types.py @@ -15,12 +15,11 @@ class Kernel(StrEnum): class FirmwareOptdep(StrEnum): - """linux-firmware's optional dependencies. + """ + The optional dependencies of linux-firmware (pacman -Si linux-firmware). - The metapackage pulls in its hard deps only, so these blobs never reach the - target: hardware that needs one (a Marvell wifi card, a Mellanox NIC) comes - up without firmware and there is nothing in the installer that says so. - Mirrors `pacman -Si linux-firmware` optdepends. + The metapackage installs its hard dependencies only, so these blobs are + never present on the target unless they are requested explicitly. """ LIQUIDIO = 'linux-firmware-liquidio' From 1bcb2a489cca03716e3ce70c95db96fdee16cf79 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Wed, 12 Aug 2026 14:09:57 +0200 Subject: [PATCH 03/10] misc(fmt) Signed-off-by: h8d13 --- archinstall/lib/models/package_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/models/package_types.py b/archinstall/lib/models/package_types.py index 30921ffbf0..a78829690d 100644 --- a/archinstall/lib/models/package_types.py +++ b/archinstall/lib/models/package_types.py @@ -16,7 +16,7 @@ class Kernel(StrEnum): class FirmwareOptdep(StrEnum): """ - The optional dependencies of linux-firmware (pacman -Si linux-firmware). + The optional dependencies of linux-firmware. The metapackage installs its hard dependencies only, so these blobs are never present on the target unless they are requested explicitly. From 30fafd9cf5fc3445330d6922696ad3bafc0107f0 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Wed, 12 Aug 2026 14:17:49 +0200 Subject: [PATCH 04/10] idiomatic types Signed-off-by: h8d13 --- archinstall/lib/installer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 7ba3522800..04aed28f6f 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -85,7 +85,7 @@ def __init__( disk_config: DiskLayoutConfiguration, base_packages: list[str] | None = None, kernels: list[str] | None = None, - firmware: list[str] | None = None, + firmware: list[str] = [], silent: bool = False, ) -> None: """ @@ -109,7 +109,7 @@ def __init__( self._base_packages.append(kernel) # Optional firmware is strapped with base so the blobs are in place before the initramfs is generated - self._base_packages.extend(firmware or []) + self._base_packages.extend(firmware) # If using accessibility tools in the live environment, append those to the packages list if accessibility_tools_in_use(): From 760030909d52c2a28497c51fecf9e4febba39e91 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Wed, 12 Aug 2026 14:23:29 +0200 Subject: [PATCH 05/10] chore(mypy) Signed-off-by: h8d13 --- archinstall/lib/args.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index eca3b6c899..95f7a34d47 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -126,6 +126,8 @@ def text(self) -> str: return tr('Hostname') case ArchConfigType.KERNELS: return tr('Kernels') + case ArchConfigType.FIRMWARE_OPTDEPS: + return tr('Optional firmware') case ArchConfigType.NTP: return tr('Automatic time sync (NTP)') case ArchConfigType.TIMEZONE: From e47eab2a2752ed0d80a6b2098e912492db83a08c Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 29 Aug 2026 03:09:31 +0200 Subject: [PATCH 06/10] fb 29-8 Signed-off-by: h8d13 --- archinstall/lib/global_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 7e5182cdcb..4153675fdc 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -111,7 +111,7 @@ def _get_menu_options(self) -> list[MenuItem]: key='kernels', ), MenuItem( - text=tr('Optional firmware'), + text=tr('Additional firmware'), value=[], action=select_firmware_optdeps, preview_action=self._prev_firmware_optdeps, From 46a6b7c6de0d37242110aa66994f06699aded381 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 29 Aug 2026 03:16:53 +0200 Subject: [PATCH 07/10] modify preview text too Signed-off-by: h8d13 --- archinstall/lib/global_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 4153675fdc..3dac53fe65 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -448,7 +448,7 @@ def _prev_kernel(self, item: MenuItem) -> str | None: def _prev_firmware_optdeps(self, item: MenuItem) -> str | None: if item.value: firmware = ', '.join(item.value) - return f'{tr("Optional firmware")}: {firmware}' + return f'{tr("Additional firmware")}: {firmware}' return None def _prev_bootloader_config(self, item: MenuItem) -> str | None: From c893608674fd95182f4b9f0980323ee8c9386623 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 29 Aug 2026 03:20:45 +0200 Subject: [PATCH 08/10] chore(lint): ruff, pylint * dont touch other stuff Signed-off-by: h8d13 --- archinstall/lib/general/system_menu.py | 3 ++- archinstall/lib/installer.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/general/system_menu.py b/archinstall/lib/general/system_menu.py index bd92d1ea89..e2607a3d23 100644 --- a/archinstall/lib/general/system_menu.py +++ b/archinstall/lib/general/system_menu.py @@ -40,13 +40,14 @@ async def select_kernel(preset: list[Kernel] | None = None) -> list[Kernel]: return result.get_values() -async def select_firmware_optdeps(preset: list[FirmwareOptdep] = []) -> list[FirmwareOptdep]: +async def select_firmware_optdeps(preset: list[FirmwareOptdep] | None = None) -> list[FirmwareOptdep]: """ Asks the user which of linux-firmware's optional dependencies to install. :return: The selected firmware packages :rtype: list[FirmwareOptdep] """ + preset = preset or [] group = MenuItemGroup.from_enum(FirmwareOptdep, sort_items=True, preset=preset) result = await Selection[FirmwareOptdep]( diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 04aed28f6f..7ba3522800 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -85,7 +85,7 @@ def __init__( disk_config: DiskLayoutConfiguration, base_packages: list[str] | None = None, kernels: list[str] | None = None, - firmware: list[str] = [], + firmware: list[str] | None = None, silent: bool = False, ) -> None: """ @@ -109,7 +109,7 @@ def __init__( self._base_packages.append(kernel) # Optional firmware is strapped with base so the blobs are in place before the initramfs is generated - self._base_packages.extend(firmware) + self._base_packages.extend(firmware or []) # If using accessibility tools in the live environment, append those to the packages list if accessibility_tools_in_use(): From 3b9aced1844d833558819349e023873ea8d5b490 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sun, 27 Sep 2026 13:50:14 +0200 Subject: [PATCH 09/10] add descriptions from official descs Signed-off-by: h8d13 --- archinstall/lib/general/system_menu.py | 4 +++- archinstall/lib/models/package_types.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/general/system_menu.py b/archinstall/lib/general/system_menu.py index e2607a3d23..aa64aa56fb 100644 --- a/archinstall/lib/general/system_menu.py +++ b/archinstall/lib/general/system_menu.py @@ -48,7 +48,9 @@ async def select_firmware_optdeps(preset: list[FirmwareOptdep] | None = None) -> :rtype: list[FirmwareOptdep] """ preset = preset or [] - group = MenuItemGroup.from_enum(FirmwareOptdep, sort_items=True, preset=preset) + items = [MenuItem(f'{f.value} ({f.description()})', value=f) for f in FirmwareOptdep] + group = MenuItemGroup(items, sort_items=True) + group.set_selected_by_value(preset) result = await Selection[FirmwareOptdep]( group, diff --git a/archinstall/lib/models/package_types.py b/archinstall/lib/models/package_types.py index a78829690d..73dd00eea7 100644 --- a/archinstall/lib/models/package_types.py +++ b/archinstall/lib/models/package_types.py @@ -1,6 +1,8 @@ from enum import StrEnum, auto from typing import Final +from archinstall.lib.translationhandler import tr + class Kernel(StrEnum): LINUX = auto() @@ -28,3 +30,18 @@ class FirmwareOptdep(StrEnum): NFP = 'linux-firmware-nfp' QCOM = 'linux-firmware-qcom' QLOGIC = 'linux-firmware-qlogic' + + def description(self) -> str: + match self: + case FirmwareOptdep.LIQUIDIO: + return tr('Cavium LiquidIO server adapters') + case FirmwareOptdep.MARVELL: + return tr('Marvell devices') + case FirmwareOptdep.MELLANOX: + return tr('Mellanox Spectrum switches') + case FirmwareOptdep.NFP: + return tr('Netronome Flow Processors') + case FirmwareOptdep.QCOM: + return tr('Qualcomm SoCs') + case FirmwareOptdep.QLOGIC: + return tr('QLogic devices') From eb62b173a0084578d77ca8b690266441f126d455 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Tue, 29 Sep 2026 10:50:59 +0200 Subject: [PATCH 10/10] fix(tests): add `firmware_optdeps` to roundtrip fixtures Signed-off-by: h8d13 --- tests/data/test_config.json | 3 +++ tests/test_args.py | 1 + 2 files changed, 4 insertions(+) diff --git a/tests/data/test_config.json b/tests/data/test_config.json index a4ecf800c8..faf6656afe 100644 --- a/tests/data/test_config.json +++ b/tests/data/test_config.json @@ -126,6 +126,9 @@ } ] }, + "firmware_optdeps": [ + "linux-firmware-qcom" + ], "hostname": "archy", "kernels": [ "linux-zen" diff --git a/tests/test_args.py b/tests/test_args.py index 303f227d66..0010e2dc91 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -236,6 +236,7 @@ def test_config_file_parsing( ), hostname='archy', kernels=['linux-zen'], + firmware_optdeps=['linux-firmware-qcom'], ntp=True, packages=['firefox'], pacman_config=PacmanConfiguration(parallel_downloads=66),