release / govulncheck (push) Successful in 14s
release / web (push) Failing after 16s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m54s
release / android (push) Successful in 5m5s
release / Build signed APK (releases and dev) (push) Successful in 5m18s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
- Library: the folders, Run scan and a row per scan job, cover art, fingerprints and loudness (off the Overview and Duplicates) - Acquisition: indexers and clients, searching, re-acquisition (off Missing files), Lidarr - Metadata sources: AcoustID, and one provider block for cover art and tags in place of the pasted pair - Email & network: outgoing email, the notification schedule, public address, proxies - Lidarr, outgoing email and the provider block are rebuilt as SettingsGroup rows; failures read as sentences, not codes - Duplicates and Missing files say what their setting is and link to where it is set Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.4 KiB
Markdown
93 lines
3.4 KiB
Markdown
# Hosting Minstrel
|
||
|
||
Minstrel serves plain HTTP on port 4533 and never terminates TLS itself. On a
|
||
home network or a VPN you trust, that is all you need. Once the server is
|
||
reachable from the internet, put it behind a reverse proxy that speaks HTTPS,
|
||
and tell Minstrel about that proxy. This page covers both.
|
||
|
||
## On a LAN or VPN
|
||
|
||
Publish the port to the network and use `http://<host>:4533`:
|
||
|
||
```yaml
|
||
ports: ['4533:4533']
|
||
```
|
||
|
||
If nothing sits in front of Minstrel, set **Admin › Email & network → Client IP
|
||
detection** to 0. It defaults to 1, which assumes one proxy (see below for
|
||
why that matters).
|
||
|
||
## On the internet
|
||
|
||
Three things, all required:
|
||
|
||
1. **Don't publish 4533 to the world.** Bind it to the loopback interface so
|
||
only the proxy on the same host can reach it:
|
||
|
||
```yaml
|
||
ports: ['127.0.0.1:4533:4533']
|
||
```
|
||
|
||
If the proxy runs in another container on the same Docker network, drop
|
||
`ports:` entirely and point the proxy at `minstrel:4533`.
|
||
|
||
2. **Terminate HTTPS at a reverse proxy.** Any proxy works. With Caddy, which
|
||
gets and renews certificates by itself:
|
||
|
||
```
|
||
music.example.com {
|
||
reverse_proxy 127.0.0.1:4533
|
||
}
|
||
```
|
||
|
||
Two settings matter for every proxy:
|
||
- **Don't buffer responses.** Live updates use Server-Sent Events and audio
|
||
is streamed; a buffering proxy holds both back. Caddy streams by default.
|
||
For nginx, set `proxy_buffering off;`.
|
||
- **Allow long-lived responses.** An event stream stays open for as long as
|
||
the app is. Raise the proxy's read timeout well past a minute (nginx:
|
||
`proxy_read_timeout 1h;`).
|
||
|
||
3. **Tell Minstrel where it lives**, in **Admin › Email & network**:
|
||
- **Client IP detection:** the number of proxies in front of Minstrel. One
|
||
proxy (Caddy, Traefik, nginx) is 1; Cloudflare in front of Traefik is 2.
|
||
Minstrel uses this to find the real client address, for login rate
|
||
limits and the sessions list, and to tell whether a request arrived over
|
||
HTTPS.
|
||
- **Public address:** the URL people use, e.g. `https://music.example.com`.
|
||
Password-reset emails link here. **Until it is set, no reset email is
|
||
sent**, so a forged `Host` header can never put a reset token on a link
|
||
to someone else's server.
|
||
|
||
### Why the proxy count matters
|
||
|
||
Minstrel only believes `X-Forwarded-For` and `X-Forwarded-Proto` from the
|
||
number of proxies you configure, counted from the right. Set it too high, or
|
||
leave it at the default of 1 with no proxy in front, and a client can write
|
||
those headers itself: it could claim any address to slip past the per-address
|
||
login limit, or claim HTTPS. With no proxy, set it to 0.
|
||
|
||
When the proxy reports HTTPS, Minstrel marks the session cookie `Secure` and
|
||
sends `Strict-Transport-Security`. Over plain HTTP it does neither, and it
|
||
never redirects to HTTPS, so LAN use keeps working unchanged.
|
||
|
||
## First account
|
||
|
||
A fresh server with no accounts prints a **setup token** in its log:
|
||
|
||
```
|
||
docker compose logs minstrel | grep setup_token
|
||
```
|
||
|
||
Creating the first account (which becomes the admin) requires that token, so
|
||
whoever reaches a newly exposed server first can't claim it. The token
|
||
changes on every restart until an account exists.
|
||
|
||
## Android app
|
||
|
||
The app connects over whatever URL you give it, HTTP or HTTPS. Once the
|
||
server has a public HTTPS address, give the app that address so the session
|
||
cookie never crosses the internet unencrypted. See
|
||
[security notes](./security.md#android-allows-plain-http) for why plain HTTP
|
||
is still allowed.
|