fix(birdnet-pipy): ship the stream supervisor and start icecast to match the upstream (#3079)

Upstream BirdNET-PiPy's start-icecast.sh, after 0.8.8, execs a Python
stream supervisor (deployment/audio/scripts/stream_supervisor.py) that
owns one FFmpeg publisher per source and reloads sources live. This
Dockerfile copied only the shell script, so with that upstream the
icecast service found no supervisor and live streaming stopped.

Copy the scripts directory as a whole, so the build succeeds on 0.8.8 and
later alike, and let the icecast service pick the startup mode:

- supervisor present: run as root. start-icecast.sh hands Icecast to
  icecast2 through <changeowner>, and the supervisor keeps root so it can
  read the root-owned 0600 user_settings.json the API service writes and
  publish its status file into the root-owned data directory.
- supervisor absent (0.8.8): unchanged behaviour under gosu icecast2.

Safe to merge before or after the upstream release that ships the
supervisor.


Claude-Session: https://claude.ai/code/session_01QhVJknwRMMhPFsuDNJRJGi

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Yudong Sun
2026-09-20 03:20:38 -04:00
committed by GitHub
parent 3c22a7e817
commit 47fb7473db
2 changed files with 25 additions and 11 deletions

View File

@@ -98,8 +98,11 @@ RUN sed -i \
-e "s/BIRDNET_HOST = 'model-server'/BIRDNET_HOST = '127.0.0.1'/" \ -e "s/BIRDNET_HOST = 'model-server'/BIRDNET_HOST = '127.0.0.1'/" \
/app/config/settings.py /app/config/settings.py
COPY --from=frontend-builder /src/deployment/audio/scripts/start-icecast.sh /usr/local/bin/start-icecast.sh # Icecast startup scripts, copied as a directory so the build succeeds whether
RUN chown icecast2 /usr/local/bin/start-icecast.sh && chmod 755 /usr/local/bin/start-icecast.sh # or not this upstream ships the Python stream supervisor (added after 0.8.8).
# rootfs/etc/services.d/icecast/run picks the matching startup mode.
COPY --from=frontend-builder /src/deployment/audio/scripts/ /usr/local/lib/birdnet-audio/
RUN chmod 755 /usr/local/lib/birdnet-audio/*.sh
COPY --from=frontend-builder /src/frontend/dist /usr/share/nginx/html COPY --from=frontend-builder /src/frontend/dist /usr/share/nginx/html

View File

@@ -3,14 +3,25 @@
set -euo pipefail set -euo pipefail
export PULSE_SERVER="${PULSE_SERVER:-unix:/run/pulse/native}" export PULSE_SERVER="${PULSE_SERVER:-unix:/run/pulse/native}"
# Ensure the log directory and file are writable by the icecast2 user. AUDIO_DIR=/usr/local/lib/birdnet-audio
# /app/data/logs is created as root by 01-structure.sh, but start-icecast.sh export STREAM_SUPERVISOR="${AUDIO_DIR}/stream_supervisor.py"
# runs via gosu icecast2 and would otherwise fail with "Permission denied"
# on the first log write, taking icecast down before it can boot.
mkdir -p /app/data/logs mkdir -p /app/data/logs
touch /app/data/logs/icecast.log
# Use `icecast2:` (trailing colon) so chown picks the user's primary group,
# which is `icecast` on Debian, not `icecast2`.
chown icecast2: /app/data/logs /app/data/logs/icecast.log
gosu icecast2 /usr/local/bin/start-icecast.sh if [ -f "${STREAM_SUPERVISOR}" ]; then
# Upstream ships the Python stream supervisor (after 0.8.8): run as root.
# start-icecast.sh hands the Icecast daemon to icecast2 itself through
# <changeowner>; the supervisor keeps root because the API service (also
# root) writes user_settings.json with mode 0600 into the root-owned data
# directory, which the supervisor must read, and it writes
# streaming_status.json beside it.
exec "${AUDIO_DIR}/start-icecast.sh"
fi
# Upstream without the supervisor (0.8.8 and earlier): the script owns its
# FFmpeg processes itself and Icecast refuses to start as root, so drop to
# icecast2. /app/data/logs is created by root in 01-structure.sh, so make the
# first log write possible. Use `icecast2:` so chown picks the account's
# primary group (`icecast` on Debian).
touch /app/data/logs/icecast.log
chown icecast2: /app/data/logs /app/data/logs/icecast.log
exec gosu icecast2 "${AUDIO_DIR}/start-icecast.sh"