Files
Busch-Radio-Spotify/busch_radio_spotify/run.sh
retr0 76bb0694fe Release v1.0.4: OAuth statt Zeroconf
Zeroconf/mDNS schlägt in Containern fehl weil Port 5353 vom Host
(systemd-resolved/avahi) bereits belegt ist. mdns-sd kann den Socket
nicht teilen → librespot: 'discovery disabled'.

Lösung: OAuth-Authentifizierung (der offiziell unterstützte Weg in
librespot 0.7+). Das Gerät registriert sich bei Spotifys Servern und
erscheint ohne lokales mDNS in der Spotify-App.

- --disable-discovery + --enable-oauth + --system-cache /data/librespot-cache
- Credentials werden in /data persistent gespeichert (data:rw)
- Beim ersten Start: URL im Log → Browser öffnen → Spotify autorisieren
- Ab dem zweiten Start: direkt mit gecachten Credentials

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:06:32 +02:00

227 lines
9.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Busch-Radio Spotify Bridge Startup-Script
#
# Startet:
# 1. Icecast2 HTTP-Stream-Server
# 2. librespot Virtueller Spotify Connect-Empfänger (gibt PCM auf stdout aus)
# 3. ffmpeg Kodiert PCM → MP3 und schickt es als Icecast-Quelle
#
# Bei Absturz wird die komplette Pipeline nach 5 Sekunden neu gestartet.
# ==============================================================================
set -u
# ── Konfiguration lesen ───────────────────────────────────────────────────────
DEVICE_NAME=$(bashio::config 'device_name')
BITRATE=$(bashio::config 'bitrate')
STREAM_PORT=$(bashio::config 'stream_port')
STREAM_MOUNT=$(bashio::config 'stream_mount')
ICECAST_PASSWORD=$(bashio::config 'icecast_password')
bashio::log.info "╔══════════════════════════════════════════════╗"
bashio::log.info "║ Busch-Radio Spotify Bridge v1.0.0 ║"
bashio::log.info "╚══════════════════════════════════════════════╝"
bashio::log.info "Gerätename : ${DEVICE_NAME}"
bashio::log.info "Bitrate : ${BITRATE} kbps"
bashio::log.info "Stream-Port : ${STREAM_PORT}"
bashio::log.info "Stream-Pfad : ${STREAM_MOUNT}"
# ── Verzeichnisse anlegen + Berechtigungen für nobody-User setzen ────────────
# Icecast läuft im Container als root, wechselt aber via changeowner zu nobody.
mkdir -p /var/log/icecast /run/icecast /tmp/busch-radio
chown -R nobody:nobody /var/log/icecast /run/icecast /tmp/busch-radio
# ── Icecast-Webroot ermitteln ─────────────────────────────────────────────────
# Alpine und Debian legen Icecast in unterschiedliche Pfade
ICECAST_WEBROOT=""
for candidate in /usr/share/icecast /usr/share/icecast2; do
if [ -d "$candidate" ]; then
ICECAST_WEBROOT="$candidate"
break
fi
done
if [ -z "$ICECAST_WEBROOT" ]; then
bashio::log.warning "Icecast-Webroot nicht gefunden, verwende /usr/share/icecast"
ICECAST_WEBROOT="/usr/share/icecast"
mkdir -p "${ICECAST_WEBROOT}/web" "${ICECAST_WEBROOT}/admin"
fi
bashio::log.debug "Icecast-Webroot: ${ICECAST_WEBROOT}"
# ── Icecast-Konfiguration generieren ─────────────────────────────────────────
bashio::log.info "Icecast-Konfiguration wird erstellt..."
cat > /tmp/busch-radio/icecast.xml <<ICECAST_EOF
<icecast>
<location>Home Assistant</location>
<admin>admin@localhost</admin>
<limits>
<clients>10</clients>
<sources>2</sources>
<queue-size>524288</queue-size>
<client-timeout>30</client-timeout>
<header-timeout>15</header-timeout>
<source-timeout>10</source-timeout>
<burst-on-connect>0</burst-on-connect>
<burst-size>65536</burst-size>
</limits>
<authentication>
<source-password>${ICECAST_PASSWORD}</source-password>
<relay-password>${ICECAST_PASSWORD}</relay-password>
<admin-user>admin</admin-user>
<admin-password>${ICECAST_PASSWORD}</admin-password>
</authentication>
<hostname>0.0.0.0</hostname>
<listen-socket>
<port>${STREAM_PORT}</port>
</listen-socket>
<mount>
<mount-name>${STREAM_MOUNT}</mount-name>
<public>0</public>
<stream-name>Busch-Radio Spotify Bridge</stream-name>
<stream-description>Spotify via Home Assistant</stream-description>
<bitrate>${BITRATE}</bitrate>
<type>audio/mpeg</type>
</mount>
<fileserve>1</fileserve>
<paths>
<basedir>${ICECAST_WEBROOT}</basedir>
<logdir>/var/log/icecast</logdir>
<webroot>${ICECAST_WEBROOT}/web</webroot>
<adminroot>${ICECAST_WEBROOT}/admin</adminroot>
<pidfile>/run/icecast/icecast.pid</pidfile>
</paths>
<logging>
<!-- "-" loggt auf stderr → wird von HA erfasst -->
<accesslog>-</accesslog>
<errorlog>-</errorlog>
<loglevel>2</loglevel>
<logsize>10000</logsize>
</logging>
<security>
<chroot>0</chroot>
<!-- Icecast wechselt nach dem Start zu nobody verhindert den root-Fehler -->
<changeowner>
<user>nobody</user>
<group>nobody</group>
</changeowner>
</security>
</icecast>
ICECAST_EOF
# ── Icecast starten ───────────────────────────────────────────────────────────
bashio::log.info "Icecast wird gestartet (Port: ${STREAM_PORT})..."
icecast -c /tmp/busch-radio/icecast.xml &
ICECAST_PID=$!
# Kurz warten bis Icecast hochgefahren ist
sleep 3
if ! kill -0 "${ICECAST_PID}" 2>/dev/null; then
bashio::log.fatal "Icecast konnte nicht gestartet werden! Prüfe die Konfiguration."
exit 1
fi
bashio::log.info "Icecast läuft (PID: ${ICECAST_PID})"
# ── Credential-Cache-Verzeichnis ─────────────────────────────────────────────
# Credentials werden in /data gespeichert (persistentes Add-on Verzeichnis).
CREDENTIAL_CACHE="/data/librespot-cache"
mkdir -p "${CREDENTIAL_CACHE}"
# ── Librespot-Argumente zusammenstellen ───────────────────────────────────────
# Zeroconf/mDNS funktioniert nicht zuverlässig in Containern, da Port 5353
# vom Host (systemd-resolved/avahi) bereits belegt ist.
# Stattdessen: OAuth-Authentifizierung. Das Gerät registriert sich bei
# Spotifys Servern und erscheint in der App ohne lokales mDNS.
LIBRESPOT_ARGS=(
"--name" "${DEVICE_NAME}"
"--bitrate" "${BITRATE}"
"--backend" "pipe"
"--disable-audio-cache"
"--initial-volume" "100"
"--disable-discovery"
"--enable-oauth"
"--system-cache" "${CREDENTIAL_CACHE}"
)
# Prüfen ob bereits Credentials gecacht sind
if [ -f "${CREDENTIAL_CACHE}/credentials.json" ]; then
bashio::log.info "Gespeicherte Credentials gefunden Direktstart."
else
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bashio::log.info "ERSTER START Spotify-Autorisierung erforderlich!"
bashio::log.info ""
bashio::log.info "librespot gibt gleich eine URL aus. Öffne diese"
bashio::log.info "im Browser und melde dich mit deinem Spotify-Konto an."
bashio::log.info "Danach startet das Add-on automatisch durch."
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
fi
# ── Icecast-Source-URL ────────────────────────────────────────────────────────
ICECAST_SOURCE_URL="icecast://source:${ICECAST_PASSWORD}@localhost:${STREAM_PORT}${STREAM_MOUNT}"
# ── Stream-URL ausgeben ───────────────────────────────────────────────────────
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bashio::log.info "Stream-URL für das Busch-Jäger Radio:"
bashio::log.info " http://<homeassistant-ip>:${STREAM_PORT}${STREAM_MOUNT}"
bashio::log.info ""
bashio::log.info "Icecast-Statusseite:"
bashio::log.info " http://<homeassistant-ip>:${STREAM_PORT}"
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bashio::log.info "Wähle '${DEVICE_NAME}' in der Spotify-App als Abspielgerät."
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# ── Aufräumen bei SIGTERM ─────────────────────────────────────────────────────
cleanup() {
bashio::log.info "Add-on wird beendet..."
kill "${ICECAST_PID}" 2>/dev/null || true
exit 0
}
trap cleanup SIGTERM SIGINT
# ── Haupt-Schleife: Pipeline starten und bei Absturz neu starten ──────────────
#
# Signalfluss:
# librespot (Spotify Connect) → stdout (raw PCM 44100Hz/S16LE/Stereo)
# ↓
# ffmpeg (PCM → MP3 Encoding) → Icecast (HTTP-Stream)
# ↓
# Busch-Jäger Radio (HTTP-Client)
#
# Wenn entweder librespot oder ffmpeg abstürzt, wird die komplette Pipeline
# nach RESTART_DELAY Sekunden neu gestartet.
RESTART_DELAY=5
ATTEMPT=0
while true; do
ATTEMPT=$((ATTEMPT + 1))
bashio::log.info "Pipeline-Start (Versuch ${ATTEMPT})..."
librespot "${LIBRESPOT_ARGS[@]}" \
| ffmpeg \
-hide_banner \
-loglevel warning \
-f s16le -ar 44100 -ac 2 \
-i pipe:0 \
-codec:a libmp3lame \
-b:a "${BITRATE}k" \
-q:a 2 \
-f mp3 \
"${ICECAST_SOURCE_URL}" \
|| true
bashio::log.warning "Pipeline beendet. Neustart in ${RESTART_DELAY}s..."
sleep "${RESTART_DELAY}"
done