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

@@ -0,0 +1,24 @@
---
- name: "Seed lscpu/lspci/lsusb registers ({{ fixture.name }})"
ansible.builtin.set_fact:
_hardware_lscpu: { stdout: "{{ fixture.lscpu }}" }
_hardware_lspci: { stdout: "{{ fixture.lspci | join('\n') }}", stdout_lines: "{{ fixture.lspci }}" }
_hardware_lsusb: { stdout: "{{ fixture.lsusb }}" }
- name: "Resolve hardware profile ({{ fixture.name }})"
ansible.builtin.include_tasks: ../../roles/environment/tasks/_resolve_hardware_profile.yml
- name: "Assert resolved profile ({{ fixture.name }})"
ansible.builtin.assert:
that:
- hardware_profile_active.cpu == fixture.expect.cpu
- hardware_profile_active.gpus == fixture.expect.gpus
- hardware_profile_active.wireless == fixture.expect.wireless
- hardware_profile_active.audio == fixture.expect.audio
- (hardware_profile_active.fingerprint | bool) == (fixture.expect.fingerprint | bool)
- (hardware_profile_active.bluetooth | bool) == (fixture.expect.bluetooth | bool)
- (hardware_profile_active.camera.uvc | bool) == (fixture.expect.camera.uvc | bool)
- (hardware_profile_active.camera.ipu6 | bool) == (fixture.expect.camera.ipu6 | bool)
- (hardware_profile_active.nvidia_supports_open | bool) == (fixture.expect.nvidia_supports_open | bool)
fail_msg: "[{{ fixture.name }}] FAIL got {{ hardware_profile_active }}"
success_msg: "[{{ fixture.name }}] OK {{ hardware_profile_active }}"

View File

@@ -0,0 +1,44 @@
---
- name: "Seed detection + declarative group (merge {{ mf.name }})"
ansible.builtin.set_fact:
hardware_profile_active: "{{ mf.detected }}"
os_family: "{{ mf.os_family }}"
_nvidia_driver_resolved: "{{ mf.nvidia_driver_resolved | default('nouveau') }}"
system_cfg:
features:
firmware: { enabled: true, microcode: true }
gpu: { enabled: true }
peripherals:
enabled: true
camera: "auto"
fingerprint: "auto"
audio: "auto"
bluetooth: "auto"
displaylink: false
hardware: "{{ mf.hardware }}"
- name: "Merge group over detection (merge {{ mf.name }})"
ansible.builtin.include_tasks: ../../roles/environment/tasks/_merge_hardware_profile.yml
- name: "Assert merged profile (merge {{ mf.name }})"
ansible.builtin.assert:
that:
- (hardware_profile_active.fingerprint | bool) == (mf.expect_profile.fingerprint | bool)
- (hardware_profile_active.bluetooth | bool) == (mf.expect_profile.bluetooth | bool)
- _hardware_profile_kernel_params == (mf.hardware.kernel_params | default([]))
fail_msg: "[merge {{ mf.name }}] profile FAIL {{ hardware_profile_active }}"
- name: "Load package map (merge {{ mf.name }})"
ansible.builtin.include_vars:
file: ../../roles/bootstrap/vars/hardware.yml
- name: "Resolve packages (merge {{ mf.name }})"
ansible.builtin.include_tasks: ../../roles/bootstrap/tasks/_resolve_hardware_packages.yml
- name: "Assert resolved package list (merge {{ mf.name }})"
ansible.builtin.assert:
that:
- (mf.expect_contains | default([])) | difference(_hardware_packages) | length == 0
- (mf.expect_excludes | default([])) | intersect(_hardware_packages) | length == 0
fail_msg: "[merge {{ mf.name }}] FAIL got {{ _hardware_packages }}"
success_msg: "[merge {{ mf.name }}] OK {{ _hardware_packages }}"

View File

@@ -0,0 +1,32 @@
---
- name: "Seed resolve inputs (pkg {{ pf.name }})"
ansible.builtin.set_fact:
os_family: "{{ pf.os_family }}"
hardware_profile_active: "{{ pf.profile }}"
_nvidia_driver_resolved: "{{ pf.nvidia_driver_resolved | default('nouveau') }}"
system_cfg:
features:
firmware: { enabled: true, microcode: true }
gpu: { enabled: true }
peripherals:
enabled: true
camera: "auto"
fingerprint: "auto"
audio: "auto"
bluetooth: "auto"
displaylink: false
- name: "Load package map (pkg {{ pf.name }})"
ansible.builtin.include_vars:
file: ../../roles/bootstrap/vars/hardware.yml
- name: "Resolve packages (pkg {{ pf.name }})"
ansible.builtin.include_tasks: ../../roles/bootstrap/tasks/_resolve_hardware_packages.yml
- name: "Assert resolved package list (pkg {{ pf.name }})"
ansible.builtin.assert:
that:
- (pf.expect_contains | default([])) | difference(_hardware_packages) | length == 0
- (pf.expect_excludes | default([])) | intersect(_hardware_packages) | length == 0
fail_msg: "[pkg {{ pf.name }}] FAIL got {{ _hardware_packages }}"
success_msg: "[pkg {{ pf.name }}] OK {{ _hardware_packages }}"

