From a08788e3df399fe61e2d79667a66ee50fdb044b5 Mon Sep 17 00:00:00 2001 From: Samuel James Date: Thu, 3 Sep 2026 16:03:27 +0000 Subject: [PATCH] thinkcenter-obs: build v4l2loopback 0.15.4 from source (distro dkms fails on kernel 7.x); best-effort vcam; dpkg self-heal + re-verify-on-fail --- life-church/playbooks/thinkcenter-obs.yml | 162 +++++++++++++++++++--- 1 file changed, 140 insertions(+), 22 deletions(-) diff --git a/life-church/playbooks/thinkcenter-obs.yml b/life-church/playbooks/thinkcenter-obs.yml index 50d8ac7..8a8c33c 100644 --- a/life-church/playbooks/thinkcenter-obs.yml +++ b/life-church/playbooks/thinkcenter-obs.yml @@ -69,6 +69,7 @@ vars: ansible_become_pass: "{{ hostvars['localhost'].thinkcenter_become_pass }}" obs_ppa_codename: noble # Ubuntu base for Mint 22.3 (NOT 'zena') + v4l2_version: "0.15.4" # source build — distro dkms 0.12.7 fails on kernel 7.x tasks: - name: "[safety] Confirm we are on thinkcenter" ansible.builtin.assert: @@ -113,12 +114,16 @@ retries: 3 delay: 15 - - name: "[apt] Install OBS Studio + virtual-cam + ffmpeg (retry on dpkg lock)" + - name: "[apt] Repair any half-configured dpkg state before installing" + ansible.builtin.command: dpkg --configure -a + register: dpkg_repair + changed_when: "'Setting up' in (dpkg_repair.stdout | default(''))" + failed_when: false + + - name: "[apt] Install OBS Studio + ffmpeg (core — must succeed; retry on lock)" ansible.builtin.apt: name: - obs-studio - - v4l2loopback-dkms - - v4l2loopback-utils - ffmpeg state: present register: obs_install @@ -126,7 +131,83 @@ retries: 3 delay: 15 - - name: "[vcam] Configure v4l2loopback module for the OBS virtual camera" + # ----------------------------------------------------------------------- + # Virtual camera (v4l2loopback) — OPTIONAL / best-effort. The Ubuntu-packaged + # v4l2loopback-dkms (0.12.7) FAILS to build on kernel 7.x (upstream changed + # the v4l2_fh_add/del signature). So we DO NOT install that package. Instead + # we build v4l2loopback {{ v4l2_version }} from source, which supports the + # new kernel API. If anything here fails, we log and continue — OBS itself + # is already installed; only the "Start Virtual Camera" button is affected. + # ----------------------------------------------------------------------- + - name: "[vcam] Ensure the broken distro dkms package is not installed" + ansible.builtin.apt: + name: v4l2loopback-dkms + state: absent + purge: true + register: vcam_dkms_removed + failed_when: false + + - name: "[vcam] Install build prerequisites + v4l2loopback-utils (best-effort)" + ansible.builtin.apt: + name: + - build-essential + - "linux-headers-{{ ansible_kernel }}" + - v4l2loopback-utils + state: present + register: vcam_deps + failed_when: false + until: vcam_deps is succeeded + retries: 2 + delay: 10 + + - name: "[vcam] Is a working v4l2loopback module already present for this kernel?" + ansible.builtin.command: "modinfo -k {{ ansible_kernel }} v4l2loopback" + register: vcam_modinfo + changed_when: false + failed_when: false + + - name: "[vcam] Build + install v4l2loopback {{ v4l2_version }} from source (only if missing)" + when: vcam_modinfo.rc != 0 + block: + - name: "[vcam] Fetch v4l2loopback source" + ansible.builtin.get_url: + url: "https://github.com/v4l2loopback/v4l2loopback/archive/refs/tags/v{{ v4l2_version }}.tar.gz" + dest: "/tmp/v4l2loopback-{{ v4l2_version }}.tar.gz" + mode: "0644" + register: v4l2_dl + until: v4l2_dl is succeeded + retries: 3 + delay: 10 + + - name: "[vcam] Extract source" + ansible.builtin.unarchive: + src: "/tmp/v4l2loopback-{{ v4l2_version }}.tar.gz" + dest: /tmp + remote_src: true + + - name: "[vcam] Build the kernel module (make)" + ansible.builtin.command: + cmd: make + chdir: "/tmp/v4l2loopback-{{ v4l2_version }}" + register: v4l2_make + changed_when: v4l2_make.rc == 0 + + - name: "[vcam] Install the module + utils (make install)" + ansible.builtin.command: + cmd: make install + chdir: "/tmp/v4l2loopback-{{ v4l2_version }}" + register: v4l2_makeinstall + changed_when: v4l2_makeinstall.rc == 0 + + - name: "[vcam] depmod so the new module is discoverable" + ansible.builtin.command: "depmod -a {{ ansible_kernel }}" + changed_when: true + rescue: + - name: "[vcam] Source build failed — record, do NOT fail the play" + ansible.builtin.debug: + msg: "v4l2loopback source build failed; OBS is installed but the Virtual Camera won't work until this is resolved. See /tmp/v4l2loopback-{{ v4l2_version }}." + + - name: "[vcam] Configure v4l2loopback module options for the OBS virtual camera" ansible.builtin.copy: dest: /etc/modprobe.d/obs-v4l2loopback.conf mode: "0644" @@ -134,32 +215,69 @@ options v4l2loopback devices=1 video_nr=10 card_label="OBS Virtual Camera" exclusive_caps=1 register: vcam_conf - - name: "[vcam] Load v4l2loopback now (non-fatal if module build pending a reboot)" + - name: "[vcam] Load v4l2loopback now (best-effort)" community.general.modprobe: name: v4l2loopback state: present register: vcam_load failed_when: false - - name: "[verify] Query obs-studio package status (dpkg, not the GUI binary)" - ansible.builtin.command: dpkg-query -W -f='${Status} ${Version}' obs-studio - register: obs_pkg - changed_when: false - failed_when: false + # --- Verify, with automatic repair + re-verify on failure --------------- + - name: "[verify] Verify OBS (auto-repair + re-verify if it fails)" + block: + - name: "[verify] Query obs-studio package status (dpkg, not the GUI binary)" + ansible.builtin.command: dpkg-query -W -f='${Status} ${Version}' obs-studio + register: obs_pkg + changed_when: false + failed_when: false - - name: "[verify] obs binary present on PATH" - ansible.builtin.command: which obs - register: obs_bin - changed_when: false - failed_when: false + - name: "[verify] obs binary present on PATH" + ansible.builtin.command: which obs + register: obs_bin + changed_when: false + failed_when: false - - name: "[verify] Assert OBS installed" - ansible.builtin.assert: - that: - - "'install ok installed' in (obs_pkg.stdout | default(''))" - - obs_bin.rc == 0 - fail_msg: "OBS Studio not installed. pkg='{{ obs_pkg.stdout | default('') }}', bin rc={{ obs_bin.rc }}" - success_msg: "OBS Studio installed." + - name: "[verify] Assert OBS installed" + ansible.builtin.assert: + that: + - "'install ok installed' in (obs_pkg.stdout | default(''))" + - obs_bin.rc == 0 + fail_msg: "OBS verify failed. pkg='{{ obs_pkg.stdout | default('') }}', bin rc={{ obs_bin.rc }}" + success_msg: "OBS Studio installed." + rescue: + - name: "[reverify] Repair dpkg + reinstall OBS, then re-verify" + block: + - name: "[reverify] dpkg --configure -a" + ansible.builtin.command: dpkg --configure -a + failed_when: false + changed_when: false + + - name: "[reverify] apt-get install -f (fix broken deps)" + ansible.builtin.command: apt-get -y -f install + failed_when: false + changed_when: false + + - name: "[reverify] Reinstall OBS + ffmpeg" + ansible.builtin.apt: + name: [obs-studio, ffmpeg] + state: present + register: obs_reinstall + until: obs_reinstall is succeeded + retries: 3 + delay: 15 + + - name: "[reverify] Re-query package + binary" + ansible.builtin.shell: "dpkg-query -W -f='${Status}' obs-studio; echo '|'; which obs || true" + register: obs_recheck + changed_when: false + + - name: "[reverify] Assert OBS present after repair" + ansible.builtin.assert: + that: + - "'install ok installed' in obs_recheck.stdout" + - "'/obs' in obs_recheck.stdout" + fail_msg: "OBS still not installed after repair attempt: {{ obs_recheck.stdout }}" + success_msg: "OBS Studio installed (after auto-repair)." - name: "[report] OBS summary" ansible.builtin.debug: