radio: martwy PulseAudio nie zabija już całego radia #23

Merged
gitea merged 1 commits from radio-pulse-resilience into main 2026-08-12 15:11:16 +00:00
Owner

Łańcuch z loga

Daemon startup failed (pulse) → Connection refused na input.pulseaudio_0 / buffer.consumer_0 / pulse_outShutdown started! → restart → i tak w kółko.

Dlaczego pętla się sama napędza

/run jest częścią zapisywalnej warstwy kontenera, a restart: unless-stopped restartuje ten sam kontener — więc /run/pulse/pid po ubitym demonie przeżywa restart, kolejny start PA odbija się o „już działam", i mamy samo-podtrzymującą się pętlę. Entrypoint czyści teraz nieaktualny pid/socket tylko gdy żaden proces pulseaudio faktycznie nie żyje.

Zmiany

  1. Czyszczenie stale pulse state przed startem demona (jw.).
  2. Głośny komunikat gdy PA i tak nie wstaje: co to oznacza (liquidsoap pójdzie w pętlę) i jak z tego wyjść (Icecast nie potrzebuje żadnego urządzenia audio) — zamiast jednolinijkowego WARNING ginącego nad tracebackiem.
  3. output.pulseaudio(fallible=true, radio) — monitor lokalny może paść bez wywalania zegara i całego radia.
  4. RADIO_FORCE_SCRIPT=1 — re-seeduje radio_conjurer.liq z obrazu (stary ląduje jako .bak).

Ważne: skrypt w kontenerze jest STARY

Entrypoint celowo nigdy nie nadpisuje $DATA/radio_conjurer.liq (ręczne edycje wygrywają) — ale to znaczy też, że poprawki z obrazu nigdy nie docierały do wolumenu zaseedowanego dawno temu. Dowód z loga: Nonexistent file or ill-formed URI "/srv/betoniarka/musicjingiel4.mp3" — tej ścieżki nie ma w repo, więc działający kontener ma inną, ręcznie edytowaną wersję. Stąd RADIO_FORCE_SCRIPT.

Czego NIE zmieniam

input.pulseaudio() (mikrofon) zostaje twardą zależnością — usunięcie go zmienia zachowanie audio (mic wchodzi w add/switch z live_enabled domyślnie true), a nie mam tu runtime'u liquidsoap, żeby to zweryfikować. Opisane komentarzem w skrypcie; dla kontenera headless właściwa konfiguracja to zakomentować oba tory pulse.

🤖 Generated with Claude Code

## Łańcuch z loga `Daemon startup failed` (pulse) → `Connection refused` na `input.pulseaudio_0` / `buffer.consumer_0` / `pulse_out` → `Shutdown started!` → restart → i tak w kółko. ## Dlaczego pętla się sama napędza `/run` jest częścią **zapisywalnej warstwy kontenera**, a `restart: unless-stopped` restartuje **ten sam** kontener — więc `/run/pulse/pid` po ubitym demonie **przeżywa restart**, kolejny start PA odbija się o „już działam", i mamy samo-podtrzymującą się pętlę. Entrypoint czyści teraz nieaktualny pid/socket **tylko** gdy żaden proces pulseaudio faktycznie nie żyje. ## Zmiany 1. **Czyszczenie stale pulse state** przed startem demona (jw.). 2. **Głośny komunikat** gdy PA i tak nie wstaje: co to oznacza (liquidsoap pójdzie w pętlę) i jak z tego wyjść (Icecast **nie potrzebuje** żadnego urządzenia audio) — zamiast jednolinijkowego WARNING ginącego nad tracebackiem. 3. **`output.pulseaudio(fallible=true, radio)`** — monitor lokalny może paść bez wywalania zegara i całego radia. 4. **`RADIO_FORCE_SCRIPT=1`** — re-seeduje `radio_conjurer.liq` z obrazu (stary ląduje jako `.bak`). ## Ważne: skrypt w kontenerze jest STARY Entrypoint celowo **nigdy nie nadpisuje** `$DATA/radio_conjurer.liq` (ręczne edycje wygrywają) — ale to znaczy też, że poprawki z obrazu **nigdy nie docierały** do wolumenu zaseedowanego dawno temu. Dowód z loga: `Nonexistent file or ill-formed URI "/srv/betoniarka/musicjingiel4.mp3"` — tej ścieżki **nie ma w repo**, więc działający kontener ma inną, ręcznie edytowaną wersję. Stąd `RADIO_FORCE_SCRIPT`. ## Czego NIE zmieniam `input.pulseaudio()` (mikrofon) zostaje twardą zależnością — usunięcie go zmienia zachowanie audio (`mic` wchodzi w `add`/`switch` z `live_enabled` domyślnie **true**), a **nie mam tu runtime'u liquidsoap**, żeby to zweryfikować. Opisane komentarzem w skrypcie; dla kontenera headless właściwa konfiguracja to zakomentować **oba** tory pulse. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gitea self-assigned this 2026-08-12 15:08:29 +00:00
gitea added 1 commit 2026-08-12 15:08:29 +00:00
radio: stop a dead PulseAudio from crash-looping the whole radio
CI / compile (pull_request) Successful in 6s
CI / unit (pull_request) Successful in 23s
CI / integration (pull_request) Successful in 24s
build / build (push) Successful in 13m5s
CI / compile (push) Successful in 5s
CI / unit (push) Successful in 22s
CI / integration (push) Failing after 24s
c2e6b8e60e
Field report: the radio died and kept restarting. The log chain is
unambiguous - "Daemon startup failed" (pulse), then Connection refused on
input.pulseaudio_0 / buffer.consumer_0 / pulse_out, then "Shutdown
started!", then round again.

Three fixes:

* Clear stale pulse runtime state before starting the daemon. /run is part
  of the container's writable layer, so "docker restart" - and the loop that
  restart:unless-stopped produces - preserves /run/pulse/pid from the killed
  daemon; the next start then refuses with "Daemon startup failed", which is
  what makes the loop self-sustaining. We only remove it when no pulseaudio
  process is actually alive.

* When pulse still won't start, say so loudly and explain the consequence
  and the way out (Icecast needs no sound device; comment the pulse tor out),
  instead of a one-line WARNING that gets lost above the traceback.

* output.pulseaudio(fallible=true): the local monitor output can now fail
  without failing its clock and tearing down the radio. The mic input stays
  a hard dependency - documented inline, since removing it changes audio
  behaviour and cannot be verified without a liquidsoap runtime.

Also: RADIO_FORCE_SCRIPT=1 re-seeds radio_conjurer.liq from the image
(keeping a .bak). The live script is deliberately never overwritten so hand
edits win - but that also means image fixes never reached volumes seeded
long ago, which is why a running radio can still hold a stale script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gitea merged commit c2e6b8e60e into main 2026-08-12 15:11:16 +00:00
gitea deleted branch radio-pulse-resilience 2026-08-12 15:11:16 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gitea/conjurer#23