thinkcenter-audio: harden — linger-first + wait runtime dir + rescue fallback for user units, apt retries, user/uid asserts

This commit is contained in:
Samuel James 2026-09-03 13:37:59 +00:00
parent aca4801744
commit 6fa9505f5a

View File

@ -94,25 +94,44 @@
that: inventory_hostname == 'thinkcenter'
fail_msg: "Refusing to run — this play is scoped to thinkcenter only."
- name: "[safety] Assert audio_user resolved from Infisical"
ansible.builtin.assert:
that:
- audio_user is defined
- audio_user | length > 0
fail_msg: "audio_user is empty — THINKCENTER_USER not read from Infisical /lc-hosts."
- name: "[facts] Resolve audio_user UID (for XDG_RUNTIME_DIR)"
ansible.builtin.getent:
database: passwd
key: "{{ audio_user }}"
- name: "[facts] Assert the user actually exists on the host"
ansible.builtin.assert:
that: audio_user in ansible_facts.getent_passwd
fail_msg: "User '{{ audio_user }}' does not exist on {{ inventory_hostname }}."
- name: "[facts] Set audio_uid"
ansible.builtin.set_fact:
audio_uid: "{{ ansible_facts.getent_passwd[audio_user][1] }}"
- name: "[apt] Update cache"
- name: "[apt] Update cache (retry on transient network/lock)"
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
register: apt_cache
until: apt_cache is succeeded
retries: 3
delay: 10
- name: "[apt] Install unified PipeWire audio stack + qpwgraph"
- name: "[apt] Install unified PipeWire audio stack + qpwgraph (retry on dpkg lock)"
ansible.builtin.apt:
name: "{{ audio_packages }}"
state: present
register: pkg_install
until: pkg_install is succeeded
retries: 3
delay: 15
- name: "[conflict] Detect a STANDALONE pulseaudio daemon package (not the pw-pulse shim)"
ansible.builtin.shell: "dpkg -l pulseaudio 2>/dev/null | awk '/^ii/{print $2}'"
@ -134,27 +153,59 @@
register: mask_pa
changed_when: mask_pa.rc == 0
- name: "[services] Enable PipeWire user services for {{ audio_user }} (lingering + enable)"
# --- User-session services: made robust ---------------------------------
# systemctl --user needs a live user runtime dir + D-Bus. Enabling linger
# FIRST makes systemd create a persistent /run/user/<uid> even with no GUI
# login, so this works whether or not steve is currently logged in.
- name: "[services] Enable lingering for {{ audio_user }} (persistent user manager)"
ansible.builtin.command: "loginctl enable-linger {{ audio_user }}"
register: linger
changed_when: "'already' not in (linger.stderr | default(''))"
failed_when: false
changed_when: "linger.rc == 0 and 'already enabled' not in (linger.stderr | default(''))"
failed_when: linger.rc != 0 and 'already enabled' not in (linger.stderr | default('') + linger.stdout | default(''))
- name: "[services] Enable pipewire / pipewire-pulse / wireplumber for the user"
become: true
become_user: "{{ audio_user }}"
ansible.builtin.systemd_service:
name: "{{ item }}"
scope: user
enabled: true
loop:
- pipewire.socket
- pipewire-pulse.socket
- wireplumber.service
environment:
XDG_RUNTIME_DIR: "/run/user/{{ audio_uid }}"
failed_when: false
register: user_services
- name: "[services] Wait for user runtime dir /run/user/{{ audio_uid }} to exist"
ansible.builtin.stat:
path: "/run/user/{{ audio_uid }}"
register: runtime_dir
until: runtime_dir.stat.exists
retries: 6
delay: 5
- name: "[services] Enable PipeWire user units (with rescue if session bus is not ready)"
block:
- name: "[services] Enable pipewire / pipewire-pulse / wireplumber for the user"
become: true
become_user: "{{ audio_user }}"
ansible.builtin.systemd_service:
name: "{{ item }}"
scope: user
enabled: true
loop:
- pipewire.socket
- pipewire-pulse.socket
- wireplumber.service
environment:
XDG_RUNTIME_DIR: "/run/user/{{ audio_uid }}"
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ audio_uid }}/bus"
register: user_services
rescue:
- name: "[services] FALLBACK — enable units via systemctl --user by uid (no D-Bus dependency)"
become: true
become_user: "{{ audio_user }}"
ansible.builtin.command: "systemctl --user enable {{ item }}"
loop:
- pipewire.socket
- pipewire-pulse.socket
- wireplumber.service
environment:
XDG_RUNTIME_DIR: "/run/user/{{ audio_uid }}"
register: user_services_fallback
changed_when: "'Created symlink' in (user_services_fallback.stderr | default(''))"
failed_when: false
- name: "[services] Note the fallback path was used"
ansible.builtin.debug:
msg: "User D-Bus not ready; enabled PipeWire units via the systemctl fallback. They will start on next login/linger."
- name: "[verify] Which server does pactl report?"
become: true
@ -162,6 +213,7 @@
ansible.builtin.command: pactl info
environment:
XDG_RUNTIME_DIR: "/run/user/{{ audio_uid }}"
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ audio_uid }}/bus"
register: pactl_info
changed_when: false
failed_when: false