Files
Busch-Radio-Spotify/busch_radio_spotify/run.sh
retr0 6f4e408190 Release v1.0.7: OAuth-Server korrekt auf 0.0.0.0 gebunden
Die bisherigen Patches zielten auf Muster, die in oauth/src/lib.rs
nicht vorhanden sind. Der tatsächliche Bind-Aufruf ist
TcpListener::bind(socket_address) – dieser wird jetzt direkt durch
SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port) ersetzt.

Dadurch ist der OAuth-Callback-Server unter http://<ha-ip>:5588
erreichbar. Der Benutzer ersetzt nach der Spotify-Weiterleitung
127.0.0.1 durch die HA-IP in der Adressleiste.

run.sh erkennt die HA-IP automatisch und zeigt eine klare
Schritt-für-Schritt-Anleitung beim ersten Start.

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

250 lines
11 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}"
# ── IP-Adresse des Hosts ermitteln (für OAuth-Anleitung) ─────────────────────
HA_IP=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
if [ -z "${HA_IP}" ]; then
HA_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
fi
if [ -z "${HA_IP}" ]; then
HA_IP="<homeassistant-ip>"
fi
# ── 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 "1) librespot gibt gleich eine 'Browse to:'-URL aus."
bashio::log.info " Kopiere diese URL und öffne sie im Browser."
bashio::log.info ""
bashio::log.info "2) Melde dich mit deinem Spotify-Konto an"
bashio::log.info " und erteile die Berechtigung."
bashio::log.info ""
bashio::log.info "3) Dein Browser wird danach weitergeleitet auf:"
bashio::log.info " http://127.0.0.1:5588/login?code=..."
bashio::log.info " Diese Seite schlägt fehl das ist normal."
bashio::log.info ""
bashio::log.info "4) WICHTIG: Ersetze in der Adressleiste"
bashio::log.info " '127.0.0.1' durch '${HA_IP}'"
bashio::log.info " → http://${HA_IP}:5588/login?code=..."
bashio::log.info " und drücke Enter."
bashio::log.info ""
bashio::log.info "5) Bei Erfolg siehst du: 'Autorisierung erfolgreich!'"
bashio::log.info " Das Add-on startet danach automatisch."
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