diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index bbf61610d7..9b4b75d023 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() @@ -117,6 +118,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) @@ -162,6 +164,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, @@ -277,6 +280,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..aa64aa56fb 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,35 @@ async def select_kernel(preset: list[Kernel] | None = None) -> list[Kernel]: return result.get_values() +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 [] + 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, + 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 ebc2610617..e8e3cc8a54 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('Additional firmware'), + value=[], + action=select_firmware_optdeps, + preview_action=self._prev_firmware_optdeps, + key='firmware_optdeps', + ), MenuItem( text=tr('Hostname'), value='archlinux', @@ -432,6 +439,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("Additional 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 70d45b3374..f6b0b2bcd1 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,9 @@ def __init__( for kernel in self.kernels: 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 []) + # 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..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() @@ -12,3 +14,34 @@ class Kernel(StrEnum): DEFAULT_KERNEL: Final = Kernel.LINUX + + +class FirmwareOptdep(StrEnum): + """ + 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. + """ + + LIQUIDIO = 'linux-firmware-liquidio' + MARVELL = 'linux-firmware-marvell' + MELLANOX = 'linux-firmware-mellanox' + 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') diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index e80afb4545..8bceabe17a 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 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),