radio: run liquidsoap as dedicated non-root user; quiet pulse warnings

- Liquidsoap refuses to start as root (init: security exit). Instead of
  the settings.init.allow_root override, the image now has a dedicated
  'radio' user (audio + pulse-access groups) and the entrypoint drops
  privileges via setpriv after doing its root-only work (volume seeding,
  icecast config render/start, pulse start, chown of the data volume).
  The icecast secret is made group-readable (640 root:radio) because the
  script parses it directly.
- The opam root moved from /root/.opam to /opt/opam so the liquidsoap
  binary AND its stdlib .liq files are readable by the radio user.
  NOTE: this invalidates the cached opam build layer - next build
  recompiles liquidsoap (~15-20 min).
- pulseaudio (PULSE_MODE=internal) now starts with
  --disallow-module-loading: system.pa startup modules still load, only
  later client-requested loads are blocked, and the system-mode warning
  goes away. The 'forcibly disabling SHM mode' notice is inherent to
  system mode and harmless (documented).

Keeps the user's libcurl4-gnutls-dev build-dep fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Michal Tuszowski
2026-07-08 17:14:18 +02:00
committed by Michał Tuszowski
parent fae9c76ede
commit f17cf7fdd8
3 changed files with 41 additions and 9 deletions
+11 -6
View File
@@ -47,10 +47,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
icecast2 jq \
&& rm -rf /var/lib/apt/lists/*
# Group membership for PulseAudio/ALSA access. On the Pi this was done by
# hand for the system-wide pulse socket; in the image it is baked in. The
# 'pulse-access' group comes with the pulseaudio package.
RUN usermod -aG audio,pulse-access root
# Liquidsoap refuses to run as root (security exit), so it gets a dedicated
# user. audio/pulse-access mirror what was done by hand on the Pi for the
# system-wide pulse socket ('pulse-access' comes with the pulseaudio pkg).
RUN useradd --system --create-home --home-dir /var/lib/radio \
--shell /usr/sbin/nologin radio \
&& usermod -aG audio,pulse-access radio
# opam as a static binary so OPAM_VERSION is a real choice (apt would pin us
# to whatever bookworm ships).
@@ -60,13 +62,16 @@ RUN ARCH=$(uname -m) \
&& chmod +x /usr/local/bin/opam
# OCaml switch + liquidsoap and its optional feature libraries in one solve,
# so liquidsoap is compiled WITH those features enabled.
# so liquidsoap is compiled WITH those features enabled. OPAMROOT lives in
# /opt/opam (not /root/.opam) so the unprivileged 'radio' user can read the
# liquidsoap binary AND its stdlib .liq files at runtime.
ENV OPAMROOT=/opt/opam
RUN opam init -y --bare --disable-sandboxing \
&& opam switch create default "${OCAML_VERSION}" \
&& opam install -y "liquidsoap.${LIQUIDSOAP_VERSION}" ${LIQ_OPAM_PACKAGES} \
&& opam clean -a -c -s --logs
ENV PATH="/root/.opam/default/bin:${PATH}"
ENV PATH="/opt/opam/default/bin:${PATH}"
# Build-time smoke test: the binary runs and reports the requested version.
RUN liquidsoap --version
+17 -2
View File
@@ -73,7 +73,12 @@ fi
# 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 \
# --disallow-module-loading: modules from system.pa still load at
# startup; this only blocks later client-requested loads (and
# silences the system-mode warning). The "forcibly disabling SHM"
# notice is inherent to system mode and harmless.
pulseaudio --system --daemonize=yes --disallow-exit \
--disallow-module-loading --exit-idle-time=-1 \
|| echo "WARNING: internal pulseaudio failed to start" >&2
export PULSE_SERVER="${PULSE_SERVER:-unix:/var/run/pulse/native}"
;;
@@ -84,5 +89,15 @@ case "${PULSE_MODE:-internal}" in
;;
esac
# Liquidsoap refuses to run as root (init: security exit), so hand the data
# volume to the dedicated 'radio' user and drop privileges for the main
# process. The script parses the icecast secret directly, so make that one
# file group-readable for 'radio' (kept 640, root-owned).
chown -R radio:radio "$DATA"
chgrp radio "$CREDS" 2>/dev/null && chmod 640 "$CREDS" || true
if ! setpriv --reuid radio --regid radio --init-groups -- test -r "$MUSIC"; then
echo "WARNING: music dir $MUSIC is not readable by the 'radio' user" >&2
fi
cd "$DATA"
exec "$@"
exec setpriv --reuid radio --regid radio --init-groups -- "$@"