View File

@@ -0,0 +1,41 @@
---
# Canned lscpu/lspci/lsusb -> expected profile. lspci lines are `lspci -nn` shaped,
# lsusb is plain `lsusb`.
hardware_fixtures:
- name: intel-laptop-nvidia-turing
lscpu: "Architecture: x86_64\nVendor ID: GenuineIntel\nModel name: 12th Gen Core i7"
lspci:
- "00:02.0 VGA compatible controller [0300]: Intel Corporation Alder Lake-P GT2 [8086:46a6] (rev 0c)"
- "01:00.0 3D controller [0302]: NVIDIA Corporation TU117M [GeForce GTX 1650 Mobile] [10de:1f99] (rev a1)"
- "00:14.3 Network controller [0280]: Intel Corporation Wi-Fi 6 AX201 [8086:a0f0] (rev 11)"
- "00:1f.3 Audio device [0403]: Intel Corporation Alder Lake PCH-P High Definition Audio [8086:51c8] (rev 01)"
lsusb: |-
Bus 001 Device 003: ID 06cb:00bd Synaptics, Inc. Prometheus MIS Touch Fingerprint Reader
Bus 001 Device 004: ID 8087:0026 Intel Corp. AX201 Bluetooth
Bus 001 Device 005: ID 5986:118d Acer, Inc. Integrated Camera
expect: { cpu: intel, gpus: [intel, nvidia], nvidia_supports_open: true, wireless: [intel], audio: [intel], fingerprint: true, bluetooth: true, camera: { uvc: true, ipu6: false } }
- name: amd-desktop-realtek
lscpu: "Architecture: x86_64\nVendor ID: AuthenticAMD\nModel name: AMD Ryzen 7 5800X"
lspci:
- "0a:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 22 [1002:73df] (rev c7)"
- "05:00.0 Network controller [0280]: Realtek Semiconductor Co., Ltd. RTL8822CE 802.11ac [10ec:c822]"
- "0b:00.4 Audio device [0403]: Advanced Micro Devices, Inc. [AMD] Starship/Matisse HD Audio [1022:1487]"
lsusb: "Bus 002 Device 002: ID 046d:c52b Logitech, Inc. Unifying Receiver"
expect: { cpu: amd, gpus: [amd], nvidia_supports_open: false, wireless: [realtek], audio: [amd], fingerprint: false, bluetooth: false, camera: { uvc: false, ipu6: false } }
- name: nvidia-legacy-maxwell
lscpu: "Vendor ID: GenuineIntel"
lspci:
- "01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GM107 [GeForce GTX 750 Ti] [10de:1380] (rev a2)"
lsusb: ""
expect: { cpu: intel, gpus: [nvidia], nvidia_supports_open: false, wireless: [], audio: [], fingerprint: false, bluetooth: false, camera: { uvc: false, ipu6: false } }
- name: intel-laptop-ipu6-camera
lscpu: "Vendor ID: GenuineIntel"
lspci:
- "00:02.0 VGA compatible controller [0300]: Intel Corporation Raptor Lake-P [8086:a7a0] (rev 04)"
- "00:05.0 Multimedia controller [0480]: Intel Corporation Raptor Lake IPU6 [8086:a75d] (rev 04)"
- "00:14.3 Network controller [0280]: Intel Corporation Wi-Fi 6E AX211 [8086:51f0]"
lsusb: "Bus 003 Device 002: ID 8087:0033 Intel Corp. AX211 Bluetooth"
expect: { cpu: intel, gpus: [intel], nvidia_supports_open: false, wireless: [intel], audio: [], fingerprint: false, bluetooth: true, camera: { uvc: false, ipu6: true } }

View File

