docs: README in the standard order; setup files say what works today (#6277)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m25s
release / go (push) Successful in 1m40s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m32s
release / Build signed APK (releases and dev) (push) Successful in 5m51s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 15s
release / Verify release artifacts (tag releases only) (push) Skipped

README: logo header, short summary, a compose quick start with one-line
comments, first-run steps, a configuration table, setup pitfalls,
updating and backups, then features grouped by what you do. The Specs
section (it called the Android client the "Flutter companion app"), the
dated tag-history sentence and the Fable pointer are gone. Feature lines
were checked against the code: radio seeds from a track only, and the
Android app plays from its cache rather than offering downloads.

.env.example used the SMARTMUSIC_ prefix, which the config loader has not
read for a long time, so copying it set nothing. It now uses MINSTREL_.

config.example.yaml is also the config the image bakes in at
/etc/smartmusic/config.yaml. It listed five recommendation weights
(base_weight, like_boost, recency_weight, skip_penalty, jitter_magnitude)
that moved to Admin > Tuning; yaml.Unmarshal ignored them silently. They
are removed, and the file parses to exactly Default(), so the running
server is unchanged. coverart_from_mbcaa is not listed: nothing reads it.

docker-compose.yml, the Makefile and the spec banners lose their task and
milestone ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 12:02:25 -04:00
co-authored by Claude Opus 5.5
parent eac320c8d7
commit 9f9609591d
7 changed files with 128 additions and 214 deletions
+9 -10
View File
@@ -1,12 +1,11 @@
# Minstrel environment variable overrides.
# Copy to .env for docker compose, or export before `go run ./cmd/minstrel`.
# Values shown are sensible defaults for the local docker-compose postgres.
# Environment for running the server outside Docker (`go run ./cmd/minstrel`).
# The values match the Postgres in docker-compose.yml.
SMARTMUSIC_SERVER_ADDRESS=:4533
SMARTMUSIC_DATABASE_URL=postgres://minstrel:minstrel@localhost:5432/minstrel?sslmode=disable
SMARTMUSIC_LOG_LEVEL=info
SMARTMUSIC_LOG_FORMAT=text
MINSTREL_SERVER_ADDRESS=:4533
MINSTREL_DATABASE_URL=postgres://minstrel:minstrel@localhost:5432/minstrel?sslmode=disable
MINSTREL_LOG_LEVEL=info
MINSTREL_LOG_FORMAT=text
# Set this when running `go test ./...` without -short to exercise the
# migration test against a running postgres.
# MINSTREL_TEST_DATABASE_URL=postgres://minstrel:minstrel@localhost:5432/minstrel?sslmode=disable
# Runs the database tests in `go test ./...` (without -short). Use a separate
# database: the tests truncate every table.
# MINSTREL_TEST_DATABASE_URL=postgres://minstrel:minstrel@localhost:5432/minstrel_test?sslmode=disable
+4 -7
View File
@@ -13,11 +13,8 @@ generate:
generate-go:
go run github.com/sqlc-dev/sqlc/cmd/sqlc@v$(SQLC_VERSION) generate
# Fail if the committed generated code no longer matches the .sql sources.
#
# Nothing verified this before, so internal/db/dbq could silently drift from
# internal/db/queries — a hand-edit, a half-applied regen, or a schema change
# without a regen would all pass CI while the typed layer lied about the SQL.
# Fail if the committed generated code no longer matches the .sql sources:
# a hand-edit, a half-applied regen, or a schema change without a regen.
#
# The diff is printed BEFORE the exit-code check on purpose: when this fails,
# the log then contains sqlc's exact expected output, which is what you commit.
@@ -36,8 +33,8 @@ test-short:
go test -short -race ./...
# Full suite incl. integration tests, against the dedicated minstrel_test
# DB so a run never truncates the dev `minstrel` DB (Fable #339). Ensures
# the test DB exists (idempotent — createdb errors if present, ignored).
# DB so a run never truncates the dev `minstrel` DB. Ensures the test DB
# exists (createdb fails harmlessly if it is already there).
test-integration:
docker compose up -d postgres
-docker compose exec -T postgres createdb -U minstrel minstrel_test
+76 -118
View File
@@ -1,62 +1,26 @@
# Minstrel
<p align="center"><img src="web/static/brand/mark.svg" width="120" alt="Minstrel logo"></p>
A self-hosted music server that thinks for you. Smart shuffle, contextual likes, ListenBrainz-aware radio, and a wanted list it fetches for you — server-side, so every client (web, mobile, Subsonic third-party) gets the same intelligence.
<h1 align="center">Minstrel</h1>
> State and intelligence belong on the server, not the client.
<p align="center">A self-hosted music server that picks what to play next.</p>
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.
<a href="docs/screenshots/home.png"><img src="docs/screenshots/home.png" width="820" alt="Minstrel home — your library at a glance"></a>
## Highlights
- **OpenSubsonic-compatible.** Existing Subsonic clients (DSub, Symfonium, play:Sub, etc.) connect with no special configuration.
- **Server-side smart shuffle.** Track-similarity vectors, dual-like model (general + contextual), and session memory keep mixes coherent across devices.
- **ListenBrainz radio.** Session-aware "more like this" pulls from ListenBrainz similarity data, not a static genre tag.
- **Finds, downloads and files music itself.** A wanted list — requests, followed artists, albums gone missing — searched through indexers *you* configure and downloaded by a client *you* run. Each download is checked against MusicBrainz, tagged, and filed into your library, and a better copy replaces a poor one. Optional, and off until you add a search service and a download client.
- **Built-in web SPA.** Full-feature library, search, queue, playlists, and admin — no separate frontend container to deploy.
- **Native Android client, shipped with the server.** The signed APK is bundled into every image and attached to each [release](https://git.fabledsword.com/bvandeusen/minstrel/releases) — sideload it once, then the app self-updates straight from your own server (no app store, no separate download to track).
## Scope and responsible use
**Minstrel is a library server.** It indexes files on disk you point it at, and streams them to your own clients.
Minstrel ships **no** indexers, **no** trackers, **no** torrent / Usenet / NZB engine of its own, and **no** DRM circumvention of any kind.
**Acquisition is optional and inert until you configure it.** You add the search service (a Prowlarr instance or a Torznab indexer) and the download client (qBittorrent) you already run; Minstrel then searches those for the albums on its wanted list, hands the release it picks to your client, and checks and files what arrives. It configures no sources on your behalf: any it uses are ones you added yourself.
**What you put in your library, and what sources you configure, are your responsibility.** Copyright law applies to your collection the same way it applies to any other software that plays a file. Please respect it, and respect the terms of any service you connect.
Minstrel is not affiliated with or endorsed by ListenBrainz, MusicBrainz, Prowlarr, qBittorrent, or Subsonic.
## Quickstart
## Quick start
```yaml
# compose.yaml
services:
minstrel:
image: git.fabledsword.com/bvandeusen/minstrel:latest
# Reachable from your LAN at http://<host>:4533. If this host faces the
# internet, bind it to 127.0.0.1 and put an HTTPS proxy in front instead:
# see docs/hosting.md.
ports: ['4533:4533']
volumes:
# Your music library. Point ./music at wherever your audio files
# live. Writable, because Minstrel files the albums it downloads into
# it (as Artist/Album (Year)/..., tagged with MusicBrainz ids) and
# deletes a file when an admin asks it to (for example, quarantine's
# "Delete file"). Files already there are never moved, renamed or
# retagged. The container runs as uid 1000, so that user needs write
# access to the folders. Mount it :ro to forbid writes: those actions
# then refuse, say why, and change nothing.
- ./music:/music
# Generated data: playlist cover collages, artist art, caches.
# The path must match MINSTREL_STORAGE_DATA_DIR, which the image
# sets to /app/data — keep this mount on /app/data or your cache
# won't survive a container recreate.
- minstrel-data:/app/data
- ./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
# Colon-separated library roots to scan; must match the container
# path of the music mount above (/music here).
MINSTREL_LIBRARY_SCAN_PATHS: /music
depends_on: [db]
@@ -66,9 +30,7 @@ services:
POSTGRES_USER: minstrel
POSTGRES_PASSWORD: minstrel
POSTGRES_DB: minstrel
# Postgres data dir — users, likes, play history, sessions, settings.
# The one volume you must never lose; back it up with pg_dump.
volumes: [pgdata:/var/lib/postgresql/data]
volumes: [pgdata:/var/lib/postgresql/data] # users, likes, history: back this up
volumes:
minstrel-data:
@@ -77,105 +39,101 @@ volumes:
```bash
docker compose up -d
docker compose logs minstrel | grep setup_token
```
## First run
Open `http://<host>:4533/register` and create the first account with that setup token. The first account is the administrator.
With the stack up, a handful of in-app steps get you to a working library. Use your own host in place of `localhost` if you're reaching the server over a LAN/VPN address. Plain `http://` is fine on a network you trust; a server reachable from the internet belongs behind HTTPS, which [docs/hosting.md](docs/hosting.md) walks through.
### First run
**1. Create your admin account.** Visit `http://localhost:4533/register`. The first account on a fresh instance becomes the administrator, and creating it asks for the **setup token** the server prints in its log (`docker compose logs minstrel | grep setup_token`), so nobody else can claim a newly exposed server first. Later users join through the same form or an invite token (step 5).
<a href="docs/screenshots/register.png"><img src="docs/screenshots/register.png" width="320" alt="Creating the first (admin) account on a fresh instance"></a>
**2. Let the first library scan finish.** `scan_on_startup` is on by default, so Minstrel walks your mounted library on boot and imports artists, albums, and tracks — no button to press. Watch progress (and re-scan any time) on the **Admin** page (`/admin`); the scan runs in stages and is incremental, so later restarts only pick up what changed.
1. **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.
2. **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.
3. **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.
4. **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**.
<a href="docs/screenshots/library-scan.png"><img src="docs/screenshots/library-scan.png" width="820" alt="The Admin page, where the library scan runs and reports progress"></a>
**3. (Optional) Set up acquisition and connections.** In admin **Acquisition** (`/admin/acquisition`), add a search service and a download client so Minstrel can fetch what's wanted; each person adds their own ListenBrainz token (scrobbling + similarity radio) under **Settings → Connections** (`/settings/connections`). These live in the UI and apply without a restart; the display name can also be set via `MINSTREL_BRANDING_APP_NAME`.
<a href="docs/screenshots/integrations.png"><img src="docs/screenshots/integrations.png" width="820" alt="Search services, download clients and searching in admin Acquisition"></a>
**4. Install the Android app.** Open **Settings** (`/settings`) and use the *Install the Android app* card to download the APK that ships inside this server image, then sign in with the same account. From then on the app self-updates straight from your server.
<a href="docs/screenshots/android-download.png"><img src="docs/screenshots/android-download.png" width="820" alt="The &quot;Install the Android app&quot; download card in Settings"></a>
**5. Invite the rest of the household.** From admin **Users** (`/admin/users`), generate an invite token (or enable open registration). Each person gets their own account, so likes, play history, and recommendations stay per-user.
<a href="docs/screenshots/invite-users.png"><img src="docs/screenshots/invite-users.png" width="820" alt="Generating an invite token in admin Users"></a>
For the full configuration surface, see [`config.example.yaml`](./config.example.yaml).
Hosting Minstrel on the internet: see [docs/hosting.md](docs/hosting.md). What Minstrel does to protect accounts, and why: [docs/security.md](docs/security.md).
## Configuration
Most operators only need the env vars in the quickstart above. A few extras worth knowing:
The quick start sets everything that's required. These are the settings people most often change:
- `MINSTREL_BRANDING_APP_NAME` — rename the instance ("Family Jukebox", "Office Music"). Surfaces in the header, browser tab, and OG share previews.
- `MINSTREL_STORAGE_DATA_DIR` — where generated artefacts (playlist cover collages, artist art, caches) are written. The container image sets this to `/app/data`, which is why the quickstart mounts the `minstrel-data` volume there.
- `MINSTREL_LIBRARY_SCAN_PATHS` — colon-separated list of music library roots to scan. Supports multiple roots (`/music:/podcasts`).
| 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`. |
ListenBrainz (per-user scrobble + similarity tokens) and acquisition (search services and download clients) are configured in the UI rather than env vars or yaml — per Minstrel's "config in UI" rule, integration settings live where operators can edit them without restarting.
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`](./config.example.yaml); to use it, mount your copy at `/etc/smartmusic/config.yaml`.
Most operational keys have a `MINSTREL_<SECTION>_<FIELD>` env override. Recommendation and events tuning are yaml-only. See [`config.example.yaml`](./config.example.yaml) for the authoritative surface.
## Setup pitfalls
## Updating
- **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 `:ro` to forbid writes: those actions then refuse and say why.
- **`MINSTREL_LIBRARY_SCAN_PATHS` names 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 to `127.0.0.1` and put a reverse proxy in front: see [docs/hosting.md](docs/hosting.md).
Image tags (`git.fabledsword.com/bvandeusen/minstrel:<tag>`):
## Updating and backups
- `:latest` — production. Tracks `main`'s tip and moves on every `main` push and every release. What most operators should run.
- `:<commit-sha>` — the rollback unit. Every `main` push publishes one, so any production commit is addressable without a release ceremony. Immutable: a given SHA tag is never re-pushed. Pin one if you need a deployment that cannot change under you, and use it to roll back.
- `:dev` — the rolling test channel, rebuilt on every push to `dev` and carrying its own freshly-built Android APK. Run this to try something before it ships. It moves constantly, has no per-commit tag, and its only recovery path is forward — if a `:dev` image is broken, the fix is the next push, not a rollback.
```bash
docker compose pull && docker compose up -d
```
That is the whole tag map. **There are no version-numbered image tags**, and no `:main`. Git and the build's own self-reported version answer "which build is this" — the Settings page shows it, and so does `/healthz`. Release *tags* in git are still `vYYYY.MM.DD.HHMM`; they name a changelog entry and the APK attached to it, not an image.
Database migrations run at startup. Image tags:
Rolling back to `:<commit-sha>` pins the **server code** at that commit — not the server-and-app pair. The Android APK is baked in at image build time, so a SHA image carries whichever app was current when that commit was built, which may be older than what `:latest` bundles now. If both halves matter, check what the image bundles rather than trusting the tag's name.
- `: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 `:latest`, `:<commit-sha>` and `:dev` bundles a signed Android APK, so the in-app update channel is always live. All are signed with the same key, so a phone can move between the stable and dev channels without uninstalling — point it at a `:dev` server and the in-app updater offers that channel's 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.
The app reports which channel it is on alongside its version, and decides whether an update is available using the build's ordering key rather than its displayed name — the same value Android installs by, so an offer it makes is one the platform will accept.
**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.
Database migrations run automatically at startup; rollbacks require restoring a Postgres dump.
## Features
Releases up to 2026-09-10 also published a `:vYYYY.MM.DD[.HHMM]` image tag. Those images still exist and still work — they are simply not extended.
**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.
## Specs
**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.
Authoritative scope lives under [`docs/`](./docs):
**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.
- [Server spec](./docs/smart-music-server-spec.md) — current implementation focus.
- [Client spec](./docs/smart-music-client-spec.md) — Flutter companion app.
**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
Two concurrent dev processes:
- **Backend:** `docker compose up` in this repo runs Postgres and Minstrel on `:4533`.
- **Web:** `cd web && npm install && npm run dev` starts Vite on `:5173`. It proxies `/api/*` and `/rest/*` to `:4533`.
- **Tests:** `make test-short` runs the unit tests. `make test-integration` also runs the database tests, against a separate `minstrel_test` database 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`; `main` changes through pull requests from `dev`.
1. **Backend:** `docker compose up` — Postgres + Minstrel on `:4533`.
2. **Frontend:** `cd web && npm install && npm run dev` — Vite dev server on `:5173` with HMR. The Vite server proxies `/api/*` and `/rest/*` to `:4533` so session cookies work.
### Testing
- Unit + race (no DB): `make test-short`.
- Full suite incl. integration tests: `make test-integration`. This runs
against a dedicated `minstrel_test` database so a test run never
truncates your dev `minstrel` data (admin user, library, likes). It
brings up the compose Postgres and creates the test DB if missing.
- CI runs both: a fast `go test -short -race` gate plus an integration
job with its own ephemeral Postgres (the `integration` lane in
`.gitea/workflows/release.yml`, which also gates every image publish).
### Production build
`docker build -t minstrel .` runs the SvelteKit build inside a `node` stage, copies the output into the `golang` stage, and `//go:embed`s it into the final binary. The container serves the SPA from `/` alongside the API surfaces; no separate static-file server is required.
### Branches
- Day-to-day work happens on `dev` (or feature branches merged into `dev`).
- `main` is **protected** — changes land via PR from `dev`.
- Releases are cut by tagging `v*` off `main`; the release workflow builds the signed APK, attaches it to the release, and refreshes `:latest` around it.
Task and milestone tracking: Fable (`Minstrel` project, id 12).
How accounts and sessions are protected: [docs/security.md](docs/security.md).
## License
See [LICENSE](./LICENSE).
MIT. See [LICENSE](./LICENSE).
+30 -58
View File
@@ -1,82 +1,54 @@
# Minstrel example configuration.
# Every field is optional; defaults shown below match Default() in internal/config.
# Each setting can be overridden at runtime via MINSTREL_* environment variables
# (see internal/config/config.go for the full list).
# Minstrel example configuration. In the container image, mount it at
# /etc/smartmusic/config.yaml. Every field is optional; the values below are
# the defaults. Each MINSTREL_* variable named here overrides its field.
server:
address: ":4533"
address: ":4533" # MINSTREL_SERVER_ADDRESS
storage:
# On-disk root for cached runtime artifacts (currently playlist cover
# collages under <data_dir>/playlist_covers/). Default: "./data" relative
# to the working directory. Production deployments should set an absolute
# path on persistent storage.
# Env: MINSTREL_STORAGE_DATA_DIR
data_dir: "./data"
# Artwork, caches and the stream-signing key. Use persistent storage.
data_dir: "./data" # MINSTREL_STORAGE_DATA_DIR (the image sets /app/data)
database:
# Postgres connection string, e.g. postgres://minstrel:minstrel@localhost:5432/minstrel?sslmode=disable
url: ""
# e.g. postgres://minstrel:minstrel@localhost:5432/minstrel?sslmode=disable
url: "" # MINSTREL_DATABASE_URL
log:
level: "info" # debug | info | warn | error
format: "text" # text | json
level: "info" # debug | info | warn | error. MINSTREL_LOG_LEVEL
format: "text" # text | json. MINSTREL_LOG_FORMAT
library:
# Filesystem roots to scan for music. Each path is walked recursively.
# These are the starting folders only: once they are changed in
# Admin › Library, the saved ones are used and this value is ignored.
# Changing a folder there moves its tracks with it, so moving the library
# to a new mount keeps every play, like and playlist.
# Env: MINSTREL_LIBRARY_SCAN_PATHS (colon-separated, PATH-style)
scan_paths: []
# Kick off a scan on server startup. On by default so a fresh install
# populates its library on first boot with no manual step. Scans are
# incremental (unchanged files are skipped by mtime), so the cost on
# every later restart is just a directory walk, not a full re-index.
# Set false if you'd rather scan only on a schedule / via the admin UI.
# Env: MINSTREL_LIBRARY_SCAN_ON_STARTUP
scan_on_startup: true
# Library folders, walked recursively. Used on first start only: once they
# are changed in Admin › Library, the saved ones win. Changing a folder there
# moves its tracks with it, so plays, likes and playlists survive a new mount.
scan_paths: [] # MINSTREL_LIBRARY_SCAN_PATHS (colon-separated)
# Scan on startup. Scans are incremental, so a restart only walks the folders.
scan_on_startup: true # MINSTREL_LIBRARY_SCAN_ON_STARTUP
# Contact sent to the Cover Art Archive in the User-Agent. Empty sends
# the project's URL.
contact_email: "" # MINSTREL_CONTACT_EMAIL
# Per-instance branding. Operators can rename their instance ("Family
# Jukebox", "Office Music", etc.) and override the OG share-preview
# description. Both fields default to sensible Minstrel values when
# unset; you only need this section to override.
# Env: MINSTREL_BRANDING_APP_NAME, MINSTREL_BRANDING_DESCRIPTION
branding:
app_name: "Minstrel"
description: "Self-hosted music server with Subsonic compatibility, smart shuffle, and music acquisition."
app_name: "Minstrel" # MINSTREL_BRANDING_APP_NAME
description: "Self-hosted music server with Subsonic compatibility, smart shuffle, and music acquisition." # MINSTREL_BRANDING_DESCRIPTION
subsonic:
# Gate for the legacy `p=` plaintext password parameter on /rest/*.
# Leave disabled unless a specific client requires it — Minstrel prefers
# apiKey (OpenSubsonic) and falls back to u+t+s token auth.
# Env: MINSTREL_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD
allow_plaintext_password: false
# Accept the plaintext `p=` password on /rest/*. Leave off unless a client
# needs it; apiKey and token auth work without it.
allow_plaintext_password: false # MINSTREL_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD
events:
# Idle gap (minutes) between play_events that closes a play_session and
# starts a new one. Spec §6.
# Minutes of silence that end a listening session.
session_timeout_minutes: 30
# Skip rule (spec §6): a play counts as a SKIP if completion ratio is below
# this threshold AND duration played (ms) is below the next threshold.
# A play is a skip when it stops before this fraction of the track AND
# before this many milliseconds.
skip_max_completion_ratio: 0.5
skip_max_duration_played_ms: 30000
recommendation:
# Base score every candidate gets before adjustments. Spec §6.
base_weight: 1.0
# Bonus for tracks the user has liked (general_likes).
like_boost: 2.0
# Multiplier on recency_decay (1.0 for tracks ≥ 30 days stale, 0 for fresh).
recency_weight: 1.0
# Penalty multiplier on skip_ratio (skips/plays); 1.0 = full penalty.
skip_penalty: 1.0
# ± random jitter applied to every candidate; breaks ties without dominating.
jitter_magnitude: 0.1
# Hard-suppression window: tracks played within this many hours never appear.
# Scoring weights are tuned in Admin › Tuning, not here.
# Tracks played within this many hours are left out of radio.
recently_played_hours: 1
# Default radio size when ?limit= is not specified.
# Radio length when ?limit= is not given, and the most ?limit= may ask for.
radio_size: 50
# Maximum allowed ?limit= (server caps to this regardless).
radio_size_max: 200
+7 -19
View File
@@ -1,17 +1,8 @@
version: "3.9"
# Local development environment for Minstrel.
#
# Integration tests run against a SEPARATE database (minstrel_test) so a
# test run never truncates the dev `minstrel` DB (Fable #339). Use the
# Makefile target, which ensures the test DB exists then points the
# tests at it:
# make test-integration
# (CI runs the same suite against its own ephemeral Postgres service —
# see the `integration` job in .gitea/workflows/release.yml.)
#
# Full stack (server + db):
# docker compose up --build
# Local development stack: `docker compose up --build`.
# The database tests use a separate minstrel_test database, so they never
# truncate your dev data. Run them with `make test-integration`.
services:
postgres:
@@ -26,8 +17,8 @@ services:
# - "5432:5432"
volumes:
- minstrel-pgdata:/var/lib/postgresql/data
# Creates minstrel_test on a fresh volume (Fable #339). No-op on
# an existing volume — make test-integration ensures it instead.
# Creates minstrel_test on a fresh volume. On an existing volume,
# make test-integration creates it instead.
- ./deploy/initdb:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U minstrel -d minstrel"]
@@ -51,12 +42,9 @@ services:
ports:
- "4533:4533"
volumes:
# Point ./music at your test library (symlink, bind-mount, whatever).
# Read-only so a buggy scanner can't rewrite your files.
# Your test library, read-only so a scanner bug can't change your files.
- /mnt/Media/Music:/music:ro
# Cached artifacts: playlist cover collages, artist art, album-cover
# fallbacks (when the music dir is RO). Persists across container
# recreates so the operator doesn't redownload art on every up/down.
# Artwork and caches, kept across container recreates.
- minstrel-data:/app/data
volumes:
+1 -1
View File
@@ -1,6 +1,6 @@
# Project Spec: Smart Music Client (companion app)
> **History.** This is the original design spec, kept as written. Where it describes an outside library manager doing search, adds and deletes, Minstrel now does that itself (M502).
> **History.** This is the original design spec, kept as written. Where it describes an outside library manager doing search, adds and deletes, Minstrel now does that itself.
## 1. Overview
+1 -1
View File
@@ -1,6 +1,6 @@
# Project Spec: Smart Music Server (working name)
> **History.** This is the original design spec, kept as written. Where it describes an outside library manager doing search, adds and deletes, Minstrel now does that itself (M502).
> **History.** This is the original design spec, kept as written. Where it describes an outside library manager doing search, adds and deletes, Minstrel now does that itself.
## 1. Overview