feat(hardware): auto-detect audio, bluetooth, camera with declarative override

This commit is contained in:
2026-05-25 04:36:21 +02:00
parent 44f5adc682
commit d2a19cfd5c
21 changed files with 615 additions and 227 deletions

View File

@@ -1,22 +1,6 @@
---
# 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.
# A user-supplied override profile skips detection (golden-image flow: bake an
# image with a fixed profile).
- name: Resolve hardware detection requirement
ansible.builtin.set_fact:
_hardware_detection_needed: >-
@@ -37,7 +21,12 @@
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 }}"
audio: "{{ _hardware_profile_override.audio | default([]) | map('lower') | list }}"
fingerprint: "{{ _hardware_profile_override.fingerprint | default(false) | bool }}"
bluetooth: "{{ _hardware_profile_override.bluetooth | default(false) | bool }}"
camera:
uvc: "{{ _hardware_profile_override.camera.uvc | default(false) | bool }}"
ipu6: "{{ _hardware_profile_override.camera.ipu6 | default(false) | bool }}"
- name: Detect hardware from live host
when:
@@ -61,71 +50,7 @@
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 }}"
ansible.builtin.include_tasks: _resolve_hardware_profile.yml
- name: Initialize empty hardware profile when detection skipped
when: not (_hardware_detection_needed | bool)
@@ -135,7 +60,14 @@
gpus: []
nvidia_supports_open: true
wireless: []
audio: []
fingerprint: false
bluetooth: false
camera: { uvc: false, ipu6: false }
- name: Merge declarative hardware group over detection
when: _hardware_detection_needed | bool
ansible.builtin.include_tasks: _merge_hardware_profile.yml
- name: Report active hardware profile
when: _hardware_detection_needed | bool
@@ -146,4 +78,7 @@
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) }}
audio={{ hardware_profile_active.audio | default([]) | join(',') | default('-', true) }},
fingerprint={{ hardware_profile_active.fingerprint | default(false) }},
bluetooth={{ hardware_profile_active.bluetooth | default(false) }},
camera={{ 'uvc' if hardware_profile_active.camera.uvc | default(false) else '' }}{{ '+ipu6' if hardware_profile_active.camera.ipu6 | default(false) else '' }}