feat(server): scan library on startup by default + README first-run walkthrough
Make a fresh install usable out of the box and document the first-run flow for the public-facing repo. - Default scan_on_startup to true (Default() + config.example.yaml, which is the live config baked into the image). Previously false, so a fresh stack came up with an empty library and no hint to scan. Scans are incremental (mtime skip), so the per-restart cost is just a directory walk. Re-point the env-override test to exercise the override against the new default. - README: add a "First run" walkthrough (register -> scan -> integrations -> install Android app -> invite users), each grounded in a real route. - Add docs/screenshots/ with six captures, referenced via width-constrained <img> wrapped in a link (shrink inline + click to open full size). API token and invite token were cropped/redacted out of the captures before commit so no live credential lands in the public history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ A self-hosted music server that thinks for you. Smart shuffle, contextual likes,
|
|||||||
|
|
||||||
> State and intelligence belong on the server, not the client.
|
> State and intelligence belong on the server, not the client.
|
||||||
|
|
||||||
<!-- TODO: screenshot of the home page -->
|
<a href="docs/screenshots/home.png"><img src="docs/screenshots/home.png" width="820" alt="Minstrel home — your library at a glance"></a>
|
||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
@@ -58,7 +58,29 @@ volumes:
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
After the stack is up, visit `http://localhost:4533/register` and create your admin account. The first user to register on a fresh instance is automatically marked as the administrator; subsequent users can register through the same form (or via invite tokens generated from the admin Users panel, depending on how you configure registration).
|
## First run
|
||||||
|
|
||||||
|
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 — no TLS required).
|
||||||
|
|
||||||
|
**1. Create your admin account.** Visit `http://localhost:4533/register`. The first account on a fresh instance is automatically the administrator; 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.
|
||||||
|
|
||||||
|
<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) Name the instance and wire up integrations.** In admin **Settings → Integrations** (`/admin/integrations`), add a ListenBrainz token (scrobbling + similarity radio) and/or a Lidarr URL + API key (the request flow). 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="ListenBrainz and Lidarr integration cards in admin Settings"></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 "Install the Android app" 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).
|
For the full configuration surface, see [`config.example.yaml`](./config.example.yaml).
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -26,8 +26,13 @@ library:
|
|||||||
# Filesystem roots to scan for music. Each path is walked recursively.
|
# Filesystem roots to scan for music. Each path is walked recursively.
|
||||||
# Env: MINSTREL_LIBRARY_SCAN_PATHS (colon-separated, PATH-style)
|
# Env: MINSTREL_LIBRARY_SCAN_PATHS (colon-separated, PATH-style)
|
||||||
scan_paths: []
|
scan_paths: []
|
||||||
# Kick off a scan on server startup. Env: MINSTREL_LIBRARY_SCAN_ON_STARTUP
|
# Kick off a scan on server startup. On by default so a fresh install
|
||||||
scan_on_startup: false
|
# 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
|
||||||
|
|
||||||
# Per-instance branding. Operators can rename their instance ("Family
|
# Per-instance branding. Operators can rename their instance ("Family
|
||||||
# Jukebox", "Office Music", etc.) and override the OG share-preview
|
# Jukebox", "Office Music", etc.) and override the OG share-preview
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# README screenshots
|
||||||
|
|
||||||
|
Drop the files below into this directory with these **exact names** — the
|
||||||
|
root `README.md` already references them, so no Markdown editing is needed.
|
||||||
|
Export as PNG. Capture while logged in as admin and *after* the first
|
||||||
|
library scan so nothing looks empty; keep the same browser width and theme
|
||||||
|
across all shots for a consistent look.
|
||||||
|
|
||||||
|
| Filename | Route | What to capture |
|
||||||
|
|---|---|---|
|
||||||
|
| `home.png` | `/` | The home page with a populated library (system playlists / recently added) |
|
||||||
|
| `register.png` | `/register` | The account-creation form on a fresh instance |
|
||||||
|
| `library-scan.png` | `/admin` | The library scan running in stages (grab it mid-scan so stage tallies show) |
|
||||||
|
| `integrations.png` | `/admin/integrations` | The ListenBrainz + Lidarr integration cards |
|
||||||
|
| `android-download.png` | `/settings` | The "Install the Android app" download card |
|
||||||
|
| `invite-users.png` | `/admin/users` | The invite-token generation / users list |
|
||||||
|
|
||||||
|
Tip: to control display width in the README, you can swap the Markdown
|
||||||
|
`` for an HTML tag, e.g. `<img src="docs/screenshots/home.png" width="800" alt="...">`.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -135,6 +135,11 @@ func Default() Config {
|
|||||||
RadioSizeMax: 200,
|
RadioSizeMax: 200,
|
||||||
},
|
},
|
||||||
Library: LibraryConfig{
|
Library: LibraryConfig{
|
||||||
|
// On by default so a fresh install populates its library on
|
||||||
|
// first boot without the operator hunting for a "scan now"
|
||||||
|
// button. Scans are incremental (unchanged files skipped by
|
||||||
|
// mtime), so every later restart is just a cheap directory walk.
|
||||||
|
ScanOnStartup: true,
|
||||||
CoverArtFromMBCAA: true,
|
CoverArtFromMBCAA: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ func TestEnvOverrides(t *testing.T) {
|
|||||||
func TestLibraryEnvOverrides(t *testing.T) {
|
func TestLibraryEnvOverrides(t *testing.T) {
|
||||||
stubStreamSecret(t)
|
stubStreamSecret(t)
|
||||||
t.Setenv("MINSTREL_LIBRARY_SCAN_PATHS", "/music:/other::/third")
|
t.Setenv("MINSTREL_LIBRARY_SCAN_PATHS", "/music:/other::/third")
|
||||||
t.Setenv("MINSTREL_LIBRARY_SCAN_ON_STARTUP", "true")
|
// Default is true; set false so the override path is actually exercised.
|
||||||
|
t.Setenv("MINSTREL_LIBRARY_SCAN_ON_STARTUP", "false")
|
||||||
|
|
||||||
cfg, err := Load("")
|
cfg, err := Load("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,8 +113,8 @@ func TestLibraryEnvOverrides(t *testing.T) {
|
|||||||
t.Errorf("scan_paths[%d] = %q, want %q", i, cfg.Library.ScanPaths[i], p)
|
t.Errorf("scan_paths[%d] = %q, want %q", i, cfg.Library.ScanPaths[i], p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !cfg.Library.ScanOnStartup {
|
if cfg.Library.ScanOnStartup {
|
||||||
t.Error("scan_on_startup = false, want true")
|
t.Error("scan_on_startup = true, want false (env override of the true default)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user