Fix: Repository-Struktur für HAOS Add-on Store korrigiert

- ha-addon/ → busch_radio_spotify/ (Ordner muss dem Slug entsprechen)
- repository.yaml im Root hinzugefügt (von HAOS zum Erkennen des Repos benötigt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 12:31:57 +02:00
parent 7b56fdc2bb
commit 1349e6515f
5 changed files with 4 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
ARG BUILD_FROM
# ============================================================
# Build stage: Librespot aus Rust-Quellcode kompilieren
# ============================================================
FROM rust:alpine AS librespot-builder
RUN apk add --no-cache \
musl-dev \
pkgconfig \
openssl-dev \
openssl-libs-static
# Librespot ohne Hardware-Audio-Backends kompilieren.
# --no-default-features entfernt ALSA/PulseAudio; der Pipe-Backend
# ist immer verfügbar und reicht für diesen Anwendungsfall.
RUN cargo install librespot \
--no-default-features \
--root /install
# ============================================================
# Runtime stage: HA-Basis-Image + ffmpeg + icecast
# ============================================================
FROM ${BUILD_FROM}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Abhängigkeiten installieren
# icecast liegt in Alpine's Community-Repository
RUN apk add --no-cache \
bash \
jq \
curl \
python3 \
ffmpeg \
&& apk add --no-cache \
--repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
icecast \
|| apk add --no-cache icecast
# Librespot-Binary aus dem Build-Stage übernehmen
COPY --from=librespot-builder /install/bin/librespot /usr/local/bin/librespot
RUN chmod +x /usr/local/bin/librespot
# Add-on Dateien kopieren
COPY run.sh /run.sh
RUN chmod a+x /run.sh
CMD ["/run.sh"]

View File

@@ -0,0 +1,11 @@
---
# Multi-Architektur Build-Konfiguration für das HA Add-on.
# Jede Architektur bekommt das passende Alpine-basierte HA-Basisimage.
build_from:
aarch64: ghcr.io/home-assistant/aarch64-base:latest
amd64: ghcr.io/home-assistant/amd64-base:latest
armhf: ghcr.io/home-assistant/armhf-base:latest
armv7: ghcr.io/home-assistant/armv7-base:latest
args:
BUILD_FROM: ""

View File

@@ -0,0 +1,51 @@
---
name: "Busch-Radio Spotify Bridge"
version: "1.0.0"
slug: busch_radio_spotify
description: >
Streamt Spotify-Musik als Internet-Radio-Stream für das Busch-Jäger Unterputz-Internetradio.
Das Add-on erzeugt ein virtuelles Spotify Connect-Gerät (via librespot) und stellt das Audio
als MP3-Stream über Icecast bereit.
url: "https://gitea.example.com/retr0/busch-radio-spotify"
arch:
- aarch64
- amd64
- armhf
- armv7
init: false
homeassistant: "2023.1.0"
# ---- Konfigurationsoptionen ----
options:
device_name: "Busch-Radio"
bitrate: 320
stream_port: 8000
stream_mount: "/stream.mp3"
icecast_password: "busch-radio-geheim"
username: ""
password: ""
schema:
device_name: str
bitrate: "list(96|160|320)"
stream_port: port
stream_mount: str
icecast_password: str
username: str?
password: password?
# ---- Netzwerk ----
ports:
"8000/tcp": 8000
ports_description:
"8000/tcp": "Icecast HTTP-Stream (für das Radio)"
# ---- UI ----
panel_icon: mdi:radio
panel_title: "Busch-Radio"
# ---- Sonstiges ----
map:
- config:rw
- share:rw

209
busch_radio_spotify/run.sh Normal file
View File

@@ -0,0 +1,209 @@
#!/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 ─────────────────────────────────────────────────────
mkdir -p /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>
</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})"
# ── Librespot-Argumente zusammenstellen ───────────────────────────────────────
LIBRESPOT_ARGS=(
"--name" "${DEVICE_NAME}"
"--bitrate" "${BITRATE}"
"--backend" "pipe"
"--disable-audio-cache"
"--initial-volume" "100"
)
# Optionale Spotify-Zugangsdaten
SP_USER=$(bashio::config 'username')
SP_PASS=$(bashio::config 'password')
if [ -n "${SP_USER}" ]; then
LIBRESPOT_ARGS+=("--username" "${SP_USER}")
bashio::log.info "Spotify-Konto: ${SP_USER}"
else
bashio::log.info "Kein Spotify-Konto konfiguriert (Zeroconf/mDNS-Erkennung aktiv)"
fi
if [ -n "${SP_PASS}" ]; then
LIBRESPOT_ARGS+=("--password" "${SP_PASS}")
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