From f60f9ff0322ea90d9ccf47ae6e1b2a1f4f28bb88 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Wed, 12 Aug 2026 14:03:32 +0200 Subject: [PATCH] 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 b2003d4b..602fcbd6 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -88,6 +88,7 @@ class ArchConfigType(StrEnum): ENCRYPTION_PASSWORD = 'encryption_password' HOSTNAME = 'hostname' KERNELS = 'kernels' + FIRMWARE_OPTDEPS = 'firmware_optdeps' NTP = 'ntp' TIMEZONE = 'timezone' SERVICES = 'services' @@ -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 @@ class ArchConfig: 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 @@ class ArchConfig: 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 82dc4cdb..4f26c4a5 100644 --- a/archinstall/lib/general/system_menu.py +++ b/archinstall/lib/general/system_menu.py @@ -3,7 +3,7 @@ from typing import assert_never 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 @@ -37,6 +37,32 @@ async def select_kernel(preset: list[Kernel] = []) -> 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 1460d340..7d5e7fc5 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -9,7 +9,7 @@ from archinstall.lib.bootloader.utils import validate_bootloader_layout 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 @@ class GlobalMenu(AbstractMenu[None]): 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 @@ class GlobalMenu(AbstractMenu[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 aeb5c6ae..4b16f8cf 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -79,6 +79,7 @@ class Installer: disk_config: DiskLayoutConfiguration, base_packages: list[str] = [], kernels: list[str] | None = None, + firmware: list[str] | None = None, silent: bool = False, ): """ @@ -101,6 +102,10 @@ class Installer: 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 e314c30a..bf1fe188 100644 --- a/archinstall/lib/models/package_types.py +++ b/archinstall/lib/models/package_types.py @@ -10,3 +10,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 627a3b75..335bc315 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