feat(audio): HDMI audio works out of the box — pipewire in ISO + router daemon + ELD boot-race heal

Root cause of silent HDMI on kiosk TVs (framework-pt, LG TV):
1) fresh ISOs installed NO audio stack even though the kiosk launcher
   expects PipeWire-Pulse — add pipewire/pipewire-pulse/pipewire-alsa/
   wireplumber/alsa-utils to the rootfs and put the archipelago user in
   'audio' (linger user manager gets no logind seat ACLs on /dev/snd);
2) the kiosk's boot-time Xorg modeset races the i915→HDA audio-component
   bind, the ELD notify is lost, and every HDMI profile stays
   'available: no' forever. Verified live: one off/on modeset re-delivers
   the ELD and WirePlumber immediately switches to HDMI.

New archipelago-audio-router daemon (same splice-from-configs pattern as
the kiosk, ISO + include_str! self-heal): routes the card to the best
available HDMI profile, keeps the default sink on it, migrates live
streams on hot-plug, unmutes (HDMI forced 100% — the TV owns volume), and
re-modesets a connected external output once when no ELD reports a
monitor. bootstrap::ensure_audio_stack() installs packages + router on
already-deployed kiosk nodes via OTA (apt through systemd-run — the
service sandbox keeps /usr and dpkg read-only). main.rs also spawns the
pine satellite keeper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-23 15:28:47 -04:00
co-authored by Claude Fable 5
parent 852ffc5b18
commit 8a450bb8f8
5 changed files with 298 additions and 1 deletions
+9
View File
@@ -386,6 +386,15 @@ async fn main() -> Result<()> {
// flags) on already-deployed nodes via OTA; no-op if the kiosk isn't installed.
tokio::spawn(bootstrap::ensure_kiosk_hardened());
// HDMI audio: install the PipeWire stack + audio-router daemon on kiosk
// nodes (older ISOs shipped no audio stack; the router also heals the
// boot-time ELD race that leaves HDMI silently unavailable).
tokio::spawn(bootstrap::ensure_audio_stack());
// Pine voice: re-point IP-pinned Wyoming satellite entries (speakers) when
// DHCP renumbering strands them — HA never re-resolves on its own.
tokio::spawn(api::rpc::wyoming_satellite_keeper());
// Spawn periodic container snapshot (for crash recovery)
crash_recovery::spawn_snapshot_task(config.data_dir.clone());