The same rewrite as d191a282, for four packages whose comments carried the
most history: tuning stories, quotes from the operator, single-install
measurements in library_scale.go, and 'used to' accounts of bugs. The
constraint each story protected stays as a present-tense reason (why tier 3
is reduced not removed, why the randomised arms take an order seed, why the
seed artist is eligible in Songs-like). Comments naming system_cron.go and
config.RecommendationConfig, neither of which holds what they claimed, now
name the scheduler and internal/recsettings.
Checked: non-comment tokens identical to HEAD; gofmt clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Minstrel
A self-hosted music server that picks what to play next.
Minstrel serves your music library to the web, to its own Android app, and to any Subsonic client. Shuffle, radio, likes and play history are worked out on the server, so every device gets the same mixes and the same history. It can also find, download and file the albums you want, through a search service and a download client you run yourself. It is for anyone who keeps their own music collection and wants it to sound like more than a random shuffle.
Quick start
# compose.yaml
services:
minstrel:
image: git.fabledsword.com/bvandeusen/minstrel:latest
ports: ['4533:4533']
volumes:
- ./music:/music # your library; must be writable by uid 1000
- minstrel-data:/app/data # art and caches
environment:
MINSTREL_DATABASE_URL: postgres://minstrel:minstrel@db:5432/minstrel?sslmode=disable
MINSTREL_LIBRARY_SCAN_PATHS: /music
depends_on: [db]
db:
image: postgres:17
environment:
POSTGRES_USER: minstrel
POSTGRES_PASSWORD: minstrel
POSTGRES_DB: minstrel
volumes: [pgdata:/var/lib/postgresql/data] # users, likes, history: back this up
volumes:
minstrel-data:
pgdata:
docker compose up -d
docker compose logs minstrel | grep setup_token
Open http://<host>:4533/register and create the first account with that setup token. The first account is the administrator.
First run
- Let the first scan finish. Minstrel scans the library on startup. Watch it, or scan again, on the Admin page (
/admin). Later scans only pick up what changed. - Install the Android app. In Settings (
/settings), the Install the Android app card downloads the app this server ships. It updates itself from your server after that. - Invite the household. In Admin › Users (
/admin/users), make an invite token or turn on open registration. Each person gets their own likes, history and recommendations. - Optional: connect services. Add a search service and a download client in Admin › Acquisition (
/admin/acquisition). Each person adds their own ListenBrainz token in Settings › Connections.
Configuration
The quick start sets everything that's required. These are the settings people most often change:
| Variable | What it does |
|---|---|
MINSTREL_DATABASE_URL |
Postgres connection string. Required. |
MINSTREL_LIBRARY_SCAN_PATHS |
Library folders inside the container, colon-separated (/music:/audiobooks). They are used on first start only; after that, change them in Admin › Library. |
MINSTREL_BRANDING_APP_NAME |
The name shown in the header, the browser tab and link previews. |
MINSTREL_LOG_LEVEL |
debug, info, warn or error. |
Search services, download clients, ListenBrainz, email and recommendation tuning are set in the web UI and take effect without a restart. The full file-based surface is in config.example.yaml; to use it, mount your copy at /etc/smartmusic/config.yaml.
Setup pitfalls
- The library mount has to be writable by uid 1000. Minstrel files the albums it downloads into the library, and it deletes a file when an admin asks it to. It never moves, renames or retags files that were already there. Mount the library
:roto forbid writes: those actions then refuse and say why. MINSTREL_LIBRARY_SCAN_PATHSnames the path inside the container (/music), not the folder on your host.- On the internet, put it behind HTTPS. Plain
http://is fine on a network you trust. For a public server, bind the port to127.0.0.1and put a reverse proxy in front: see docs/hosting.md.
Updating and backups
docker compose pull && docker compose up -d
Database migrations run at startup. Image tags:
:latest: the stable release. Most people should run this.:<commit-sha>: one fixed build. Pin one to stop updates, or to roll back.:dev: the test channel, rebuilt on every change. It may break, and the fix is the next build.
Every image carries a signed Android app, all with the same key, so a phone can move between channels without reinstalling. Rolling back a SHA tag rolls back the app it carries too.
Back up the Postgres volume with pg_dump. It holds accounts, likes, play history, playlists and settings, and a migration can only be undone by restoring a dump. The minstrel-data volume holds artwork and caches that Minstrel rebuilds if lost.
Features
Listening
- Smart shuffle that weighs what you like, what you skip and what you've heard lately, instead of picking at random.
- Two kinds of like: a song you love, and a song that's right for what you're listening to now.
- Radio that starts from any song, using ListenBrainz similarity as well as your own history.
- Daily mixes and system playlists built from your listening.
- Playback on Sonos and other UPnP speakers from the Android app.
- Loudness levelling between tracks, so you never reach for the volume.
Your library
- Browse by artist, album, genre and year, with search across everything.
- Duplicates, missing files and suspect copies are found and listed under Collection for an admin to fix.
- Missing album art and MusicBrainz ids are filled in automatically.
Getting music
- A wanted list: requests from the household, artists you follow, and albums whose files went missing.
- Minstrel searches the indexers you configure, hands the best release to your download client, checks what arrives against MusicBrainz, tags it, and files it. A better copy replaces a poor one.
- Off until you add a search service and a download client.
Clients and accounts
- A full web app, built into the server.
- A native Android app that ships inside the server image, keeps playing what it has cached when the server is out of reach, and updates itself from your server.
- OpenSubsonic-compatible, so existing clients such as DSub, Symfonium and play:Sub connect as they are.
- Separate accounts for each person, with invites, and an inbox for what happened while you were away.
Scope and responsible use
Minstrel indexes the files you point it at and streams them to your own clients. It ships no indexers, no trackers, no torrent, Usenet or NZB engine of its own, and no DRM circumvention.
Getting music is optional and does nothing until you configure it. It searches only the search service (Prowlarr or a Torznab indexer) and the download client (qBittorrent) you add yourself.
What you put in your library, and the sources you configure, are your responsibility. Copyright law applies to your collection as it does to any other software that plays a file; please respect it, and the terms of any service you connect.
Minstrel is not affiliated with or endorsed by ListenBrainz, MusicBrainz, Prowlarr, qBittorrent or Subsonic.
Development
- Backend:
docker compose upin this repo runs Postgres and Minstrel on:4533. - Web:
cd web && npm install && npm run devstarts Vite on:5173. It proxies/api/*and/rest/*to:4533. - Tests:
make test-shortruns the unit tests.make test-integrationalso runs the database tests, against a separateminstrel_testdatabase so your dev data is never truncated. - Image:
docker build -t minstrel .builds the web app and embeds it in the server binary. - Branches: work lands on
dev;mainchanges through pull requests fromdev.
How accounts and sessions are protected: docs/security.md.
License
MIT. See LICENSE.

