Merge pull request 'fix(auth): subsonic_password on bootstrap + dev compose defaults' (#10) from dev into main

This commit was merged in pull request #10.
This commit is contained in:
2026-04-19 23:07:13 +00:00
3 changed files with 38 additions and 4 deletions
+19 -2
View File
@@ -16,8 +16,10 @@ services:
POSTGRES_USER: minstrel
POSTGRES_PASSWORD: minstrel
POSTGRES_DB: minstrel
ports:
- "5432:5432"
networks:
- minstrel
# ports:
# - "5432:5432"
volumes:
- minstrel-pgdata:/var/lib/postgresql/data
healthcheck:
@@ -34,8 +36,23 @@ services:
environment:
SMARTMUSIC_DATABASE_URL: postgres://minstrel:minstrel@postgres:5432/minstrel?sslmode=disable
SMARTMUSIC_LOG_FORMAT: text
SMARTMUSIC_LIBRARY_SCAN_PATHS: /music
SMARTMUSIC_LIBRARY_SCAN_ON_STARTUP: "true"
SMARTMUSIC_AUTH_ADMIN_USERNAME: admin
# SMARTMUSIC_AUTH_ADMIN_PASSWORD left unset on purpose — bootstrap
# 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.
networks:
- minstrel
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.
- /mnt/Media/Music:/music:ro
volumes:
minstrel-pgdata:
networks:
minstrel:
+16 -2
View File
@@ -64,12 +64,26 @@ func Bootstrap(ctx context.Context, pool *pgxpool.Pool, cfg config.AdminBootstra
return fmt.Errorf("auth: insert admin: %w", err)
}
// Mirror the bootstrap password into subsonic_password so Subsonic clients
// (Feishin, Symfonium, …) can authenticate via t+s without the operator
// having to run a separate SQL update after first boot. This is plaintext
// at rest — the cost of Subsonic's legacy auth — and matches what servers
// like Navidrome do. Operators who don't want a plaintext copy can clear
// it later with SetSubsonicPassword(nil).
subPass := password
if err := q.SetSubsonicPassword(ctx, dbq.SetSubsonicPasswordParams{
ID: user.ID,
SubsonicPassword: &subPass,
}); err != nil {
return fmt.Errorf("auth: set subsonic password: %w", err)
}
logger.Info("admin bootstrapped", "username", cfg.Username, "user_id", user.ID.String())
if passwordGenerated {
fmt.Fprintf(os.Stderr, "\n--- minstrel admin bootstrap (shown once) ---\nusername: %s\npassword: %s\napi_token: %s\n---\n\n",
fmt.Fprintf(os.Stderr, "\n--- minstrel admin bootstrap (shown once) ---\nusername: %s\npassword: %s\napi_token: %s\n(password works for both the native UI and Subsonic clients)\n---\n\n",
cfg.Username, password, apiToken)
} else {
fmt.Fprintf(os.Stderr, "\n--- minstrel admin api token (shown once) ---\nusername: %s\napi_token: %s\n---\n\n",
fmt.Fprintf(os.Stderr, "\n--- minstrel admin api token (shown once) ---\nusername: %s\napi_token: %s\n(your configured password now also works for Subsonic clients)\n---\n\n",
cfg.Username, apiToken)
}
return nil
+3
View File
@@ -63,6 +63,9 @@ func TestBootstrap_Integration(t *testing.T) {
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte("hunter2")); err != nil {
t.Errorf("bcrypt compare: %v", err)
}
if user.SubsonicPassword == nil || *user.SubsonicPassword != "hunter2" {
t.Errorf("subsonic_password = %v, want \"hunter2\"", user.SubsonicPassword)
}
byToken, err := q.GetUserByAPIToken(ctx, user.ApiToken)
if err != nil {