2.7.0
multi-user gets real — and so does securing it. each profile can connect its own streaming accounts and pull its own playlists, the admin gets a quick way to switch active sources, plus a full opt-in login system (real username/password accounts, or sit behind a reverse proxy), a stack of bug fixes, and some hardening.
living doc — i'll keep adding to this as 2.7.0 comes together (deezer/qobuz per-profile still to come).
per-profile accounts (the headline)
- new My Accounts modal (the ♫ button by your profile, open to everyone): connect your own spotify / tidal / listenbrainz and from then on your playlist browsing/pulling uses your account — not the admin's.
- metadata + downloads stay global (the admin's accounts run the app's background work). only playlist reads use the personal account. so two people see their own playlists at the same time without stepping on each other.
- shared-app model: you don't make your own dev app — you just log in (spotify/tidal) or paste a token (listenbrainz) against the admin's existing app.
show_dialogforces the account picker so you can't accidentally grab the admin's session. - non-regressive by design: admin, anyone who hasn't connected, and all background workers fall back to the global accounts exactly as before — so single-user setups notice nothing. tidal's per-profile token refresh is redirected to the profile so it can never clobber the admin's tokens.
per-profile automations (auto-sync runs as you)
- background automations now run as their owner. before, anything on a timer had no logged-in session so it fell back to admin — meaning a non-admin's auto-sync pipeline would've used the admin's account. now the engine tags each run with its owner (a contextvar it sets around the handler), and
get_current_profile_id()honours it only when there's no real request. - the auto-sync playlist read then resolves that owner's account (the source adapters already grabbed their client through a getter — just pointed it at the per-profile resolver). so a non-admin's scheduled pipeline pulls their playlist.
- admin's existing auto-sync pipelines are untouched — they're profile 1, which resolves to the global account exactly as before. proven by tests (admin → global; non-admin automation → its owner; context resets even if the handler throws).
- watchlist scan + wishlist download were already per-profile (they loop every profile). deezer/qobuz auto-sync still uses the global account (deferred).
quick switch for admin
- the sidebar Service Status panel is now clickable (admin only) — opens a manage-workers-style modal to switch the active metadata source / media server / download source (+ real hybrid drag), with brand logos and a hero header. surfaces the configured-vs-effective source so "spotify" vs "spotify (no auth)" is never confusing.
- Manage Profiles modal got a visual revamp too.
secure access (new — all opt-in, off by default)
a normal direct/LAN setup behaves exactly as before. none of this turns on unless you flip it on, and the launch PIN keeps working untouched if that's what you use.
- native login — flip on "require login" and each profile becomes a real account: a username + password sign-in screen replaces the picker + PIN. password is separate from the quick-switch PIN (a 4-digit PIN shouldn't be the thing guarding a public instance), hashed, never sent to the browser. brute-force limited per IP, generic errors so usernames don't leak, and anti-lockout — you can't enable it until the admin has a password.
- forgot-password recovery — set a security question (pick one or write your own) + answer; if you forget your password you answer it on the sign-in screen to reset. answer is hashed, matched forgivingly (case/spacing).
- reverse proxy mode — for running behind nginx / caddy / traefik: trusts
X-Forwarded-*(correct client IP + https detection), marks the session cookie secure, adds security headers (HSTS etc.). off by default so direct http:// LAN access is untouched (it'd otherwise break the cookie). + a fullSupport/REVERSE-PROXY.mdguide with the socket.io upgrade headers everyone forgets. - auth-proxy trust — let authelia / authentik / oauth2-proxy be the gatekeeper: trust a
Remote-User-style header and skip the launch PIN for already-authenticated requests. spoof-safe (ignored unless you explicitly name the header). - PIN brute-force limit — the launch PIN now backs off after 10 wrong tries from one IP (correct entry never affected, self-heals).
- it all lives in a reorganized Settings → Security page: three labelled groups (PIN / user accounts / reverse proxy), step-ordered, with the "require login" toggle visibly greyed out until you set an admin password, confirm-password fields, and "✓ saved" state so it doesn't look unset after a refresh.
bug fixes
- #835 — a
/in a song title (sawano's "YouSeeBIGGIRL/T:T") was treated as a path separator, truncating the search target and quarantining valid youtube/tidal downloads. - #836 — a rejected slskd download hung the task at "downloading" forever; added a terminal-grace backstop so a stuck error state gets marked failed and frees the worker.
- #837 — manual find & add recreated the whole jellyfin/emby playlist instead of appending the one track.
- #838 — auto-sync capped public spotify playlists at 100 tracks (embed scraper); now uses the full paginated fetch.
- #843 — "discovery state not found" when fixing a match after a restart/import; the durable cache write was wrongly gated on in-memory state, and keyed by the full artist string instead of the first artist.
- find & add search — exact-title matches were buried under a case-sensitive sort (lowercase "bad guy" lost to every capitalized "Bad Guy" and fell off the limit); now relevance-ordered.
artist sync = a mini deep scan
- the artist-detail sync button is now genuinely a single-artist deep scan: it reuses the same server-diff stale-removal as the whole-library deep scan, scoped to one artist. the old disk-existence check (which could mass-delete on an unreachable mount) is gone — removal only runs when the server pull succeeds, with the same >50%-unseen safety guard, admin-only.
security + hardening
- #842 — fixed the launch PIN re-triggering the full first-run setup wizard on every visit (regression from the #832 server-side PIN gate; the setup check was getting blocked before the PIN screen).
- destructive endpoints (db restore/backup/vacuum, library/track delete, cache clear, plex clear-library, api-key mint) are now admin-only server-side — they were UI-hidden but API-open. profile-scoped stuff (e.g. clearing your own wishlist) deliberately left open.
notes
- version constant still reads 2.6.9 (inherited base) — bump to 2.7.0 + what's-new entry at release time.
- every piece landed with seam-level + endpoint + regression tests; full suite green (only the pre-existing soundcloud mock failures, unrelated).
- still on the global account for now (next up): per-profile deezer + qobuz (their playlist login is tangled with the download login — needs careful isolation). last.fm is out (it's a scrobbler, no playlists to pull).