From 7d45f25a7e05a2222fa8dc688a0f8b4b95df2347 Mon Sep 17 00:00:00 2001 From: Sandwich Date: Wed, 29 Apr 2026 21:23:55 +0200 Subject: [PATCH] feat(bootstrap): install vendor-matched hardware packages --- main.yml | 2 +- roles/bootstrap/tasks/_hardware.yml | 187 ++++++++++++++++++++++++++++ roles/bootstrap/tasks/main.yml | 7 ++ roles/bootstrap/vars/hardware.yml | 183 +++++++++++++++++++++++++++ vars_baremetal_example.yml | 10 ++ 5 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 roles/bootstrap/tasks/_hardware.yml create mode 100644 roles/bootstrap/vars/hardware.yml diff --git a/main.yml b/main.yml index 2747f44..e67f814 100644 --- a/main.yml +++ b/main.yml @@ -3,7 +3,7 @@ # 1. global_defaults — normalize + validate system/hypervisor/disk input # 2. system_check — pre-flight hardware/environment safety checks # 3. virtualization — create VM on hypervisor (libvirt/proxmox/vmware/xen) -# 4. environment — detect live ISO, configure installer network, install tools +# 4. environment — detect live ISO, configure installer network, install tools, detect hardware # 5. partitioning — partition disk, create FS, LUKS, LVM, mount everything # 6. bootstrap — debootstrap/pacstrap/dnf install the target OS into /mnt # 7. configuration — users, network, encryption, fstab, bootloader, services diff --git a/roles/bootstrap/tasks/_hardware.yml b/roles/bootstrap/tasks/_hardware.yml new file mode 100644 index 0000000..06d1871 --- /dev/null +++ b/roles/bootstrap/tasks/_hardware.yml @@ -0,0 +1,187 @@ +--- +# Installs vendor-matched microcode/firmware/GPU/peripheral packages into /mnt +# based on hardware_profile_active set by environment/_detect_hardware.yml. +- name: Load hardware package definitions + ansible.builtin.include_vars: + file: hardware.yml + +- name: Validate hardware support for current os_family + ansible.builtin.assert: + that: + - os_family in bootstrap_hardware_packages + - hardware_profile_active is defined + fail_msg: >- + Hardware feature requested but no package map for os_family + '{{ os_family }}'. Extend roles/bootstrap/vars/hardware.yml. + quiet: true + +# nvidia_driver: auto → open (Turing+) → proprietary (older, if family ships it) +# → nouveau (fallback). Explicit value falls back to nouveau when +# the family lacks packages for it. +- name: Resolve Nvidia driver flavor + vars: + _family: "{{ bootstrap_hardware_packages[os_family] }}" + _user_driver: "{{ system_cfg.features.gpu.nvidia_driver | default('auto') }}" + _has_nvidia: "{{ 'nvidia' in (hardware_profile_active.gpus | default([])) }}" + _supports_open: "{{ hardware_profile_active.nvidia_supports_open | default(true) | bool }}" + _open_pkgs: "{{ _family.gpu_nvidia.open | default([]) }}" + _prop_pkgs: "{{ _family.gpu_nvidia.proprietary | default([]) }}" + _auto_choice: >- + {{ + ('open' if _supports_open and _open_pkgs | length > 0 + else ('proprietary' if _prop_pkgs | length > 0 + else 'nouveau')) + }} + _user_choice: >- + {{ + _auto_choice if _user_driver == 'auto' + else (_user_driver + if (_family.gpu_nvidia[_user_driver] | default([]) | length > 0) + else 'nouveau') + }} + ansible.builtin.set_fact: + _nvidia_driver_resolved: "{{ _user_choice if _has_nvidia else 'nouveau' }}" + +# Fedora's akmod-nvidia* packages live in RPMFusion non-free, which is not +# enabled out of the box; install the release RPM before the package step. +- name: Enable RPMFusion non-free for Fedora Nvidia install + when: + - os_family == 'RedHat' + - os == 'fedora' + - system_cfg.features.gpu.enabled | bool + - _nvidia_driver_resolved in ['open', 'proprietary'] + ansible.builtin.command: >- + {{ chroot_command }} dnf install -y + https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-{{ os_version_major }}.noarch.rpm + https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-{{ os_version_major }}.noarch.rpm + register: _rpmfusion_result + changed_when: _rpmfusion_result.rc == 0 + +- name: Resolve hardware package set + vars: + _family: "{{ bootstrap_hardware_packages[os_family] }}" + _cpu: "{{ hardware_profile_active.cpu | default('') | string }}" + _gpus: "{{ hardware_profile_active.gpus | default([]) | list }}" + _wifi: "{{ hardware_profile_active.wireless | default([]) | list }}" + _fp_detected: "{{ hardware_profile_active.fingerprint | default(false) | bool }}" + _firmware_on: "{{ system_cfg.features.firmware.enabled | bool }}" + _microcode_on: "{{ _firmware_on and (system_cfg.features.firmware.microcode | bool) }}" + _gpu_on: "{{ system_cfg.features.gpu.enabled | bool }}" + _peripherals_on: "{{ system_cfg.features.peripherals.enabled | bool }}" + _webcam_pref: "{{ system_cfg.features.peripherals.webcam | default('auto') }}" + _fp_pref: "{{ system_cfg.features.peripherals.fingerprint | default('auto') }}" + _dl_on: "{{ system_cfg.features.peripherals.displaylink | bool }}" + _webcam_on: >- + {{ + _peripherals_on + and (_webcam_pref == 'true' or (_webcam_pref == 'auto' and _peripherals_on)) + }} + _fp_on: >- + {{ + _peripherals_on + and (_fp_pref == 'true' or (_fp_pref == 'auto' and _fp_detected)) + }} + # Union of GPU/wireless/CPU vendors; CPU vendor is included so Intel-CPU + # systems pull i915/iwlwifi firmware via the same vendor split. + _cpu_vendor_list: "{{ ([_cpu] if (_cpu | length > 0) else []) | list }}" + _firmware_vendors: >- + {{ + (_firmware_on | ternary( + (_gpus + _wifi + _cpu_vendor_list) + | reject('equalto', '') | unique | list, + [] + )) + }} + _microcode_pkgs: >- + {{ + ((_microcode_on and _cpu | length > 0) | ternary( + _family.cpu_microcode[_cpu] | default([]), + [] + )) | list + }} + _firmware_pkgs: >- + {{ + (_firmware_on | ternary( + (_family.firmware_base | default([]) | list) + + (_firmware_vendors + | map('extract', _family.firmware | default({})) + | select('truthy') + | list + | sum(start=[])), + [] + )) | list + }} + _gpu_base_pkgs: "{{ (_gpu_on | ternary(_family.gpu_base | default([]), [])) | list }}" + _gpu_vendor_pkgs: >- + {{ + (_gpu_on | ternary( + (_gpus | reject('equalto', 'nvidia') | list) + | map('extract', _family.gpu | default({})) + | select('truthy') + | list + | sum(start=[]), + [] + )) | list + }} + _gpu_nvidia_pkgs: >- + {{ + ((_gpu_on and ('nvidia' in _gpus)) | ternary( + _family.gpu_nvidia[_nvidia_driver_resolved] | default([]), + [] + )) | list + }} + _peripherals_base_pkgs: >- + {{ + (_webcam_on | ternary(_family.peripherals_base | default([]), [])) | list + }} + _peripherals_fingerprint_pkgs: >- + {{ + (_fp_on | ternary(_family.peripherals_fingerprint | default([]), [])) | list + }} + _peripherals_displaylink_pkgs: >- + {{ + (_dl_on | ternary(_family.peripherals_displaylink | default([]), [])) | list + }} + ansible.builtin.set_fact: + _hardware_packages: >- + {{ + (_microcode_pkgs + _firmware_pkgs + + _gpu_base_pkgs + _gpu_vendor_pkgs + _gpu_nvidia_pkgs + + _peripherals_base_pkgs + _peripherals_fingerprint_pkgs + + _peripherals_displaylink_pkgs) + | reject('equalto', '') + | unique + | list + }} + +- name: Report hardware package selection + ansible.builtin.debug: + msg: >- + Hardware install ({{ os_family }}): + cpu={{ hardware_profile_active.cpu | default('-') }}, + gpus={{ hardware_profile_active.gpus | default([]) | join(',') | default('-', true) }}, + nvidia_driver={{ _nvidia_driver_resolved }}, + wireless={{ hardware_profile_active.wireless | default([]) | join(',') | default('-', true) }}, + fingerprint={{ hardware_profile_active.fingerprint | default(false) }} + → {{ _hardware_packages | length }} package(s) + +- name: Install hardware packages + when: _hardware_packages | length > 0 + vars: + _install_commands: + RedHat: >- + {{ chroot_command }} dnf --releasever={{ os_version }} + --setopt=install_weak_deps=False install -y {{ _hardware_packages | join(' ') }} + Debian: >- + {{ chroot_command }} apt install -y {{ _hardware_packages | join(' ') }} + Archlinux: >- + pacstrap /mnt {{ _hardware_packages | join(' ') }} + Suse: >- + {{ chroot_command }} zypper install -y {{ _hardware_packages | join(' ') }} + Alpine: >- + {{ chroot_command }} apk add {{ _hardware_packages | join(' ') }} + Void: >- + {{ chroot_command }} xbps-install -Sy {{ _hardware_packages | join(' ') }} + ansible.builtin.command: "{{ _install_commands[os_family] }}" + register: _hardware_install_result + changed_when: _hardware_install_result.rc == 0 diff --git a/roles/bootstrap/tasks/main.yml b/roles/bootstrap/tasks/main.yml index ad38c8b..5241040 100644 --- a/roles/bootstrap/tasks/main.yml +++ b/roles/bootstrap/tasks/main.yml @@ -34,6 +34,13 @@ bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}" ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}" +- name: Install hardware-matched firmware/microcode/GPU/peripheral packages + when: >- + (system_cfg.features.firmware.enabled | bool) + or (system_cfg.features.gpu.enabled | bool) + or (system_cfg.features.peripherals.enabled | bool) + ansible.builtin.include_tasks: _hardware.yml + - name: Install desktop environment packages when: system_cfg.features.desktop.enabled | bool ansible.builtin.include_tasks: _desktop.yml diff --git a/roles/bootstrap/vars/hardware.yml b/roles/bootstrap/vars/hardware.yml new file mode 100644 index 0000000..3647ea1 --- /dev/null +++ b/roles/bootstrap/vars/hardware.yml @@ -0,0 +1,183 @@ +--- +# Hardware-aware package definitions keyed by os_family. Schema: +# cpu_microcode[intel|amd] CPU vendor microcode +# firmware_base unconditional firmware packages +# firmware[] vendor-split firmware (intel|amd|nvidia| +# atheros|broadcom|mediatek|marvell|realtek| +# qcom|cirrus|other) +# gpu_base mesa + vulkan loader +# gpu[intel|amd] per-GPU userspace +# gpu_nvidia[open|proprietary|nouveau] nvidia driver flavor +# peripherals_base webcam/scanner stack +# peripherals_fingerprint fprintd + libfprint +# peripherals_displaylink evdi kernel module + DisplayLink helpers +# Only packages matching detected hardware are installed; families without +# vendor splits collapse to a single firmware meta package. +bootstrap_hardware_packages: + Archlinux: + cpu_microcode: + intel: [intel-ucode] + amd: [amd-ucode] + firmware_base: [] + firmware: + intel: [linux-firmware-other] # iwlwifi + i915 firmware live here + amd: [linux-firmware-amdgpu] + nvidia: [linux-firmware-nvidia] + atheros: [linux-firmware-atheros] + broadcom: [linux-firmware-broadcom] + mediatek: [linux-firmware-mediatek] + marvell: [linux-firmware-marvell] + realtek: [linux-firmware-realtek] + qcom: [linux-firmware-qcom] + cirrus: [linux-firmware-cirrus] + other: [linux-firmware-other] + gpu_base: [mesa, vulkan-icd-loader] + gpu: + intel: [vulkan-intel, intel-media-driver] + amd: [vulkan-radeon, libva-mesa-driver] + gpu_nvidia: + open: [nvidia-open-dkms, nvidia-utils] + proprietary: [nvidia-dkms, nvidia-utils] + nouveau: [xf86-video-nouveau, vulkan-nouveau] + peripherals_base: [v4l-utils] + peripherals_fingerprint: [fprintd, libfprint] + peripherals_displaylink: [] # AUR only; user must wire in AUR helper + + Debian: + cpu_microcode: + intel: [intel-microcode] + amd: [amd64-microcode] + firmware_base: [firmware-linux-free] + firmware: + intel: [firmware-iwlwifi, firmware-misc-nonfree] + amd: [firmware-amd-graphics, firmware-misc-nonfree] + nvidia: [firmware-misc-nonfree] + atheros: [firmware-atheros] + broadcom: [firmware-brcm80211] + mediatek: [firmware-misc-nonfree] + marvell: [firmware-misc-nonfree] + realtek: [firmware-realtek] + qcom: [firmware-misc-nonfree] + cirrus: [firmware-misc-nonfree] + other: [firmware-misc-nonfree] + gpu_base: [mesa-vulkan-drivers, libgl1-mesa-dri] + gpu: + intel: [intel-media-va-driver, i965-va-driver] + amd: [libva-glx2, mesa-va-drivers] + gpu_nvidia: + # Debian trixie+ ships nvidia-open-kernel-dkms; older releases only have + # the proprietary nvidia-driver. Both come from the non-free component. + open: [nvidia-open-kernel-dkms, nvidia-driver, nvidia-vulkan-icd] + proprietary: [nvidia-driver, nvidia-vulkan-icd] + nouveau: [xserver-xorg-video-nouveau] + peripherals_base: [v4l-utils] + peripherals_fingerprint: [fprintd, libpam-fprintd] + peripherals_displaylink: [evdi-dkms] # userspace driver still needs vendor .run + + RedHat: + cpu_microcode: + intel: [microcode_ctl] + amd: [microcode_ctl] + firmware_base: [linux-firmware] + firmware: + intel: [] + amd: [] + nvidia: [] + atheros: [] + broadcom: [] + mediatek: [] + marvell: [] + realtek: [] + qcom: [] + cirrus: [] + other: [] + gpu_base: [mesa-dri-drivers, mesa-vulkan-drivers, vulkan-loader] + gpu: + intel: [intel-media-driver, libva-intel-driver] + amd: [mesa-va-drivers] + gpu_nvidia: + # akmod packages from RPMFusion non-free; repo enabled by _hardware.yml. + open: [akmod-nvidia-open, xorg-x11-drv-nvidia, xorg-x11-drv-nvidia-cuda] + proprietary: [akmod-nvidia, xorg-x11-drv-nvidia, xorg-x11-drv-nvidia-cuda] + nouveau: [xorg-x11-drv-nouveau] + peripherals_base: [v4l-utils] + peripherals_fingerprint: [fprintd, fprintd-pam] + peripherals_displaylink: [evdi] # COPR-supplied; repo enablement deferred + + Suse: + cpu_microcode: + intel: [ucode-intel] + amd: [ucode-amd] + firmware_base: [kernel-firmware-all] + firmware: {} + gpu_base: [Mesa, Mesa-libGL1, libvulkan1] + gpu: + intel: [libvulkan_intel] + amd: [libvulkan_radeon] + gpu_nvidia: + # NVIDIA SUSE repo packages; repo enablement out of scope for v1. + open: [nvidia-open-driver-G06-signed-kmp-default] + proprietary: [x11-video-nvidiaG06] + nouveau: [xf86-video-nouveau] + peripherals_base: [v4l-utils] + peripherals_fingerprint: [fprintd, libfprint-2-2] + peripherals_displaylink: [] + + Alpine: + cpu_microcode: + intel: [intel-ucode] + amd: [amd-ucode] + firmware_base: [] + firmware: + intel: [linux-firmware-other, linux-firmware-i915] + amd: [linux-firmware-amdgpu] + nvidia: [linux-firmware-nvidia] + atheros: [linux-firmware-ath10k_pci, linux-firmware-ath11k] + broadcom: [linux-firmware-brcm] + mediatek: [linux-firmware-mediatek] + marvell: [linux-firmware-mrvl] + realtek: [linux-firmware-rtl_nic, linux-firmware-rtlwifi] + qcom: [linux-firmware-qcom] + cirrus: [linux-firmware-cirrus] + other: [linux-firmware-other] + gpu_base: [mesa, mesa-dri-gallium] + gpu: + intel: [mesa-vulkan-intel, intel-media-driver] + amd: [mesa-vulkan-ati, mesa-va-gallium] + gpu_nvidia: + # Alpine ships only the open-kernel-modules variant in community. + open: [nvidia-open-gpu-kernel-modules] + proprietary: [] # not packaged on Alpine + nouveau: [mesa-vulkan-nouveau] + peripherals_base: [v4l-utils] + peripherals_fingerprint: [fprintd, libfprint] + peripherals_displaylink: [] + + Void: + cpu_microcode: + intel: [intel-ucode] + amd: [linux-firmware-amd] + firmware_base: [linux-firmware] + firmware: + intel: [linux-firmware-intel, linux-firmware-network] + amd: [linux-firmware-amd] + nvidia: [linux-firmware-nvidia] + atheros: [linux-firmware-network] + broadcom: [linux-firmware-broadcom] + mediatek: [linux-firmware-network] + marvell: [linux-firmware-network] + realtek: [linux-firmware-network] + qcom: [linux-firmware-network] + cirrus: [linux-firmware] + other: [linux-firmware] + gpu_base: [mesa, mesa-dri] + gpu: + intel: [mesa-vulkan-intel, intel-video-accel] + amd: [mesa-vulkan-radeon, mesa-vaapi] + gpu_nvidia: + open: [nvidia-open] # in nonfree repo + proprietary: [nvidia] # in nonfree repo + nouveau: [xf86-video-nouveau] + peripherals_base: [v4l-utils] + peripherals_fingerprint: [fprintd, libfprint] + peripherals_displaylink: [] diff --git a/vars_baremetal_example.yml b/vars_baremetal_example.yml index b744f63..78934cd 100644 --- a/vars_baremetal_example.yml +++ b/vars_baremetal_example.yml @@ -61,3 +61,13 @@ system: sudo: true chroot: tool: "arch-chroot" + # firmware/microcode default to "auto" — on for physical, off for virtual. + # gpu and peripherals.* default to "auto" tied to desktop.enabled. + # Override only when you want non-default behavior. + gpu: + enabled: false # set true for desktop installs + nvidia_driver: "auto" # auto = open|proprietary|nouveau by GPU generation + peripherals: + displaylink: false # explicit opt-in for DisplayLink docks + hardware: + profile: {} # autodetect; or set to bake a golden-image profile