@@ -0,0 +1,37 @@
---
# Detected profile + declarative hardware.* -> merged profile + resolved packages.
# Exercises union/OR/force-on, disable[] force-off, and packages[os_family] append.
merge_fixtures:
- name: force-fingerprint-disable-audio-pin-ipu6
os_family: Archlinux
nvidia_driver_resolved: nouveau
detected: { cpu: intel, gpus: [intel], nvidia_supports_open: true, wireless: [intel], audio: [intel], fingerprint: false, bluetooth: false, camera: { uvc: false, ipu6: true } }
hardware:
fingerprint: true # force-on (detection missed it)
packages: { Archlinux: [intel-ipu6-dkms, v4l2-relayd] }
disable: [audio] # force-off audio even though detected
kernel_params: ["i915.enable_psr=0"]
expect_profile: { fingerprint: true, bluetooth: false }
expect_contains: [intel-ipu6-dkms, v4l2-relayd, fprintd, libfprint, v4l-utils, intel-ucode]
expect_excludes: [sof-firmware, alsa-ucm-conf, bluez]
- name: union-vendors-force-bluetooth
os_family: Debian
nvidia_driver_resolved: nouveau
detected: { cpu: amd, gpus: [amd], nvidia_supports_open: false, wireless: [], audio: [amd], fingerprint: false, bluetooth: false, camera: { uvc: false, ipu6: false } }
hardware:
wireless: [realtek] # union: add a wireless vendor detection missed
bluetooth: true # force-on bluetooth
expect_profile: { fingerprint: false, bluetooth: true }
expect_contains: [amd64-microcode, firmware-realtek, firmware-sof-signed, bluez]
expect_excludes: [fprintd, v4l-utils]
- name: disable-vendor-drops-microcode-firmware-gpu
os_family: Archlinux
nvidia_driver_resolved: open
detected: { cpu: intel, gpus: [intel, nvidia], nvidia_supports_open: true, wireless: [intel], audio: [intel], fingerprint: false, bluetooth: false, camera: { uvc: false, ipu6: false } }
hardware:
disable: [nvidia, intel] # drop the nvidia GPU and every intel-vendor contribution
expect_profile: { fingerprint: false, bluetooth: false }
expect_contains: [mesa, vulkan-icd-loader]
expect_excludes: [nvidia-open-dkms, intel-ucode, vulkan-intel, sof-firmware, linux-firmware-other]

View File

@@ -0,0 +1,22 @@
---
# profile + os_family -> resolved package list. expect_contains: all must be present;
# expect_excludes: none may be present. Features default all-on with auto peripherals.
package_fixtures:
- name: arch-intel-audio-bt-fp
os_family: Archlinux
profile: { cpu: intel, gpus: [intel], wireless: [intel], audio: [intel], fingerprint: true, bluetooth: true, camera: { uvc: true, ipu6: false } }
expect_contains: [intel-ucode, linux-firmware-other, mesa, vulkan-icd-loader, vulkan-intel, sof-firmware, alsa-ucm-conf, bluez, bluez-utils, fprintd, libfprint, v4l-utils]
expect_excludes: [nvidia-open-dkms, evdi]
- name: debian-amd-audio-no-bt-no-fp
os_family: Debian
profile: { cpu: amd, gpus: [amd], wireless: [realtek], audio: [amd], fingerprint: false, bluetooth: false, camera: { uvc: false, ipu6: false } }
expect_contains: [amd64-microcode, firmware-linux-free, firmware-amd-graphics, firmware-realtek, mesa-vulkan-drivers, firmware-sof-signed, alsa-ucm-conf]
expect_excludes: [bluez, fprintd, libpam-fprintd, v4l-utils]
- name: redhat-intel-nvidia-bt
os_family: RedHat
profile: { cpu: intel, gpus: [intel, nvidia], wireless: [intel], audio: [intel], fingerprint: false, bluetooth: true, camera: { uvc: false, ipu6: true } }
nvidia_driver_resolved: open
expect_contains: [microcode_ctl, linux-firmware, mesa-dri-drivers, vulkan-loader, akmod-nvidia-open, alsa-sof-firmware, alsa-ucm, bluez, v4l-utils]
expect_excludes: [fprintd, evdi]

View File

@@ -0,0 +1,16 @@
---
# Run: ansible-playbook tests/hardware/test_detection.yml
- name: Hardware detection fixture tests
hosts: localhost
gather_facts: false
connection: local
vars_files:
- ../../roles/environment/defaults/main.yml
- fixtures.yml
tasks:
- name: Run each detection fixture
ansible.builtin.include_tasks: _assert_fixture.yml
loop: "{{ hardware_fixtures }}"
loop_control:
loop_var: fixture
label: "{{ fixture.name }}"

View File

@@ -0,0 +1,15 @@
---
# Run: ansible-playbook tests/hardware/test_merge.yml
- name: Hardware merge-layer fixture tests
hosts: localhost
gather_facts: false
connection: local
vars_files:
- merge_fixtures.yml
tasks:
- name: Run each merge fixture
ansible.builtin.include_tasks: _assert_merge.yml
loop: "{{ merge_fixtures }}"
loop_control:
loop_var: mf
label: "{{ mf.name }}"

View File

@@ -0,0 +1,15 @@
---
# Run: ansible-playbook tests/hardware/test_packages.yml
- name: Hardware package-resolution fixture tests
hosts: localhost
gather_facts: false
connection: local
vars_files:
- pkg_fixtures.yml
tasks:
- name: Run each package fixture
ansible.builtin.include_tasks: _assert_packages.yml
loop: "{{ package_fixtures }}"
loop_control:
loop_var: pf
label: "{{ pf.name }}"