mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-14 21:38:38 +00:00
docker: Liquidsoap radio container (opam-built, version-selectable)
Dockerises the radio_conjurer.liq environment on Debian bookworm: - Dockerfile.radio builds Liquidsoap through opam with build args for OPAM_VERSION (static binary), OCAML_VERSION (default 4.14.2; 2.1.x needs OCaml 4.x - prod ran 4.13.0, pass it for parity) and LIQUIDSOAP_VERSION (default 2.1.4, matching the script) - LIQ_OPAM_PACKAGES installs exactly the features the script uses: mad+lame (mp3), cry (icecast), taglib (tags/replaygain), pulseaudio (mic/out), samplerate, inotify (watch-reload), ffmpeg - ffmpeg.pref verdict: the Pi pin forced Debian's FFmpeg 5.x family (libavcodec59...) over RPi OS repo builds because the ocaml-ffmpeg bindings are compiled against those sonames; bookworm ships them natively so the container needs no pin (documented, not copied) - pulse-access/audio group memberships baked in (were manual on the Pi); PULSE_MODE=internal runs a system-wide pulse with a null sink so the unmodified script works on a headless VM (mic = silence), host mode mounts the real socket, none for edited scripts - mounts mirror the script's hardcoded paths (/home/pi/Conjurer, /home/pi/MediaFolder/mp3) so radio_conjurer.liq runs unmodified; entrypoint seeds script+params on first run (never overwrites), creates missing playlists, seeds placeholder icecast credentials (loud warning) and generates a silent emergency mp3 when the single() file is missing - compose.radio.yaml: extra_hosts maps the script's host="radio" to ICECAST_HOST_IP; exposes 54321 (bot's RADIO_HARBOR /skip) and 9999 - docs: radio section with build args, pulse modes and the ffmpeg.pref / groups verdicts Part 2 of this PR (splitting bot<->radio comms out of the musician into a radio+betoniarka container) follows on this branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
# Prepare the radio volumes so radio_conjurer.liq boots on a fresh VM, then
|
||||
# start (or point at) PulseAudio and exec liquidsoap. Existing files are never
|
||||
# overwritten - live-edited script/params/playlists always win.
|
||||
set -e
|
||||
|
||||
DATA="${RADIO_DATA_DIR:-/home/pi/Conjurer}"
|
||||
MUSIC="${RADIO_MUSIC_DIR:-/home/pi/MediaFolder/mp3}"
|
||||
mkdir -p "$DATA" "$MUSIC"
|
||||
|
||||
# Seed the script + persistent interactive params from the image on first run.
|
||||
[ -e "$DATA/radio_conjurer.liq" ] || cp /app/radio_conjurer.liq "$DATA/"
|
||||
if [ ! -e "$DATA/script.params" ]; then
|
||||
if [ -e /app/script.params ]; then cp /app/script.params "$DATA/"; else : > "$DATA/script.params"; fi
|
||||
fi
|
||||
|
||||
# Playlists + logs the script watches/writes; empty files keep it happy until
|
||||
# the musician/betoniarka populate them.
|
||||
for f in all_playlist.playlist priority_queue.playlist hit.playlist \
|
||||
request.playlist jingles.playlist persistence.log; do
|
||||
[ -e "$DATA/$f" ] || : > "$DATA/$f"
|
||||
done
|
||||
|
||||
# Icecast credentials are a secret - never baked into the image. Seed a
|
||||
# placeholder so the script can parse the JSON, but shout about it.
|
||||
if [ ! -e "$DATA/icecast_credentials.json" ]; then
|
||||
printf '{\n"password" : "CHANGE_ME"\n}\n' > "$DATA/icecast_credentials.json"
|
||||
echo "WARNING: seeded placeholder icecast_credentials.json - the icecast" >&2
|
||||
echo " output will be rejected until you set the real password!" >&2
|
||||
fi
|
||||
|
||||
# single() aborts the whole script when its file is missing; guarantee the
|
||||
# emergency fallback exists (5s of silence beats a dead radio).
|
||||
EMERGENCY="$MUSIC/Youtube/Dr. Peacock - Trip to Ireland [GvrvQTUbUcA].mp3"
|
||||
if [ ! -e "$EMERGENCY" ]; then
|
||||
mkdir -p "$MUSIC/Youtube"
|
||||
if ffmpeg -loglevel error -f lavfi -i anullsrc=r=44100:cl=stereo -t 5 \
|
||||
-codec:a libmp3lame -q:a 9 "$EMERGENCY"; then
|
||||
echo "WARNING: emergency track was missing - generated silent placeholder" >&2
|
||||
else
|
||||
echo "WARNING: could not create emergency track; single() may abort" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# PulseAudio wiring:
|
||||
# internal (default) - system-wide pulse inside the container with a null
|
||||
# sink (see /etc/pulse/system.pa); no sound hardware
|
||||
# needed, mic path reads silence.
|
||||
# host - use a socket mounted from the host; set PULSE_SERVER
|
||||
# (e.g. unix:/tmp/pulseaudio.socket) in the env file.
|
||||
# none - you edited the script to drop pulse in/out.
|
||||
case "${PULSE_MODE:-internal}" in
|
||||
internal)
|
||||
pulseaudio --system --daemonize=yes --disallow-exit --exit-idle-time=-1 \
|
||||
|| echo "WARNING: internal pulseaudio failed to start" >&2
|
||||
export PULSE_SERVER="${PULSE_SERVER:-unix:/var/run/pulse/native}"
|
||||
;;
|
||||
host)
|
||||
[ -n "$PULSE_SERVER" ] || echo "WARNING: PULSE_MODE=host but PULSE_SERVER is unset" >&2
|
||||
;;
|
||||
none)
|
||||
;;
|
||||
esac
|
||||
|
||||
cd "$DATA"
|
||||
exec "$@"
|
||||
Reference in New Issue
Block a user