chore(config): rename SMARTMUSIC_ env prefix to MINSTREL_; default admin user
The legacy SMARTMUSIC_ prefix dates from when the project was specced as "smart-music" before being renamed to Minstrel. Hard cutover to MINSTREL_* across config.go, tests, config.example.yaml, README, and docker-compose. Also renames SMARTMUSIC_CONFIG in cmd/minstrel/main.go which was missed in the original audit. Also seeds Auth.AdminBootstrap.Username = "admin" in Default() so MINSTREL_DATABASE_URL is the only env var required for a fresh prod deployment — the bootstrap auto-generates a password and prints it to stderr. Drops the now-redundant MINSTREL_AUTH_ADMIN_USERNAME=admin line from the README quickstart and docker-compose. M1 follow-up from the #363 final review: guards MINSTREL_BRANDING_APP_NAME and _DESCRIPTION against empty-string overrides — empty would have shipped <title></title> to share-preview crawlers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,9 +27,8 @@ services:
|
|||||||
- ./music:/music:ro
|
- ./music:/music:ro
|
||||||
- minstrel-data:/data
|
- minstrel-data:/data
|
||||||
environment:
|
environment:
|
||||||
SMARTMUSIC_DATABASE_URL: postgres://minstrel:minstrel@db:5432/minstrel?sslmode=disable
|
MINSTREL_DATABASE_URL: postgres://minstrel:minstrel@db:5432/minstrel?sslmode=disable
|
||||||
SMARTMUSIC_LIBRARY_SCAN_PATHS: /music
|
MINSTREL_LIBRARY_SCAN_PATHS: /music
|
||||||
SMARTMUSIC_AUTH_ADMIN_USERNAME: admin
|
|
||||||
depends_on: [db]
|
depends_on: [db]
|
||||||
|
|
||||||
db:
|
db:
|
||||||
@@ -57,14 +56,14 @@ For the full configuration surface, see [`config.example.yaml`](./config.example
|
|||||||
|
|
||||||
Most operators only need the env vars in the quickstart above. A few extras worth knowing:
|
Most operators only need the env vars in the quickstart above. A few extras worth knowing:
|
||||||
|
|
||||||
- `SMARTMUSIC_BRANDING_APP_NAME` — rename the instance ("Family Jukebox", "Office Music"). Surfaces in the header, browser tab, and OG share previews.
|
- `MINSTREL_BRANDING_APP_NAME` — rename the instance ("Family Jukebox", "Office Music"). Surfaces in the header, browser tab, and OG share previews.
|
||||||
- `SMARTMUSIC_STORAGE_DATA_DIR` — defaults to `./data`. Holds playlist cover collages and other generated artefacts.
|
- `MINSTREL_STORAGE_DATA_DIR` — defaults to `./data`. Holds playlist cover collages and other generated artefacts.
|
||||||
- `SMARTMUSIC_LIBRARY_SCAN_PATHS` — colon-separated list of music library roots to scan. Supports multiple roots (`/music:/podcasts`).
|
- `MINSTREL_LIBRARY_SCAN_PATHS` — colon-separated list of music library roots to scan. Supports multiple roots (`/music:/podcasts`).
|
||||||
- `SMARTMUSIC_AUTH_ADMIN_USERNAME` / `SMARTMUSIC_AUTH_ADMIN_PASSWORD` — bootstrap admin credentials. Leave the password unset to have the server generate one and print it to stderr on first start.
|
- `MINSTREL_AUTH_ADMIN_USERNAME` / `MINSTREL_AUTH_ADMIN_PASSWORD` — bootstrap admin credentials. Username defaults to `admin`; leave the password unset to have the server generate one and print it to stderr on first start.
|
||||||
|
|
||||||
ListenBrainz integration (per-user scrobble + similarity tokens) and Lidarr integration (URL + API key) are configured through the admin Settings UI rather than env vars or yaml — per Minstrel's "config in UI" rule, integration settings live where operators can edit them without restarting.
|
ListenBrainz integration (per-user scrobble + similarity tokens) and Lidarr integration (URL + API key) are configured through the admin Settings UI rather than env vars or yaml — per Minstrel's "config in UI" rule, integration settings live where operators can edit them without restarting.
|
||||||
|
|
||||||
Most operational keys have a `SMARTMUSIC_<SECTION>_<FIELD>` env override. Recommendation and events tuning are yaml-only. See [`config.example.yaml`](./config.example.yaml) for the authoritative surface.
|
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.
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
configPath := flag.String("config", os.Getenv("SMARTMUSIC_CONFIG"), "path to YAML config file")
|
configPath := flag.String("config", os.Getenv("MINSTREL_CONFIG"), "path to YAML config file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
cfg, err := config.Load(*configPath)
|
cfg, err := config.Load(*configPath)
|
||||||
|
|||||||
+11
-10
@@ -1,6 +1,6 @@
|
|||||||
# Minstrel example configuration.
|
# Minstrel example configuration.
|
||||||
# Every field is optional; defaults shown below match Default() in internal/config.
|
# Every field is optional; defaults shown below match Default() in internal/config.
|
||||||
# Each setting can be overridden at runtime via SMARTMUSIC_* environment variables
|
# Each setting can be overridden at runtime via MINSTREL_* environment variables
|
||||||
# (see internal/config/config.go for the full list).
|
# (see internal/config/config.go for the full list).
|
||||||
|
|
||||||
server:
|
server:
|
||||||
@@ -11,7 +11,7 @@ storage:
|
|||||||
# collages under <data_dir>/playlist_covers/). Default: "./data" relative
|
# collages under <data_dir>/playlist_covers/). Default: "./data" relative
|
||||||
# to the working directory. Production deployments should set an absolute
|
# to the working directory. Production deployments should set an absolute
|
||||||
# path on persistent storage.
|
# path on persistent storage.
|
||||||
# Env: SMARTMUSIC_STORAGE_DATA_DIR
|
# Env: MINSTREL_STORAGE_DATA_DIR
|
||||||
data_dir: "./data"
|
data_dir: "./data"
|
||||||
|
|
||||||
database:
|
database:
|
||||||
@@ -24,25 +24,26 @@ log:
|
|||||||
|
|
||||||
auth:
|
auth:
|
||||||
# One-shot admin bootstrap. Applied only when the users table is empty.
|
# One-shot admin bootstrap. Applied only when the users table is empty.
|
||||||
# Set username to enable; leave password blank to have the server generate
|
# Username defaults to "admin" when unset — you can omit this section
|
||||||
# one and log it to stderr on first start.
|
# entirely for a working bootstrap. Leave password blank to have the server
|
||||||
# Env: SMARTMUSIC_AUTH_ADMIN_USERNAME, SMARTMUSIC_AUTH_ADMIN_PASSWORD
|
# generate one and log it to stderr on first start.
|
||||||
|
# Env: MINSTREL_AUTH_ADMIN_USERNAME, MINSTREL_AUTH_ADMIN_PASSWORD
|
||||||
admin_bootstrap:
|
admin_bootstrap:
|
||||||
username: ""
|
username: "admin"
|
||||||
password: ""
|
password: ""
|
||||||
|
|
||||||
library:
|
library:
|
||||||
# Filesystem roots to scan for music. Each path is walked recursively.
|
# Filesystem roots to scan for music. Each path is walked recursively.
|
||||||
# Env: SMARTMUSIC_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: SMARTMUSIC_LIBRARY_SCAN_ON_STARTUP
|
# Kick off a scan on server startup. Env: MINSTREL_LIBRARY_SCAN_ON_STARTUP
|
||||||
scan_on_startup: false
|
scan_on_startup: false
|
||||||
|
|
||||||
# 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
|
||||||
# description. Both fields default to sensible Minstrel values when
|
# description. Both fields default to sensible Minstrel values when
|
||||||
# unset; you only need this section to override.
|
# unset; you only need this section to override.
|
||||||
# Env: SMARTMUSIC_BRANDING_APP_NAME, SMARTMUSIC_BRANDING_DESCRIPTION
|
# Env: MINSTREL_BRANDING_APP_NAME, MINSTREL_BRANDING_DESCRIPTION
|
||||||
branding:
|
branding:
|
||||||
app_name: "Minstrel"
|
app_name: "Minstrel"
|
||||||
description: "Self-hosted music server with Subsonic compatibility, smart shuffle, and Lidarr integration."
|
description: "Self-hosted music server with Subsonic compatibility, smart shuffle, and Lidarr integration."
|
||||||
@@ -51,7 +52,7 @@ subsonic:
|
|||||||
# Gate for the legacy `p=` plaintext password parameter on /rest/*.
|
# Gate for the legacy `p=` plaintext password parameter on /rest/*.
|
||||||
# Leave disabled unless a specific client requires it — Minstrel prefers
|
# Leave disabled unless a specific client requires it — Minstrel prefers
|
||||||
# apiKey (OpenSubsonic) and falls back to u+t+s token auth.
|
# apiKey (OpenSubsonic) and falls back to u+t+s token auth.
|
||||||
# Env: SMARTMUSIC_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD
|
# Env: MINSTREL_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD
|
||||||
allow_plaintext_password: false
|
allow_plaintext_password: false
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
|||||||
+5
-6
@@ -34,12 +34,11 @@ services:
|
|||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
SMARTMUSIC_DATABASE_URL: postgres://minstrel:minstrel@postgres:5432/minstrel?sslmode=disable
|
MINSTREL_DATABASE_URL: postgres://minstrel:minstrel@postgres:5432/minstrel?sslmode=disable
|
||||||
SMARTMUSIC_LOG_FORMAT: text
|
MINSTREL_LOG_FORMAT: text
|
||||||
SMARTMUSIC_LIBRARY_SCAN_PATHS: /music
|
MINSTREL_LIBRARY_SCAN_PATHS: /music
|
||||||
SMARTMUSIC_LIBRARY_SCAN_ON_STARTUP: "true"
|
MINSTREL_LIBRARY_SCAN_ON_STARTUP: "true"
|
||||||
SMARTMUSIC_AUTH_ADMIN_USERNAME: admin
|
# MINSTREL_AUTH_ADMIN_PASSWORD left unset on purpose — bootstrap
|
||||||
# SMARTMUSIC_AUTH_ADMIN_PASSWORD left unset on purpose — bootstrap
|
|
||||||
# generates one and prints it to stderr on first boot. Watch the logs
|
# generates one and prints it to stderr on first boot. Watch the logs
|
||||||
# of the minstrel service the first time you bring the stack up.
|
# of the minstrel service the first time you bring the stack up.
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
+17
-12
@@ -111,6 +111,11 @@ func Default() Config {
|
|||||||
AppName: "Minstrel",
|
AppName: "Minstrel",
|
||||||
Description: "Self-hosted music server with Subsonic compatibility, smart shuffle, and Lidarr integration.",
|
Description: "Self-hosted music server with Subsonic compatibility, smart shuffle, and Lidarr integration.",
|
||||||
},
|
},
|
||||||
|
Auth: AuthConfig{
|
||||||
|
AdminBootstrap: AdminBootstrapConfig{
|
||||||
|
Username: "admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
Events: EventsConfig{
|
Events: EventsConfig{
|
||||||
SessionTimeoutMinutes: 30,
|
SessionTimeoutMinutes: 30,
|
||||||
SkipMaxCompletionRatio: 0.5,
|
SkipMaxCompletionRatio: 0.5,
|
||||||
@@ -148,42 +153,42 @@ func Load(path string) (Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyEnv(cfg *Config) {
|
func applyEnv(cfg *Config) {
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_SERVER_ADDRESS"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_SERVER_ADDRESS"); ok {
|
||||||
cfg.Server.Address = v
|
cfg.Server.Address = v
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_STORAGE_DATA_DIR"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_STORAGE_DATA_DIR"); ok {
|
||||||
cfg.Storage.DataDir = v
|
cfg.Storage.DataDir = v
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_DATABASE_URL"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_DATABASE_URL"); ok {
|
||||||
cfg.Database.URL = v
|
cfg.Database.URL = v
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_LOG_LEVEL"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_LOG_LEVEL"); ok {
|
||||||
cfg.Log.Level = strings.ToLower(v)
|
cfg.Log.Level = strings.ToLower(v)
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_LOG_FORMAT"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_LOG_FORMAT"); ok {
|
||||||
cfg.Log.Format = strings.ToLower(v)
|
cfg.Log.Format = strings.ToLower(v)
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_BRANDING_APP_NAME"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_BRANDING_APP_NAME"); ok && v != "" {
|
||||||
cfg.Branding.AppName = v
|
cfg.Branding.AppName = v
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_BRANDING_DESCRIPTION"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_BRANDING_DESCRIPTION"); ok && v != "" {
|
||||||
cfg.Branding.Description = v
|
cfg.Branding.Description = v
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_AUTH_ADMIN_USERNAME"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_AUTH_ADMIN_USERNAME"); ok {
|
||||||
cfg.Auth.AdminBootstrap.Username = v
|
cfg.Auth.AdminBootstrap.Username = v
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_AUTH_ADMIN_PASSWORD"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_AUTH_ADMIN_PASSWORD"); ok {
|
||||||
cfg.Auth.AdminBootstrap.Password = v
|
cfg.Auth.AdminBootstrap.Password = v
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_LIBRARY_SCAN_PATHS"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_LIBRARY_SCAN_PATHS"); ok {
|
||||||
cfg.Library.ScanPaths = splitPaths(v)
|
cfg.Library.ScanPaths = splitPaths(v)
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_LIBRARY_SCAN_ON_STARTUP"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_LIBRARY_SCAN_ON_STARTUP"); ok {
|
||||||
if b, err := strconv.ParseBool(v); err == nil {
|
if b, err := strconv.ParseBool(v); err == nil {
|
||||||
cfg.Library.ScanOnStartup = b
|
cfg.Library.ScanOnStartup = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv("SMARTMUSIC_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD"); ok {
|
if v, ok := os.LookupEnv("MINSTREL_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD"); ok {
|
||||||
if b, err := strconv.ParseBool(v); err == nil {
|
if b, err := strconv.ParseBool(v); err == nil {
|
||||||
cfg.Subsonic.AllowPlaintextPassword = b
|
cfg.Subsonic.AllowPlaintextPassword = b
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ func TestLoadMissingFileReturnsDefaults(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvOverrides(t *testing.T) {
|
func TestEnvOverrides(t *testing.T) {
|
||||||
t.Setenv("SMARTMUSIC_SERVER_ADDRESS", ":8080")
|
t.Setenv("MINSTREL_SERVER_ADDRESS", ":8080")
|
||||||
t.Setenv("SMARTMUSIC_DATABASE_URL", "postgres://env")
|
t.Setenv("MINSTREL_DATABASE_URL", "postgres://env")
|
||||||
t.Setenv("SMARTMUSIC_LOG_LEVEL", "WARN")
|
t.Setenv("MINSTREL_LOG_LEVEL", "WARN")
|
||||||
t.Setenv("SMARTMUSIC_LOG_FORMAT", "JSON")
|
t.Setenv("MINSTREL_LOG_FORMAT", "JSON")
|
||||||
|
|
||||||
cfg, err := Load("")
|
cfg, err := Load("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,8 +80,8 @@ func TestEnvOverrides(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAdminBootstrapEnvOverrides(t *testing.T) {
|
func TestAdminBootstrapEnvOverrides(t *testing.T) {
|
||||||
t.Setenv("SMARTMUSIC_AUTH_ADMIN_USERNAME", "root")
|
t.Setenv("MINSTREL_AUTH_ADMIN_USERNAME", "root")
|
||||||
t.Setenv("SMARTMUSIC_AUTH_ADMIN_PASSWORD", "sekret")
|
t.Setenv("MINSTREL_AUTH_ADMIN_PASSWORD", "sekret")
|
||||||
|
|
||||||
cfg, err := Load("")
|
cfg, err := Load("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -96,8 +96,8 @@ func TestAdminBootstrapEnvOverrides(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLibraryEnvOverrides(t *testing.T) {
|
func TestLibraryEnvOverrides(t *testing.T) {
|
||||||
t.Setenv("SMARTMUSIC_LIBRARY_SCAN_PATHS", "/music:/other::/third")
|
t.Setenv("MINSTREL_LIBRARY_SCAN_PATHS", "/music:/other::/third")
|
||||||
t.Setenv("SMARTMUSIC_LIBRARY_SCAN_ON_STARTUP", "true")
|
t.Setenv("MINSTREL_LIBRARY_SCAN_ON_STARTUP", "true")
|
||||||
|
|
||||||
cfg, err := Load("")
|
cfg, err := Load("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,7 +118,7 @@ func TestLibraryEnvOverrides(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSubsonicEnvOverride(t *testing.T) {
|
func TestSubsonicEnvOverride(t *testing.T) {
|
||||||
t.Setenv("SMARTMUSIC_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD", "true")
|
t.Setenv("MINSTREL_SUBSONIC_ALLOW_PLAINTEXT_PASSWORD", "true")
|
||||||
cfg, err := Load("")
|
cfg, err := Load("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Load: %v", err)
|
t.Fatalf("Load: %v", err)
|
||||||
@@ -185,8 +185,8 @@ func TestBrandingYAMLOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBrandingEnvOverride(t *testing.T) {
|
func TestBrandingEnvOverride(t *testing.T) {
|
||||||
t.Setenv("SMARTMUSIC_BRANDING_APP_NAME", "Office Music")
|
t.Setenv("MINSTREL_BRANDING_APP_NAME", "Office Music")
|
||||||
t.Setenv("SMARTMUSIC_BRANDING_DESCRIPTION", "Office tunes.")
|
t.Setenv("MINSTREL_BRANDING_DESCRIPTION", "Office tunes.")
|
||||||
cfg, err := Load("")
|
cfg, err := Load("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Load: %v", err)
|
t.Fatalf("Load: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user