mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-14 21:38:38 +00:00
radio: /srv/betoniarka paths, in-container Icecast, drop ffmpeg.pref
Per review of the first radio-container cut:
- radio_conjurer.liq: replace the cargo-culted /home/pi/Conjurer and
/home/pi/MediaFolder paths with the container layout -
/srv/betoniarka/data (playlists, script.params, persistence/radio logs),
/srv/betoniarka/music (library + emergency track) and
/srv/betoniarka/secrets/icecast_credentials.json;
interactive.persistent now uses an absolute path;
output.icecast targets host="localhost" (icecast lives in the container)
- Icecast2 now runs INSIDE the radio container: entrypoint renders
/etc/icecast2/icecast.xml from docker/icecast.xml.tpl, filling
source/admin/relay passwords from the secret provisioned at install time
(same pattern as the bot's netrc); optional admin_password/relay_password
fields default to password; icecast starts as its unprivileged user;
port 8000 exposed for listeners; extra_hosts hack removed
- compose.radio.yaml: same-path mounts for /srv/betoniarka/{data,music,secrets}
- ffmpeg.pref removed from the repo (the pin only mattered against the
RPi OS repo; bookworm ships the right FFmpeg 5.x natively) - verdict
preserved in the docs and the legacy installer note updated
- docs: updated radio section (volumes, secret provisioning, wire-up)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -43,6 +43,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libavdevice-dev libswresample-dev libswscale-dev libpostproc-dev \
|
||||
ffmpeg \
|
||||
pulseaudio pulseaudio-utils \
|
||||
icecast2 jq \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Group membership for PulseAudio/ALSA access. On the Pi this was done by
|
||||
@@ -75,20 +76,25 @@ RUN liquidsoap --version
|
||||
COPY docker/pulse-system.pa /etc/pulse/system.pa
|
||||
|
||||
# The script and its persistent params are seeded into the data volume on
|
||||
# first run (never overwritten), so live edits survive image rebuilds.
|
||||
# first run (never overwritten), so live edits survive image rebuilds. The
|
||||
# icecast config is rendered from the template at startup with passwords
|
||||
# taken from the secrets volume (never baked into the image).
|
||||
WORKDIR /app
|
||||
COPY conjurer_musician/radio_conjurer.liq ./radio_conjurer.liq
|
||||
COPY conjurer_musician/script.params ./script.params
|
||||
COPY docker/icecast.xml.tpl ./icecast.xml.tpl
|
||||
COPY docker/entrypoint.radio.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Mount points mirror the paths hardcoded in radio_conjurer.liq, so the
|
||||
# script runs unmodified: data at /home/pi/Conjurer, music at
|
||||
# /home/pi/MediaFolder/mp3.
|
||||
VOLUME ["/home/pi/Conjurer", "/home/pi/MediaFolder/mp3"]
|
||||
# Volume layout matches the paths inside radio_conjurer.liq:
|
||||
# /srv/betoniarka/data - playlists, script.params, persistence/radio logs
|
||||
# /srv/betoniarka/music - the mp3 library
|
||||
# /srv/betoniarka/secrets - icecast_credentials.json (provisioned at install)
|
||||
VOLUME ["/srv/betoniarka/data", "/srv/betoniarka/music", "/srv/betoniarka/secrets"]
|
||||
|
||||
# 54321 = harbor /skip (the bot's RADIO_HARBOR), 9999 = interactive harbor.
|
||||
EXPOSE 54321 9999
|
||||
# 8000 = icecast (listeners), 54321 = harbor /skip (the bot's RADIO_HARBOR),
|
||||
# 9999 = interactive harbor.
|
||||
EXPOSE 8000 54321 9999
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
CMD ["liquidsoap", "/home/pi/Conjurer/radio_conjurer.liq"]
|
||||
CMD ["liquidsoap", "/srv/betoniarka/data/radio_conjurer.liq"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Radio (Liquidsoap) VM. Run from the repository root:
|
||||
# Radio (Liquidsoap + Icecast) VM. Run from the repository root:
|
||||
# docker compose -f docker/compose.radio.yaml up -d --build
|
||||
#
|
||||
# Version pins are build args - override via environment, e.g.:
|
||||
@@ -21,19 +21,21 @@ services:
|
||||
# host = mount the host pulse socket below and set PULSE_SERVER
|
||||
PULSE_MODE: ${PULSE_MODE:-internal}
|
||||
# PULSE_SERVER: unix:/tmp/pulseaudio.socket
|
||||
extra_hosts:
|
||||
# radio_conjurer.liq streams to host="radio" - point it at icecast.
|
||||
- "radio:${ICECAST_HOST_IP:-192.168.1.12}"
|
||||
# Hostname icecast reports in its status/YP pages:
|
||||
ICECAST_HOSTNAME: ${ICECAST_HOSTNAME:-localhost}
|
||||
ports:
|
||||
- "8000:8000" # icecast - listeners tune in here
|
||||
- "54321:54321" # harbor /skip - the bot's CONJURER_RADIO_HARBOR
|
||||
- "9999:9999" # interactive harbor (keep LAN-only!)
|
||||
volumes:
|
||||
# Playlists, logs, script.params, icecast_credentials.json, the script
|
||||
# itself (seeded on first run) - paths mirror the ones hardcoded in
|
||||
# radio_conjurer.liq so it runs unmodified.
|
||||
- /srv/radio/data:/home/pi/Conjurer
|
||||
# Playlists, logs, script.params and the script itself (seeded on first
|
||||
# run) - same paths inside and outside the container.
|
||||
- /srv/betoniarka/data:/srv/betoniarka/data
|
||||
# The mp3 library. Writable because the entrypoint seeds a silent
|
||||
# emergency-fallback mp3 when the hardcoded single() file is missing.
|
||||
- /srv/radio/music:/home/pi/MediaFolder/mp3
|
||||
- /srv/betoniarka/music:/srv/betoniarka/music
|
||||
# icecast_credentials.json - provision at install like the other
|
||||
# services' secrets (a CHANGE_ME placeholder is seeded if absent).
|
||||
- /srv/betoniarka/secrets:/srv/betoniarka/secrets
|
||||
# PULSE_MODE=host: uncomment and adjust
|
||||
# - /tmp/pulseaudio.socket:/tmp/pulseaudio.socket
|
||||
|
||||
+34
-12
@@ -1,12 +1,14 @@
|
||||
#!/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.
|
||||
# Prepare the radio volumes, start the in-container Icecast server, wire up
|
||||
# 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"
|
||||
DATA="${RADIO_DATA_DIR:-/srv/betoniarka/data}"
|
||||
MUSIC="${RADIO_MUSIC_DIR:-/srv/betoniarka/music}"
|
||||
SECRETS="${RADIO_SECRETS_DIR:-/srv/betoniarka/secrets}"
|
||||
CREDS="$SECRETS/icecast_credentials.json"
|
||||
mkdir -p "$DATA" "$MUSIC" "$SECRETS"
|
||||
|
||||
# Seed the script + persistent interactive params from the image on first run.
|
||||
[ -e "$DATA/radio_conjurer.liq" ] || cp /app/radio_conjurer.liq "$DATA/"
|
||||
@@ -21,14 +23,34 @@ for f in all_playlist.playlist priority_queue.playlist hit.playlist \
|
||||
[ -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
|
||||
# The icecast secret is provisioned at install time, like the other services'
|
||||
# secrets (bot: /srv/conjurer/secrets/.netrc). A placeholder keeps the stack
|
||||
# bootable, but both icecast and the stream stay locked until you fix it.
|
||||
if [ ! -e "$CREDS" ]; then
|
||||
printf '{\n"password" : "CHANGE_ME"\n}\n' > "$CREDS"
|
||||
echo "WARNING: $CREDS was missing - seeded a CHANGE_ME placeholder." >&2
|
||||
echo " Put the real password there (see docs) and restart." >&2
|
||||
fi
|
||||
|
||||
# Render /etc/icecast2/icecast.xml from the template with passwords from the
|
||||
# secret. Optional fields admin_password/relay_password default to password.
|
||||
SOURCE_PW=$(jq -r '.password' "$CREDS")
|
||||
ADMIN_PW=$(jq -r '.admin_password // .password' "$CREDS")
|
||||
RELAY_PW=$(jq -r '.relay_password // .password' "$CREDS")
|
||||
ICECAST_HOSTNAME="${ICECAST_HOSTNAME:-localhost}"
|
||||
sed -e "s|__SOURCE_PASSWORD__|$SOURCE_PW|" \
|
||||
-e "s|__ADMIN_PASSWORD__|$ADMIN_PW|" \
|
||||
-e "s|__RELAY_PASSWORD__|$RELAY_PW|" \
|
||||
-e "s|__HOSTNAME__|$ICECAST_HOSTNAME|" \
|
||||
/app/icecast.xml.tpl > /etc/icecast2/icecast.xml
|
||||
chown icecast2:icecast /etc/icecast2/icecast.xml 2>/dev/null || true
|
||||
chmod 640 /etc/icecast2/icecast.xml
|
||||
|
||||
# Start Icecast in the background as its unprivileged user.
|
||||
mkdir -p /var/log/icecast2 && chown -R icecast2:icecast /var/log/icecast2
|
||||
su -s /bin/sh icecast2 -c "icecast2 -b -c /etc/icecast2/icecast.xml" \
|
||||
|| echo "WARNING: icecast2 failed to start - the stream output will retry" >&2
|
||||
|
||||
# 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"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<!-- Icecast2 config template for the radio container.
|
||||
The entrypoint substitutes the __*_PASSWORD__ placeholders from
|
||||
/srv/betoniarka/secrets/icecast_credentials.json and writes the result
|
||||
to /etc/icecast2/icecast.xml. Never commit real passwords here. -->
|
||||
<icecast>
|
||||
<location>Wolne Ksiestwo Baluty</location>
|
||||
<admin>admin@localhost</admin>
|
||||
|
||||
<limits>
|
||||
<clients>64</clients>
|
||||
<sources>4</sources>
|
||||
<queue-size>524288</queue-size>
|
||||
<client-timeout>30</client-timeout>
|
||||
<header-timeout>15</header-timeout>
|
||||
<source-timeout>10</source-timeout>
|
||||
<burst-on-connect>1</burst-on-connect>
|
||||
<burst-size>65535</burst-size>
|
||||
</limits>
|
||||
|
||||
<authentication>
|
||||
<source-password>__SOURCE_PASSWORD__</source-password>
|
||||
<relay-password>__RELAY_PASSWORD__</relay-password>
|
||||
<admin-user>admin</admin-user>
|
||||
<admin-password>__ADMIN_PASSWORD__</admin-password>
|
||||
</authentication>
|
||||
|
||||
<hostname>__HOSTNAME__</hostname>
|
||||
|
||||
<listen-socket>
|
||||
<port>8000</port>
|
||||
<bind-address>0.0.0.0</bind-address>
|
||||
</listen-socket>
|
||||
|
||||
<http-headers>
|
||||
<header name="Access-Control-Allow-Origin" value="*" />
|
||||
</http-headers>
|
||||
|
||||
<fileserve>1</fileserve>
|
||||
|
||||
<paths>
|
||||
<basedir>/usr/share/icecast2</basedir>
|
||||
<logdir>/var/log/icecast2</logdir>
|
||||
<webroot>/usr/share/icecast2/web</webroot>
|
||||
<adminroot>/usr/share/icecast2/admin</adminroot>
|
||||
<alias source="/" destination="/status.xsl"/>
|
||||
</paths>
|
||||
|
||||
<logging>
|
||||
<accesslog>access.log</accesslog>
|
||||
<errorlog>error.log</errorlog>
|
||||
<loglevel>3</loglevel>
|
||||
<logsize>10000</logsize>
|
||||
<logarchive>0</logarchive>
|
||||
</logging>
|
||||
</icecast>
|
||||
Reference in New Issue
Block a user