diff --git a/roles/environment/defaults/main.yml b/roles/environment/defaults/main.yml index e2f26c4..80e9454 100644 --- a/roles/environment/defaults/main.yml +++ b/roles/environment/defaults/main.yml @@ -8,3 +8,36 @@ environment_parallel_downloads: 20 environment_pacman_lock_timeout: 120 environment_pacman_retries: 4 environment_pacman_retry_delay: 15 + +# PCI vendor IDs → vendor codes used by hardware detection. +# Only vendors that drive distinct firmware/driver packages are mapped. +environment_pci_vendor_map: + "8086": intel + "1002": amd + "1022": amd + "10de": nvidia + "14e4": broadcom + "10ec": realtek + "168c": atheros + "0cf3": atheros + "168d": atheros + "14c3": mediatek + "11ab": marvell + "1b4b": marvell + "17cb": qcom + "105b": qcom + "1cf3": cirrus + "13d7": cirrus + +# USB vendor IDs of fingerprint readers supported by libfprint / fprintd. +# Lowercase, four-digit hex; matched against `lsusb` output. +environment_fingerprint_vendor_ids: + - "06cb" # Synaptics (modern ThinkPad/Dell) + - "138a" # Validity Sensors (older ThinkPad) + - "1c7a" # LighTuning / Egis + - "27c6" # Goodix + - "04f3" # Elan + - "0a5c" # Broadcom + - "08ff" # AuthenTec (legacy) + - "147e" # Upek (legacy) + - "1491" # Futronic diff --git a/roles/environment/tasks/_detect_hardware.yml b/roles/environment/tasks/_detect_hardware.yml new file mode 100644 index 0000000..80a9c7d --- /dev/null +++ b/roles/environment/tasks/_detect_hardware.yml @@ -0,0 +1,149 @@ +--- +# Hardware detection on the live installer host. +# +# Resolves system_cfg.features.hardware.profile when not explicitly set, so +# downstream bootstrap can install vendor-matched microcode/firmware/GPU/ +# peripheral packages. When the user supplies an override profile, detection +# is skipped (golden-image flow: bake an image with a fixed profile). +# +# Output fact: hardware_profile_active = { +# cpu: 'intel'|'amd'|'', +# gpus: list of 'intel'|'amd'|'nvidia', +# nvidia_supports_open: bool, # true when all detected Nvidia GPUs are +# # Turing or newer (device id >= 0x1e00) +# wireless: list of vendor codes ('intel'|'realtek'|'atheros'|...), +# fingerprint: bool, # USB fingerprint reader detected +# } +# +# Skipped entirely when neither firmware/gpu/peripherals features are enabled. + +- name: Resolve hardware detection requirement + ansible.builtin.set_fact: + _hardware_detection_needed: >- + {{ + (system_cfg.features.firmware.enabled | bool) + or (system_cfg.features.gpu.enabled | bool) + or (system_cfg.features.peripherals.enabled | bool) + }} + _hardware_profile_override: "{{ system_cfg.features.hardware.profile | default({}) }}" + +- name: Use supplied hardware profile (override) + when: + - _hardware_detection_needed | bool + - _hardware_profile_override | length > 0 + ansible.builtin.set_fact: + hardware_profile_active: + cpu: "{{ _hardware_profile_override.cpu | default('') | string | lower }}" + gpus: "{{ _hardware_profile_override.gpus | default([]) | map('lower') | list }}" + nvidia_supports_open: "{{ _hardware_profile_override.nvidia_supports_open | default(true) | bool }}" + wireless: "{{ _hardware_profile_override.wireless | default([]) | map('lower') | list }}" + fingerprint: "{{ _hardware_profile_override.fingerprint | default(false) | bool }}" + +- name: Detect hardware from live host + when: + - _hardware_detection_needed | bool + - _hardware_profile_override | length == 0 + block: + - name: Read CPU vendor + ansible.builtin.command: lscpu + register: _hardware_lscpu + changed_when: false + + - name: Read PCI device list + ansible.builtin.command: lspci -nn + register: _hardware_lspci + changed_when: false + + - name: Read USB device list + ansible.builtin.command: lsusb + register: _hardware_lsusb + changed_when: false + failed_when: false + + - name: Resolve detected hardware profile + vars: + _vendor_keys: "{{ environment_pci_vendor_map.keys() | list }}" + _cpu_vendor_raw: >- + {{ + _hardware_lscpu.stdout + | regex_search('(?im)^Vendor ID:\\s*(\\S+)', '\\1') + | default([''], true) + | first + }} + _cpu_vendor: >- + {{ + 'intel' if _cpu_vendor_raw == 'GenuineIntel' + else ('amd' if _cpu_vendor_raw == 'AuthenticAMD' else '') + }} + # PCI classes: 0300 = VGA, 0302 = 3D, 0280 = wireless network controller. + _gpu_lines: "{{ _hardware_lspci.stdout_lines | select('search', '\\[(0300|0302)\\]:') | list }}" + _gpu_pairs: >- + {{ + _gpu_lines + | map('regex_search', '\\[([0-9a-f]{4}):([0-9a-f]{4})\\]', '\\1', '\\2') + | select('truthy') + | list + }} + _gpu_vendor_ids: "{{ _gpu_pairs | map('first') | select('in', _vendor_keys) | list }}" + _gpu_vendors: "{{ _gpu_vendor_ids | map('extract', environment_pci_vendor_map) | unique | list }}" + _nvidia_device_ids: >- + {{ + _gpu_pairs + | selectattr('0', 'equalto', '10de') + | map(attribute=1) + | list + }} + _nvidia_min_id: >- + {{ + (_nvidia_device_ids | map('int', base=16) | list | min) + if _nvidia_device_ids | length > 0 else 0 + }} + # 0x1e00 = 7680 = first Turing device id; Turing+ supports nvidia-open. + _nvidia_supports_open: "{{ _nvidia_device_ids | length > 0 and (_nvidia_min_id | int) >= 7680 }}" + _wifi_lines: "{{ _hardware_lspci.stdout_lines | select('search', '\\[0280\\]:') | list }}" + _wifi_vendor_ids: >- + {{ + _wifi_lines + | map('regex_search', '\\[([0-9a-f]{4}):[0-9a-f]{4}\\]', '\\1') + | select('truthy') + | map('first') + | select('in', _vendor_keys) + | list + }} + _wifi_vendors: "{{ _wifi_vendor_ids | map('extract', environment_pci_vendor_map) | unique | list }}" + _fingerprint_present: >- + {{ + (_hardware_lsusb.stdout | default('')) + | regex_search( + '(?i)ID (' ~ (environment_fingerprint_vendor_ids | join('|')) ~ '):' + ) + is not none + }} + ansible.builtin.set_fact: + hardware_profile_active: + cpu: "{{ _cpu_vendor }}" + gpus: "{{ _gpu_vendors }}" + nvidia_supports_open: "{{ _nvidia_supports_open | bool }}" + wireless: "{{ _wifi_vendors }}" + fingerprint: "{{ _fingerprint_present | bool }}" + +- name: Initialize empty hardware profile when detection skipped + when: not (_hardware_detection_needed | bool) + ansible.builtin.set_fact: + hardware_profile_active: + cpu: "" + gpus: [] + nvidia_supports_open: true + wireless: [] + fingerprint: false + +- name: Report active hardware profile + when: _hardware_detection_needed | bool + ansible.builtin.debug: + msg: >- + Hardware profile {{ 'override' if _hardware_profile_override | length > 0 else 'detected' }}: + cpu={{ hardware_profile_active.cpu | default('-') }}, + gpus={{ hardware_profile_active.gpus | default([]) | join(',') | default('-', true) }} + {{ '(open-supported)' if hardware_profile_active.nvidia_supports_open | bool else '(legacy)' }}, + wireless={{ hardware_profile_active.wireless | default([]) | join(',') | default('-', true) }}, + fingerprint={{ hardware_profile_active.fingerprint | default(false) }} diff --git a/roles/environment/tasks/main.yml b/roles/environment/tasks/main.yml index eda86c7..26c8106 100644 --- a/roles/environment/tasks/main.yml +++ b/roles/environment/tasks/main.yml @@ -11,5 +11,8 @@ - name: Prepare installer environment ansible.builtin.include_tasks: _prepare_installer.yml + - name: Detect hardware for firmware/GPU package selection + ansible.builtin.include_tasks: _detect_hardware.yml + - name: Run third-party preparation tasks ansible.builtin.include_tasks: _thirdparty.yml