- Dependency Updates & Cleanup

- Frontend Security Enhancements (Nginx/Entrypoint)
- Docker Networking & Binding
- Code Bug Fix
This commit is contained in:
2026-05-19 10:11:15 +02:00
parent 6146bf18b0
commit 8247692aa2
2285 changed files with 1801 additions and 485963 deletions
+652 -117
View File
@@ -1,171 +1,706 @@
# audioserver — Docker Compose stack
# audioserver
Runs on the **Audio Server (Pi 5)**.
Combines Snapserver, Mopidy, and the audiocontrol web frontend in one stack.
`audioserver` is a Docker Compose based home audio stack for a Raspberry Pi
audio server. It combines Mopidy, Snapserver, Icecast, a React control UI,
Bandcamp/Spotify credential helpers, and optional native iOS/Android clients.
```
internet / LAN
┌───────────────────────────────────────┐ Docker network
│ frontend (Nginx :80) │
│ /api/hifiberry/ ──────────────────────────→ BeoCreate Pi :5005
│ /api/mopidy/rpc → mopidy:6680 │
│ /ws/mopidy → mopidy:6680 │
│ /ws/snapcast → snapserver:1705 │
├───────────────────────────────────────┤
│ mopidy (:6680, :6600 MPD) │──→ /audio/mopidy.fifo ─┐
├───────────────────────────────────────┤ │
snapserver (:1704, :1705, :1780) │←── /audio/mopidy.fifo ─┘
│ │←── /audio/turntable.fifo ← host service
└───────────────────────────────────────┘
The intended deployment target is an always-on Linux host on the same LAN as
Snapcast clients and a HiFiBerry BeoCreate DSP device.
## What This Stack Provides
- **Mopidy music server** for local files, Spotify, YouTube, Beets, Iris, MPD,
and HTTP JSON-RPC control.
- **Snapserver multi-room audio** with streams for Mopidy, turntable input, and
several TCP sources.
- **Icecast FLAC stream** for browser and mobile playback outside Snapcast.
- **audiocontrol web UI** served by Nginx for Mopidy, Snapcast, and BeoCreate
DSP control.
- **Nginx API proxy** so the browser talks to one origin instead of many backend
services.
- **Turntable capture helper** that records from a HiFiBerry ALSA device on the
host and writes PCM into Snapserver.
- **Now-playing helper service** for Snapcast metadata and audio recognition.
- **GrooveAudio mobile apps** for iOS and Android streaming/control.
## Architecture
```text
LAN / browser / mobile app
|
v
frontend container, Nginx :${FRONTEND_PORT:-8180}
|-- React audiocontrol UI
|-- /api/mopidy/* -> mopidy:6680
|-- /ws/mopidy -> mopidy:6680
|-- /api/snapcast/rpc -> snapserver:1780/jsonrpc
|-- /ws/snapcast -> snapserver:1780/jsonrpc
|-- /api/hifiberry/* -> BeoCreate DSP REST API
|-- /api/bandcamp/* -> bandcamp-api:8091
|-- /api/spotify/* -> bandcamp-api:8091
|-- /api/now-playing/* -> host now-playing service :8090
|-- /stream.flac -> icecast:8000/stream.flac
mopidy container
|-- writes 48 kHz / 16-bit / stereo PCM to /audio/mopidy.fifo
|-- mirrors PCM to /audio/cava.fifo and /audio/stream.fifo
stream-relay container
|-- reads /audio/stream.fifo
|-- encodes FLAC with ffmpeg
|-- publishes to icecast:8000/stream.flac
snapserver container
|-- reads /audio/mopidy.fifo
|-- reads /audio/turntable.fifo
|-- connects to configured TCP audio sources
|-- serves Snapcast clients on port 1704
host turntable-capture systemd service
|-- records from HiFiBerry ALSA capture device
|-- writes raw PCM to /audio/turntable.fifo
```
---
## Repository Layout
```text
.
|-- docker-compose.yml # Main service stack
|-- .env.example # Environment template
|-- frontend/ # Nginx image and proxy template
|-- audiocontrol/ # React/Vite control UI
|-- mopidy/ # Mopidy image and config
|-- snapserver/ # Snapserver image and config
|-- icecast/ # Icecast image and config template
|-- stream-relay/ # FIFO-to-Icecast FLAC relay
|-- bandcamp-api/ # Credential helper microservice
|-- scripts/ # Host helper scripts and systemd units
|-- GrooveAudio (Android)/ # Native Android client
|-- GrooveAudio (iOS)/ # Native iOS client
|-- MOBILE_APP_INSTRUCTIONS.md # Extra mobile/networking notes
`-- IMPLEMENTATION_SUMMARY.md # Historical implementation notes
```
## Prerequisites
```bash
sudo apt install docker.io docker-compose-plugin
sudo usermod -aG docker $USER # logout & back in
```
### Host
---
- Linux host, preferably Raspberry Pi OS or Debian/Ubuntu.
- Docker Engine and Docker Compose plugin.
- `git`, `curl`, `alsa-utils`, and `systemd` for host helper services.
- A reachable BeoCreate/HiFiBerry DSP API if you want DSP control from the UI.
- A capture-capable ALSA device if you want turntable input.
## One-time setup
### 1. Clone / copy this directory to the Pi 5
Install the common packages on Debian/Raspberry Pi OS:
```bash
scp -r audioserver/ pi@audioserver.local:/opt/audiocontrol/stack
cd /opt/audiocontrol/stack
sudo apt update
sudo apt install -y docker.io docker-compose-plugin git curl alsa-utils
sudo usermod -aG docker "$USER"
```
### 2. Copy the React source
Log out and back in after adding your user to the `docker` group.
The `audiocontrol/` directory (from `audiocontrol.tar.gz`) must sit next to `docker-compose.yml`:
### Optional Host Packages
The now-playing helper can use Flask, NumPy, CAVA, and SongRec:
```bash
tar -xzf audiocontrol.tar.gz -C /opt/audiocontrol/stack/
sudo apt install -y python3 python3-pip cava
pip3 install -r scripts/requirements.txt
```
### 3. Configure
SongRec CLI is optional and must be installed separately if you want acoustic
recognition through `snapcast-now-playing.py`.
### Development Tools
Only needed when developing outside Docker:
- Node.js 20+ for `audiocontrol`.
- Android Studio for the Android client.
- Xcode for the iOS client.
## First-Time Setup
### 1. Clone The Repository
Choose a stable location. The systemd units in `scripts/` expect a fixed path,
so avoid temporary directories.
```bash
git clone <repo-url> /home/admin/appdata/audioserver
cd /home/admin/appdata/audioserver
```
Any path is fine, but if you use a different one, update the `WorkingDirectory`,
`ExecStart`, and `EnvironmentFile` paths in the systemd service files before
installing them.
### 2. Create Your Environment File
```bash
cp .env.example .env
nano .env # set BEOCREATE_HOST, TZ, HIFIBERRY_CARD, etc.
nano .env
```
### 4. Create audio FIFOs
At minimum, review these values:
```dotenv
BEOCREATE_HOST=beocreate.local
BEOCREATE_PORT=13141
AUDIO_PIPES_DIR=/opt/audiocontrol/pipes
MUSIC_DIR=/opt/audiocontrol/music
FRONTEND_PORT=8180
SNAPSERVER_HOST=192.168.178.100
HIFIBERRY_CARD=sndrpihifiberry
ICECAST_LISTENER_PASSWORD=groove_listen
```
Set `AUDIOCONTROL_PASSWORD` if the web UI should require HTTP Basic Auth:
```dotenv
AUDIOCONTROL_USERNAME=admin
AUDIOCONTROL_PASSWORD=change-me
```
Leave `AUDIOCONTROL_PASSWORD` empty to disable Basic Auth.
### 3. Create Host Directories
Create the default music and FIFO directories:
```bash
sudo mkdir -p /opt/audiocontrol/music
sudo bash scripts/init-pipes.sh
```
### 5. Install the turntable capture service
If you use a non-default FIFO path, pass it to the script and keep `.env`
aligned:
```bash
sudo cp scripts/turntable-capture.sh /opt/audiocontrol/scripts/
sudo chmod +x /opt/audiocontrol/scripts/turntable-capture.sh
sudo cp scripts/turntable-capture.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now turntable-capture
sudo bash scripts/init-pipes.sh /srv/audioserver/pipes
```
---
The script creates:
## Start the stack
- `mopidy.fifo`
- `turntable.fifo`
- `cava.fifo`
- `stream.fifo`
### 4. Add Music
Put local music in the path configured by `MUSIC_DIR`. By default:
```bash
sudo mkdir -p /opt/audiocontrol/music
sudo rsync -av /path/to/music/ /opt/audiocontrol/music/
```
The directory is mounted read-only into the Mopidy container at `/music`.
### 5. Build And Start The Stack
```bash
docker compose up -d --build
docker compose logs -f # watch startup
docker compose ps
```
---
## Service ports
| Service | Port | Purpose |
|-------------|-------|-------------------------------|
| Frontend | 80 | Web UI + API proxy |
| Snapserver | 1704 | Snapcast audio streaming |
| Snapserver | 1705 | JSON-RPC WebSocket |
| Snapserver | 1780 | Snapcast HTTP API |
| Mopidy | 6600 | MPD (optional, host access) |
Mopidy's HTTP port (6680) is **not** exposed on the host — accessed only via the frontend Nginx proxy.
---
## Updating
Watch startup logs:
```bash
# Pull latest changes, rebuild, restart
docker compose down
docker compose up -d --build
docker compose logs -f
```
## Mopidy local library scan
Open the web UI:
```text
http://<audio-server-host>:8180
```
Use the port from `FRONTEND_PORT` if you changed it.
### 6. Scan The Mopidy Local Library
After adding files to `MUSIC_DIR`, scan the library:
```bash
docker compose exec mopidy mopidy local scan
```
## Useful commands
Restart Mopidy if needed:
```bash
docker compose ps # service status
docker compose logs snapserver # snapserver logs
docker compose exec mopidy bash # shell into mopidy
docker compose restart snapserver # restart one service
```
---
## Audio routing
```
Turntable
→ HiFiBerry HAT (ALSA hw:0)
→ turntable-capture.sh (host)
→ /opt/audiocontrol/pipes/turntable.fifo
→ Snapserver (Turntable stream)
→ BeoCreate Pi (Snapclient)
Mopidy
→ GStreamer filesink
→ /opt/audiocontrol/pipes/mopidy.fifo
→ Snapserver (Mopidy stream)
→ BeoCreate Pi (Snapclient)
```
Both are 48 kHz / 16-bit / stereo to match the BeoCreate DSP profile.
---
## Troubleshooting
**Frontend shows "unreachable" for BeoCreate DSP**
Check that `BEOCREATE_HOST` in `.env` resolves from the Pi 5. Test:
```bash
curl http://beocreate.local:5005/api/volume
```
**No audio from turntable**
Check the ALSA card name in `.env`:
```bash
arecord -l # lists available capture devices
```
**Mopidy FIFO blocks on startup**
Snapserver must open the FIFO before Mopidy can write to it.
The `depends_on: snapserver` in docker-compose.yml handles this, but if Snapserver is slow, restart Mopidy:
```bash
docker compose restart mopidy
```
**Snapclient on BeoCreate can't connect**
Port 1704 must be reachable from the BeoCreate Pi. Check firewall:
### 7. Install Turntable Capture
Turntable capture runs on the host, not in Docker, because it needs ALSA device
access.
First check the capture card name:
```bash
sudo ufw allow 1704/tcp # if ufw is active
arecord -l
```
Set `HIFIBERRY_CARD` in `.env` to the matching ALSA card name. Then install the
service:
```bash
sudo cp scripts/turntable-capture.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now turntable-capture
```
Check logs:
```bash
journalctl -u turntable-capture -f
```
If the repo is not located at `/home/admin/appdata/audioserver`, edit
`/etc/systemd/system/turntable-capture.service` before enabling it.
### 8. Optional: Install Now-Playing Service
The frontend proxies `/api/now-playing/` to a host service on port `8090`.
Install Python dependencies:
```bash
pip3 install -r scripts/requirements.txt
```
Install the service:
```bash
sudo cp scripts/snapcast-now-playing.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now snapcast-now-playing
```
Check logs:
```bash
journalctl -u snapcast-now-playing -f
```
As with the turntable service, update paths in the unit file if the repo lives
somewhere other than `/home/admin/appdata/audioserver`.
## Service Ports
| Port | Service | Bind | Purpose |
| ---: | --- | --- | --- |
| `8180` | frontend | configurable via `FRONTEND_PORT` | Web UI, API proxy, `/stream.flac` proxy |
| `1704` | Snapserver | `0.0.0.0` | Snapcast client audio stream |
| `1705` | Snapserver TCP RPC | `RPC_BIND`, default `127.0.0.1` | Direct Snapcast JSON-RPC TCP |
| `1780` | Snapserver HTTP RPC | `RPC_BIND`, default `127.0.0.1` | Direct Snapcast HTTP/WebSocket API |
| `8000` | Icecast | `STREAM_BIND`, default `0.0.0.0` | Direct FLAC stream server |
| `6600` | Mopidy MPD | Docker network only by default | MPD access inside stack |
| `6680` | Mopidy HTTP | Docker network only by default | Proxied through frontend |
| `8090` | now-playing helper | host service | Proxied through frontend |
Mopidy HTTP is intentionally not exposed on the host in `docker-compose.yml`.
Use the frontend proxy for browser and mobile control.
## Important URLs
Replace `<host>` with your audio server hostname or IP.
```text
Web UI: http://<host>:8180/
Mopidy JSON-RPC proxy: http://<host>:8180/api/mopidy/rpc
Mopidy WebSocket proxy: ws://<host>:8180/ws/mopidy
Snapcast RPC proxy: http://<host>:8180/api/snapcast/rpc
Snapcast WebSocket: ws://<host>:8180/ws/snapcast
Icecast direct stream: http://<host>:8000/stream.flac
Proxied stream: http://<host>:8180/stream.flac
```
## Environment Reference
| Variable | Default | Description |
| --- | --- | --- |
| `BEOCREATE_HOST` | `beocreate.local` | Hostname/IP for the BeoCreate DSP REST API. |
| `BEOCREATE_PORT` | `13141` | BeoCreate DSP REST API port. |
| `AUDIO_PIPES_DIR` | `/opt/audiocontrol/pipes` | Host directory containing audio FIFOs. |
| `MUSIC_DIR` | `/opt/audiocontrol/music` | Host music library mounted into Mopidy. |
| `FRONTEND_PORT` | `8180` | Host port for the Nginx/React frontend. |
| `AUDIOCONTROL_USERNAME` | `admin` | Basic Auth username when auth is enabled. |
| `AUDIOCONTROL_PASSWORD` | empty | Basic Auth password. Empty disables auth. |
| `RPC_BIND` | `127.0.0.1` | Host bind address for direct Snapserver RPC ports. |
| `STREAM_BIND` | `0.0.0.0` | Host bind address for Icecast port `8000`. |
| `TZ` | `Europe/Vienna` | Container timezone. |
| `HIFIBERRY_CARD` | `sndrpihifiberry` | ALSA capture card name for turntable input. |
| `ICECAST_SOURCE_PASSWORD` | `groove_source` | Source password used by `stream-relay`. |
| `ICECAST_RELAY_PASSWORD` | `groove_relay` | Icecast relay password. |
| `ICECAST_ADMIN_PASSWORD` | `groove_admin` | Icecast admin password. |
| `ICECAST_LISTENER_PASSWORD` | `groove_listen` | Listener password used by mobile apps. |
| `SPOTIFY_CLIENT_ID` | empty | Spotify application client ID for Mopidy Spotify. |
| `SPOTIFY_CLIENT_SECRET` | empty | Spotify application client secret. |
| `SNAPSERVER_HOST` | `192.168.178.100` | LAN address of this audio server, used by Snapserver/Iris. |
| `BEETS_HOST` | `192.168.178.100` | Host running Beets web service. |
| `TCP_POP_HOST` | `192.168.178.95` | TCP source host for Snapserver stream `pop`. |
| `TCP_TP_HOST` | `192.168.178.89` | TCP source host for Snapserver stream `tp`. |
| `TCP_FRAMEWORK_HOST` | `192.168.178.183` | TCP source host for Snapserver stream `framework`. |
## Operating The Stack
Start or restart everything:
```bash
docker compose up -d --build
```
Stop everything:
```bash
docker compose down
```
Show service status:
```bash
docker compose ps
```
Follow all logs:
```bash
docker compose logs -f
```
Follow one service:
```bash
docker compose logs -f mopidy
docker compose logs -f snapserver
docker compose logs -f frontend
docker compose logs -f icecast
```
Restart one service:
```bash
docker compose restart mopidy
docker compose restart snapserver
docker compose restart frontend
```
Open a shell in a container:
```bash
docker compose exec mopidy bash
docker compose exec frontend sh
```
Rebuild after config or source changes:
```bash
docker compose build
docker compose up -d
```
## Updating
```bash
git pull
docker compose pull
docker compose up -d --build
docker compose ps
```
If `.env.example` changed, compare it with your `.env` and copy over any new
variables you need.
## Audio Routing
### Mopidy To Snapcast
Mopidy writes raw PCM into `/audio/mopidy.fifo`. Snapserver reads that FIFO as
the `mopidy` stream:
```text
Mopidy -> /audio/mopidy.fifo -> Snapserver stream "mopidy" -> Snapcast clients
```
Mopidy also mirrors the same audio into:
- `/audio/cava.fifo` for visualization.
- `/audio/stream.fifo` for Icecast streaming.
### Mopidy To Icecast
The `stream-relay` container reads `/audio/stream.fifo`, encodes FLAC with
ffmpeg, and publishes to Icecast:
```text
Mopidy -> /audio/stream.fifo -> stream-relay -> Icecast /stream.flac
```
The stream is available directly on port `8000` and through the frontend proxy.
### Turntable To Snapcast
The host `turntable-capture` service records from ALSA and writes raw PCM to
the turntable FIFO:
```text
Turntable / HiFiBerry capture -> turntable-capture -> /audio/turntable.fifo
-> Snapserver stream "turntable" -> Snapcast clients
```
All primary PCM paths use 48 kHz, 16-bit, stereo audio.
## Web UI Development
The Docker stack builds the React app automatically from `audiocontrol/`.
For local frontend development:
```bash
cd audiocontrol
npm install
npm run dev
```
The Vite development server runs on:
```text
http://localhost:5173
```
Build manually:
```bash
npm run build
```
Preview the production build:
```bash
npm run preview
```
## Mobile Apps
The native clients live in:
- `GrooveAudio (Android)/`
- `GrooveAudio (iOS)/`
They are designed to use:
- Port `8000` for the Icecast FLAC stream.
- Port `8180` for proxied Mopidy and Snapcast control APIs.
- `ICECAST_LISTENER_PASSWORD` from `.env` for stream authentication.
Default app settings assume:
```text
Host: 192.168.178.100
Password: groove_listen
```
Change those in the app settings if your server IP or listener password differs.
See `MOBILE_APP_INSTRUCTIONS.md` for additional notes about mobile and remote
access.
### Android
```bash
cd "GrooveAudio (Android)"
./gradlew assembleDebug
```
Or open the folder in Android Studio and run the app on a device.
### iOS
Open `GrooveAudio (iOS)/GrooveAudio.xcodeproj` in Xcode, select a device or
simulator, and run the app.
## Spotify And Bandcamp
### Spotify
1. Create a Spotify application in the Spotify Developer Dashboard.
2. Put the credentials in `.env`:
```dotenv
SPOTIFY_CLIENT_ID=...
SPOTIFY_CLIENT_SECRET=...
```
3. Recreate Mopidy:
```bash
docker compose up -d --build mopidy
```
The frontend also includes a proxied credential store under `/api/spotify/`
served by `bandcamp-api`.
### Bandcamp
The `bandcamp-api` service stores Bandcamp credentials or cookies in the
`bandcamp_config` Docker volume. The frontend proxies it under
`/api/bandcamp/`.
## Security Notes
- Set `AUDIOCONTROL_PASSWORD` before exposing the UI outside a trusted LAN.
- Keep `RPC_BIND=127.0.0.1` unless direct Snapserver RPC access is required.
- Prefer the frontend proxy for control APIs instead of exposing backend ports.
- Icecast binds to `0.0.0.0` by default through `STREAM_BIND`; restrict it if
you do not want direct LAN access to port `8000`.
- Do not commit `.env`; it may contain passwords and API credentials.
## Troubleshooting
### Frontend Does Not Load
Check the frontend container and logs:
```bash
docker compose ps frontend
docker compose logs -f frontend
```
Verify the configured port:
```bash
docker compose port frontend 80
```
### Basic Auth Fails
Check `AUDIOCONTROL_USERNAME` and `AUDIOCONTROL_PASSWORD` in `.env`, then
recreate the frontend:
```bash
docker compose up -d --force-recreate frontend
```
### BeoCreate DSP Is Unreachable
Confirm the host and port in `.env`:
```bash
set -a
. ./.env
set +a
curl "http://$BEOCREATE_HOST:$BEOCREATE_PORT/api/volume"
```
If that fails from the host, fix DNS/IP routing before debugging Docker.
### No Mopidy Audio In Snapcast
Confirm the FIFOs exist:
```bash
ls -l "${AUDIO_PIPES_DIR:-/opt/audiocontrol/pipes}"
```
Then check service logs:
```bash
docker compose logs -f mopidy snapserver
```
If Mopidy started before Snapserver was ready, restart Mopidy:
```bash
docker compose restart mopidy
```
### Local Music Is Missing
Confirm `MUSIC_DIR` contains music files and rescan:
```bash
docker compose exec mopidy mopidy local scan
docker compose restart mopidy
```
### No Turntable Audio
Check ALSA devices:
```bash
arecord -l
```
Update `HIFIBERRY_CARD` in `.env`, then restart the service:
```bash
sudo systemctl restart turntable-capture
journalctl -u turntable-capture -f
```
### Icecast Stream Is Silent Or Missing
Check Icecast and relay logs:
```bash
docker compose logs -f icecast stream-relay
```
Verify the stream URL:
```bash
curl -I http://localhost:8000/stream.flac
curl -I http://localhost:8180/stream.flac
```
The stream only carries Mopidy audio. If Mopidy is stopped or idle, the stream
may be silent.
### Snapcast Clients Cannot Connect
Snapcast clients need access to port `1704` on the audio server:
```bash
sudo ufw allow 1704/tcp
```
Confirm Snapserver is listening:
```bash
docker compose logs -f snapserver
```
### Now-Playing API Returns Errors
Check the host service:
```bash
systemctl status snapcast-now-playing
journalctl -u snapcast-now-playing -f
```
Make sure the service unit paths match the actual repository path and that
Python dependencies from `scripts/requirements.txt` are installed.
## Backup Notes
Important persistent state lives in:
- `.env`
- `MUSIC_DIR`
- Docker volume `audioserver_mopidy_data`
- Docker volume `audioserver_bandcamp_config`
- Any systemd unit overrides you create
List volumes:
```bash
docker volume ls | grep audioserver
```
Back up named volumes with your normal Docker volume backup process.