Security: - New internal/netguard package: podcast feed/enclosure fetching now refuses to dial non-public IPs (loopback/RFC1918/link-local/cloud metadata/CGNAT), validated per-connection including across redirects, closing an SSRF gap where a malicious or compromised feed URL could probe the server's internal network. Configurable via VIBESONIC_PODCASTS_ALLOW_PRIVATE_NETWORKS for intentional LAN feeds. - Podcast episode downloads capped at 4GB (was unbounded, disk-exhaustion risk) - Cover art decoding now rejects images above a 40-megapixel budget before full decode (decompression-bomb guard) - HTTP server sets ReadHeaderTimeout (slowloris hardening) - Admin folder-scan redirect now validated as same-site instead of trusting the raw Referer header (open-redirect fix) - Legacy Subsonic token/salt auth now uses a constant-time comparison Web UI completion: - Persistent player: shuffle and repeat (off/all/one) controls, wired into queue advance/prev logic and persisted like other player state - Admin user-permissions form: adminRole toggle (with a guard against demoting the last remaining admin) and per-user music-folder access checkboxes, backed by existing schema/enforcement that was previously unreachable from the UI - Playlist M3U8 import/export Docs: added docs/client-compatibility.md (honest tracking template — no client testing has been performed), docs/deployment.md, docs/security.md, docs/backup.md; refreshed README and cross-links; added a footnote in docs/subsonic-compatibility.md distinguishing spec-standard from VibeSonic-original OpenSubsonic extensions. Also added Go fuzz tests for the external-auth CIDR/IP parser and the LRC lyrics parser. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014r98AtcBbevpscWLsfwVvv
6.9 KiB
Deployment
VibeSonic is a single static binary plus ffmpeg/ffprobe on PATH
(and optionally mpv, for the jukebox feature). Docker is convenient
but never required for normal operation.
Configuration
All configuration is via VIBESONIC_* environment variables — see
docs/configuration.md for the full list. There is
no config file.
Running natively
go build -o vibesonic ./cmd/vibesonic
export VIBESONIC_DATA_DIR=/var/lib/vibesonic
export VIBESONIC_MUSIC_DIRS=/music
export VIBESONIC_SESSION_SECRET="$(openssl rand -hex 32)"
./vibesonic
For a persistent deployment, see
systemd/vibesonic.service — install it
with:
sudo useradd --system --home /var/lib/vibesonic --shell /usr/sbin/nologin vibesonic
sudo install -m 0755 vibesonic /usr/local/bin/vibesonic
sudo cp systemd/vibesonic.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now vibesonic
The unit runs as a dedicated vibesonic user (not DynamicUser,
because it needs a stable UID with read access to your music library —
DynamicUser allocates a random UID per start, which doesn't compose
well with a fixed set of file permissions on external media). Grant that
user read access to your music folders:
sudo usermod -aG <group-that-owns-your-music> vibesonic
# or, more simply, make the music tree group- or world-readable
StateDirectory=vibesonic in the unit creates and owns
/var/lib/vibesonic (mode 0700) for you — don't create it manually
with different permissions first. The unit also applies standard
hardening (ProtectSystem=strict, NoNewPrivileges, PrivateDevices,
etc.) and Restart=on-failure.
Remember to set a real VIBESONIC_SESSION_SECRET in the unit (or in an
EnvironmentFile=) — the shipped example is a placeholder.
Running with Docker
docker build -t vibesonic .
Runtime image is debian:bookworm-slim with ffmpeg, mpv, and CA
certificates installed, running as a non-root vibesonic user (uid
10001). See Dockerfile.
Example Compose file (also in docker-compose.yml):
services:
vibesonic:
image: vibesonic
ports:
- "8080:8080"
volumes:
- ./data:/var/lib/vibesonic
- /mnt/music:/music:ro
environment:
VIBESONIC_SESSION_SECRET: "change-me-to-a-random-string"
Mount your music read-only (:ro) — VibeSonic never writes to it.
./data (or a named volume) holds the SQLite database, cover art cache,
and downloaded podcast episodes; back it up per
docs/backup.md.
Reverse proxy
Run VibeSonic behind a reverse proxy for TLS termination in any
real deployment; the examples below forward to VibeSonic on
127.0.0.1:8080.
VibeSonic does not use WebSockets. Streaming and transcoded responses are ordinary long-lived HTTP responses using standard range requests — make sure your proxy doesn't buffer the entire response body before forwarding it (buffering breaks range requests and adds needless latency to playback start).
Caddy
music.example.com {
reverse_proxy 127.0.0.1:8080
}
Caddy handles TLS automatically and streams request/response bodies without buffering by default — no extra configuration needed for range requests or large responses.
nginx
server {
listen 443 ssl;
server_name music.example.com;
ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem;
client_max_body_size 0;
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
}
}
proxy_buffering off matters here: nginx buffers proxied responses to
disk/memory by default, which delays the start of playback and range
responses. client_max_body_size 0 avoids nginx rejecting large file
uploads if the upload feature is used.
Traefik
http:
routers:
vibesonic:
rule: "Host(`music.example.com`)"
service: vibesonic
tls:
certResolver: letsencrypt
services:
vibesonic:
loadBalancer:
servers:
- url: "http://127.0.0.1:8080"
Traefik streams responses without buffering by default, same as Caddy.
Reverse proxy + external (trusted-proxy) authentication
If you're using docs/external-auth.md to have the
proxy handle SSO (OIDC, SAML, HTTP Basic Auth, ...) for the browser UI,
two things are essential:
-
The proxy must strip any client-supplied identity header before inserting its own. If it doesn't, a client can set
Remote-User: admin(or whatever header name you configured) on its own request and have the proxy forward it unchanged, impersonating any user. Every proxy example below includes this. -
/rest/*must bypass the proxy's SSO layer. Real Subsonic/ OpenSubsonic clients (mobile apps, desktop players) authenticate directly against VibeSonic's own token/API-key scheme and have no way to complete a browser-based SSO flow (OIDC redirect, SAML POST, etc.). Exclude/rest/*from the SSO requirement so those clients keep working, while still letting/rest/*reach VibeSonic normally — VibeSonic's own/rest/*auth is independent of external auth either way.
Caddy example (with forward_auth for SSO + header stripping)
music.example.com {
# Strip any client-supplied identity header before forward_auth
# potentially sets a real one, so a client can't forge it.
header_down -Remote-User
@needsAuth {
not path /rest/*
}
forward_auth @needsAuth auth.example.com:9091 {
uri /verify
copy_headers Remote-User
}
reverse_proxy 127.0.0.1:8080
}
nginx example (with auth_request for SSO + header stripping)
server {
# ... TLS config as above ...
location /rest/ {
# Subsonic/OpenSubsonic clients: no SSO, straight through.
proxy_pass http://127.0.0.1:8080;
proxy_set_header Remote-User ""; # never forward a client-supplied value
}
location / {
auth_request /sso-verify;
auth_request_set $remote_user $upstream_http_remote_user;
proxy_set_header Remote-User $remote_user;
proxy_pass http://127.0.0.1:8080;
}
location = /sso-verify {
internal;
proxy_pass http://auth.example.com/verify;
proxy_set_header X-Original-URI $request_uri;
}
}
Either way, also set VIBESONIC_EXTAUTH_TRUSTED_SOURCES to only the
proxy's own address (e.g. 127.0.0.1/32 if it's on the same host) —
VibeSonic independently verifies the real TCP peer, so even a
misconfigured proxy can't be used to spoof identity from the internet
directly. See docs/external-auth.md for that
check in detail.