Navidrome

navidrome.org
Navidrome

Self-hosted music streaming server with a web player, and support for the many Subsonic-compatible apps (there's no official mobile app)

Open Source

Navidrome Source Code

Author

navidrome

Description

🎧 Your Personal Streaming Service

#airsonic#madsonic#media-server#music#music-server#navidrome#opensubsonic#raspberry-pi#self-hosted#streamer#streaming-api#streaming-audio#subsonic#subsonic-server

Homepage

https://www.navidrome.org

Repository

  • LicenseGPL-3.0
  • Created24 Feb 16
  • Primary languageGo
  • Size73,322 KB
  • Stars24,103
  • Forks1,755
  • Watchers24,103

Language Usage

Language Usage

Project Health

  • Last commit2 hours ago
  • Open issues302
  • Latest releasev0.64.2

Recent Commits

  • Rucolastico(11 Oct 26)

    feat(ui): add Solarized Light and Dark themes (#6280) * feat(ui): add Solarized Light and Dark themes Add Light and Dark variants of Ethan Schoonover's Solarized palette, built on the Tokyo Night theme structure so they cover the app bar, sidebar, tables, album grid, album/playlist details, login and the player. Text colors are one step stronger than canonical Solarized body text (base01 on light, base1 on dark) so table text keeps WCAG AA contrast. The Dark variant uses cyan instead of violet as the secondary accent, since violet is too dark on base03. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: rucolastico <[email protected]> * fix(ui): improve Solarized contrast and table row hover Address review feedback on the Solarized themes: - Use text-specific shades for links and the login system name, keeping them above 4.5:1 on table surfaces and hover rows (darkened blue on Light, lightened cyan on Dark). Canonical blue stays for controls. - Darken the Light theme's violet accent, which is also used as text (album artist), to keep it above 4.5:1. - Give secondary buttons readable text: base3 on violet (Light) and base03 on cyan (Dark), instead of the body text color. - Apply the row hover color to table cells, whose opaque background was hiding the row hover. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: rucolastico <[email protected]> * fix(ui): keep Solarized app bar title and icons readable The app bar uses the secondary color, whose contrastText matched the app bar background (1:1 in Light, 1.15:1 in Dark). Override the app bar's colorSecondary text with the theme foreground (4.99:1 Light, 4.86:1 Dark), keeping secondary.contrastText for buttons on the accent color. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: rucolastico <[email protected]> --------- Signed-off-by: rucolastico <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Deluan Quintão <[email protected]>

  • Deluan Quintão(11 Oct 26)

    Merge pull request #6313 from navidrome/investigate-6310-20261010 fix(playlists): drop unknown track ids when adding to a playlist

  • Deluan(10 Oct 26)

    refactor(playlists): simplify #6310 tests and update check comments Builds the removeOrphans fixture with a single INSERT instead of shifting positions in two UPDATE passes, and has the Insert test keep the playlist id instead of reading a private repository field. Two comments now say that addTracks owns the existence check as well as the library check, and that Insert closes holes left by any id keepAccessible drops. No behavior change.

  • Deluan(10 Oct 26)

    fix(playlists): drop unknown track ids when adding to a playlist Adding tracks to a playlist skipped the media_file lookup for admins and for users who can see every library, so ids matching no track were stored as orphan playlist_tracks rows and reported as added. The native API returned an inflated "added" count, and the hidden rows left gaps in the positions, so Subsonic songIndexToRemove could delete a hidden row instead of the song at that index. keepAccessible now always checks the ids against media_file. applyLibraryFilter is already a no-op for those users, so the check only drops unknown ids. Rows stored before this fix are still removed by the existing GC removeOrphans step on the next full scan, or on any scan that finds changes. Fixes #6310

  • Deluan Quintão(09 Oct 26)

    Merge pull request #6300 from navidrome/docs/hosting-badges-6258 docs: render managed hosting links as proportionate badges

  • deluan(09 Oct 26)

    docs: use equal-size local hosting badges without stretching artwork

  • deluan(09 Oct 26)

    docs: match README deployment badges to native PikaPods height

  • deluan(09 Oct 26)

    docs: render hosting providers as proportionate deployment badges

  • raphmim(02 Oct 26)

    Minor fixes based on AI suggestions

  • raphmim(02 Oct 26)

    Badges for new cloud providers

  • Deluan Quintão(08 Oct 26)

    fix(scanner): respect FollowSymlinks in watcher-triggered scans (#6293) * fix(scanner): respect FollowSymlinks in watcher-triggered scans With FollowSymlinks disabled, creating a folder symlink inside the library made the watcher schedule a selective scan with the link itself as the target. walkDirTree only checked FollowSymlinks for child entries, so it walked the link and imported its files as duplicates (or, for links that point outside the library, files that should never be scanned). walkDirTree now skips any target folder whose path, or any parent folder, is a symlink when FollowSymlinks is disabled. The skipped target stays in lastUpdates, so rows previously imported through it are marked missing, matching what a full scan does. localFS now implements fs.ReadLinkFS so fs.Lstat can see symlinks instead of following them. Fixes #6292 * test(scanner): run the #6292 symlinked target tests on Windows Remove the SkipOnWindows guard from the symlinked target folder tests, so the go-windows CI job covers the FollowSymlinks fix for selective scans.

  • Deluan Quintão(06 Oct 26)

    fix(artwork): don't crash the server when a playlist's tracks can't be loaded (#6267) Playlist().Tracks returns nil when its internal Get fails (for example when the context is canceled at shutdown), and resolvePlaylist called GetAlbumIDs on it, panicking with a nil pointer dereference. The artwork drain runs on a bare goroutine, so the panic killed the whole server. resolvePlaylist now returns an error when Tracks is nil, and the worker recovers panics per item: it logs the panic with the item details and stack, and marks the item as a failed attempt so the rest of the batch still runs. Fixes #6266

  • Deluan Quintão(06 Oct 26)

    fix(ui): make playlist toggle switches visible in all themes (#6277) * fix(ui): make playlist toggle switches visible in all themes The Public and Auto-import switches in the playlist list did not set a color, so Material-UI used the theme's secondary color. Many themes use secondary as a surface color close to the table background, which made checked switches nearly invisible (Catppuccin, Rosé Pine, Monokai, Moonbase and others). Set color="primary" on the playlist switch, like every other switch in the app, and make primary the default MuiSwitch color in useCurrentTheme so future switches cannot regress. Fixes #6272. * refactor(ui): drop secondary switch overrides from themes Dracula, Gruvbox Dark, Tokyo Night and Tokyo Night Light styled checked MuiSwitch colorSecondary to work around the same invisible-switch problem (Gruvbox in #5064). With primary as the default switch color and every switch in the app using it, no switch renders with colorSecondary anymore, so these overrides are dead code.

  • Deluan Quintão(03 Oct 26)

    fix(share): reuse cached transcodes for share streams and zip downloads (#6262) * fix(share): reuse cached transcodes when streaming from share links Public share streams built the stream request with only the share's format and bit rate, leaving sample rate, bit depth and channels at zero. Regular playback resolves those through the transcode decider (e.g. 48000 Hz for Opus), and they are part of the transcoding cache key, so a track already transcoded during normal playback was transcoded again into a separate, identical cache entry when played through a share link. The public router now resolves share stream requests with the same TranscodeDecider.ResolveRequest used by the Subsonic stream endpoint, so both paths produce the same request and share cache entries. Fixes #6261 * fix(archiver): reuse cached transcodes when zipping downloads Zip downloads (album, artist, playlist and share) built the stream request with only the format and bit rate, leaving sample rate, bit depth and channels at zero. Those are part of the transcoding cache key, so a track already transcoded for playback was transcoded again into a separate cache entry when downloaded in a zip, and vice versa. The archiver now resolves each request with TranscodeDecider.ResolveRequest, the same as single-song downloads and streams. This also applies the decider's defaults, so a zip requested without a bit rate uses the target format's default bit rate instead of leaving it to ffmpeg. * fix(archiver): name zip entries after the resolved transcoding format The transcode decider can pick a different format than the one requested (for example a player's forced transcoding, or a fallback to the default downsampling format when the requested one can't be produced). Zip entry names and the playlist M3U were still built from the requested format, so an entry could end in .mp3 or .flac while holding Opus data. Each track's request is now resolved before its entry name is built, and the name uses the resolved format.

  • Deluan Quintão(02 Oct 26)

    feat(scanner): per-library PID configuration (#6252) * feat(model): add per-library PID config columns * refactor(metadata): pass PID config to ToMediaFile and add spec validation * feat(scanner): rescan only libraries whose PID config changed * feat(server): validate library PID config and rescan on change * feat(ui): edit per-library PID config * fix(ui): label the PID mode selects * fix: tighten per-library PID rescan edge cases An interrupted PID rescan no longer upgrades every library to a full scan, a save that loses the race for the scanner logs at debug, the confirm dialog only shows when the effective PID spec changes, and it now gets translation keys. * refactor(metadata): pass the library to ToMediaFile ToMediaFile and core.Inspect took the library ID and its PID config as separate arguments, so a caller could mix values from two libraries. They now take the model.Library and resolve the effective PID config from it. * chore: tidy per-library PID comments, PropTypes and migration Trim comments that restated the code, add PropTypes to the new UI components, and recreate the migration with make migration-sql. * fix(ui): show the PID spec help under its input * feat(cmd): make inspect use the file's library PID config inspect always used the global PID config, so it showed different IDs than the scanner for files in a library with an override. It now finds the file's library in the DB and uses its effective config, falling back to the global config when there is no DB or the file is outside every library. It never creates a DB. The library path matcher moves from core/playlists to model so both can use it. * refactor: simplify per-library PID code Share the DB-file check between CLI commands, move ErrAlreadyScanning to model so core no longer imports scanner, read the libraries once for insights, and let ValidatePIDSpec accept an empty spec and look tags up directly. In the scanner, use FullScanInProgress instead of a second flag, and skip recomputing album IDs when the album spec did not change. In the UI, share the PID inputs between Create and Edit, and use docsUrl. * feat(ui): add section titles to Library Create and pre-fill Custom PID specs Custom now starts from the global spec, so admins edit a working spec instead of typing one from scratch. * fix(inspect): map files with the library-relative path the scanner uses Inspect gave metadata the file's directory as typed, so folder-based PIDs never matched the DB. It now uses the path relative to the library root, through the scanner's helper, which moves to model. * fix(scanner): say when a PID rescan only covers target folders * fix: reject tag aliases in album PID specs and match root libraries Tags are stored under canonical names, so an alias in a spec always reads as empty. In an album spec that gives every album the same ID, so album specs now require the tag name. Track specs keep accepting aliases, since the default one uses them. LibraryMatcher now matches paths under a library at the filesystem root. * refactor(model): move the tag alias lookup to tag_mappings.go * test: run the library matcher and inspect tests on Windows Build test paths with filepath instead of Unix literals, so they use the OS separator like filepath.Abs output, and drop the Windows skips. * feat(ui): add pt-BR translations for per-library PID settings

  • Deluan Quintão(29 Sept 26)

    fix(ui): don't crash the playlist list when rows lose their record (#6250) * fix(ui): don't crash playlist list rows that lost their record react-admin 3 evicts records fetched more than 10 minutes ago whenever another getList for the same resource completes, but the list keeps its cached ids. The Datagrid then renders those rows with an undefined record, and the Public and Auto-import switches crashed reading record.id. This happened when the playlist list was left open and the sidebar or the add to playlist dialog reloaded a smaller set of playlists. Both switches now render nothing when the row has no record; the next list refresh fills the row in again. * refactor(ui): merge playlist list toggles into one ToggleField The Public and Auto-import switches were copies that differed only in the field they flip. ToggleField now flips its source field, and ToggleAutoImport just shows it for playlists that have a file path. The tests render inside TestContext, so they use react-admin's real hooks instead of mocks.

  • Deluan Quintão(29 Sept 26)

    fix(ui): make Undo and AMusic Save buttons readable (#6249)

  • David Davó(29 Sept 26)

    feat(ui): add played filter to album list (#6207)

  • Matt Van Horn(28 Sept 26)

    fix(ui): honor EnableCoverAnimation for theme cover animations (#6234) * fix: honor cover animation setting in Squiddies Glass Fixes #5170 * fix(ui): move cover animation check into AlbumDetails Apply a noCoverAnimation class from AlbumDetails when enableCoverAnimation is off, so every theme gets the fix. Drop the Squiddies Glass theme changes and its test, and cover the class in AlbumDetails.test.jsx. --------- Co-authored-by: Matt Van Horn <[email protected]> Co-authored-by: Deluan Quintão <[email protected]>

  • Deluan Quintão(28 Sept 26)

    feat(ui): show read-only values in edit forms as dimmed, themable inputs (#6238)

  • DawidKrynski(28 Sept 26)

    fix(playlists): include co-credited album artists when adding an artist to a playlist - #6240 (#6241) * fix(persistence): include co-credited album artists when adding an artist to a playlist - #6240 Signed-off-by: Dawid Krynski <[email protected]> * test(persistence): cover first album artist and track-artist-only in AddArtists The joint track now uses a track artist that is not an album artist, and the AddArtists specs check all three cases: the first album artist still matches, a co-credited album artist matches, and a track-artist-only ID adds nothing. The last case guards against widening the role filter. --------- Signed-off-by: Dawid Krynski <[email protected]> Co-authored-by: Dawid Krynski <[email protected]> Co-authored-by: Deluan <[email protected]>

  • Deluan Quintão(28 Sept 26)

    feat(subsonic): OpenSubsonic API key authentication (#6219) * feat(persistence): store hashed API keys on players * feat(core): refresh key-bound players without renaming them Add Players.Touch, which records usage for a player already identified by an API key without guessing its identity or overwriting its name. Register also stops renaming players that have an API key. Register no longer returns player save errors (or a stale FindMatch ErrNotFound when the save is rate-limited); save failures are only logged, and only the transcoding lookup error is returned, same as Touch. * feat(subsonic): authenticate with OpenSubsonic API keys Co-authored-by: amCap1712 <[email protected]> * feat(subsonic): add tokenInfo and advertise apiKeyAuthentication * feat(server): add endpoints to generate and revoke player API keys * feat(ui): manage player API keys Co-authored-by: amCap1712 <[email protected]> * fix(subsonic): throttle API keys per key and IP A stale key on one device exhausted the shared per-IP bucket and locked out every valid key from the same IP. The limiter only stores a hash of the bucket string, so the key is not retained. Also adds e2e coverage of API key auth through the real repository, and clarifies the player resolution log message. * fix(ui): keep the new API key dialog open until closed The key is shown only once, so Escape and backdrop clicks no longer dismiss it. Also clarifies when the key can be used as a password. * refactor: simplify API key code paths Share the player refresh tail between Register and Touch, fold the ownership-filtered write tail into execOwned, parse the query once for apiKey conflicts, derive HasAPIKey in the player mock, share the player form inputs between create and edit, and pick the delete button by key state instead of spreading conditional props. * feat(players): set API keys through the player record The key is a write-only apiKey field applied on save: required and owner-only on create, optional on edit, empty to revoke. Replaces the generate/revoke endpoints. * fix(players): reject API keys already in use Creating or editing a player with a key another player already has now returns a validation error instead of a 500, and a create that loses the race no longer leaves a keyless player behind. Ownership is checked before the key on create. * feat(ui): edit player API keys as a form field Replaces the show-once dialog, whose icon-less Close button was invisible on mobile. The key is generated in the browser, required and pre-filled on create. * fix(ui): keep new player API keys out of the record cache The json-server create response echoes the request body, and undoable edits merge the payload into the cache, so the key could reappear on the edit page. Strip it from the create result and save player edits pessimistically. Also fall back to a prompt when the clipboard write fails. * fix(ui): polish player API key field Set userId on the created player record so owner actions show immediately, and show a neutral no-key message to non-owners. * refactor: simplify player API key create and field Write the key hash in the create INSERT so the unique index settles races, re-read the created player instead of hand-building the cached record, reuse isWritable for the revoke check, and collapse the key field's derived state and generate/regenerate buttons. * fix(ui): let the API key field size like other inputs fullWidth is now opt-in instead of forced. * fix(ui): align the API key field with other player inputs Apply react-admin's input className, move the actions (now including Copy) below the field, and use a monospace font so the whole key fits. * fix(ui): redirect to the player list after create Matches the other create pages. * refactor(persistence): name the write-access rule for owned rows Owned-row writes now say which row they target and who may write it: ownedRow(rowID, ownerOrAdmin|ownerOnly) builds the WHERE, updateOwnedRow applies it, and SetAPIKey uses ownerOnly instead of a hand-built user_id filter. updateOwned/deleteOwned keep their signatures. * fix(players): apply an edit's key change and fields atomically Update now runs SetAPIKey and the column update in one transaction. Also shares the key format check, drops FindByAPIKey's unneeded empty-key guard, and sets the context username only on the apiKey path. * fix(subsonic): treat any credential param sent with apiKey as a conflict The spec requires error 43 when u, p, t or s is present with apiKey, even with an empty value. * refactor(subsonic): leave the player cookie code unchanged for key-bound requests Return early instead of wrapping the cookie block, so the diff (and CodeQL's view of it) matches master. * fix(subsonic): don't count key lookup errors as failed logins A database error while checking a key sent as the password now surfaces as a server error instead of a bad password, so it no longer feeds the failed-login limiter. * feat(players): use nds_ as the API key prefix Part of a Navidrome secret prefix family (nd + a letter for the kind), alongside ndg_ for API v1 grants. * feat(ui): make player API keys easier to find Label the Settings menu entry "Players & API keys", add an API key filter to the player list, show the key icon in the mobile list, and add Brazilian Portuguese translations for the new player strings. Signed-off-by: Deluan <[email protected]> * feat(ui): always show the player API key filter Signed-off-by: Deluan <[email protected]> * fix(ui): hide the unset Last Seen date in the player list Players created by hand have no last_seen yet, which showed as 12/31/1. Signed-off-by: Deluan <[email protected]> --------- Signed-off-by: Deluan <[email protected]> Co-authored-by: amCap1712 <[email protected]>

  • Deluan Quintão(27 Sept 26)

    fix(server): exit with an error code when the server fails to start (#6236) When a startup step failed (for example, the port was already in use), runNavidrome only logged the error and returned. In service mode, service.Run() kept waiting for a stop signal, so the process stayed up serving nothing and the service manager never restarted it. A plain run exited with code 0. runNavidrome now returns the error, unless its context was cancelled by a normal shutdown. Both the plain run and the service goroutine exit with code 1 on that error. The systemd unit no longer lists 1, 2 and 8 in SuccessExitStatus, so Restart=on-failure restarts the service on exit code 1. Fixes #6235

  • Deluan Quintão(27 Sept 26)

    fix(log): redact LastFM keys and Prometheus password in config dump (#6233) The startup Configuration dump is rendered with pretty.Sprintf("%# v"), which pads multi-line struct fields with spaces after the colon. The ApiKey and Secret redaction patterns required the quote right after the colon, so LastFM.ApiKey and LastFM.Secret were logged in clear text even with EnableLogRedacting on. Allow optional whitespace after the colon, like the other config patterns already do. Prometheus.Password had no redaction pattern at all. Add one that also skips escaped quotes, since the password can hold any character and pretty prints it Go-quoted. Add tests for the padded and unpadded forms, plus one that redacts a real pretty.Sprintf dump of LastFM- and Prometheus-shaped structs so a padding change in pretty can't bring the leak back. Reported in https://github.com/navidrome/navidrome/discussions/6232

  • Deluan Quintão(26 Sept 26)

    feat(api): add the API v1 foundation behind DevAPIv1 (#6227) * feat(api): add OpenAPI v1 spec skeleton, lint ruleset and bundle tooling vacuum v0.30.6's `bundle --composed` mangles component names for this spec's multi-file layout (duplicates Problem as Problem__schemas etc.), so api-bundle uses the Redocly CLI (npx @redocly/cli bundle) instead. * fix(api): pin the Redocly CLI version Tried moving components out of the root document (per libopenapi's nested_files example) so vacuum's own bundler could produce clean names, but any component declared via $ref inside components.* still gets a __<parent>-suffixed twin regardless of collisions elsewhere, so vacuum's --composed bundler can't cleanly bundle this spec. Pin the already-working Redocly fallback to an exact version instead of @latest. * fix(api): bundle the OpenAPI spec with vacuum vacuum's --composed bundler suffixes any component reached via a $ref written directly inside the root document's own components.* block, regardless of collisions elsewhere. Dropping the root-level schemas/ parameters/responses declarations (keeping only securitySchemes, and leaving every component file under api/openapi/components/ untouched) lets vacuum bundle cleanly with no __ suffixes, going back to Go-only tooling. Components nothing references yet (ListMeta, offset, limit, BadRequest, Unauthorized, Forbidden, NotFound) are absent from the bundle until a later task's operation references them. * fix(api): make spec lint rules cover all schemas and error codes nd-schema-property-descriptions targeted $.components.schemas, but our schemas live in path/response files, not the root document, so it was dead code; switched to $..properties[*] to walk every resolved schema wherever it ends up. nd-error-responses-are-problems only checked a hardcoded status-code list; switched to a patternProperties schema matching the full 4xx/5xx range. Also: api-diff now diffs against the merge-base with API_DIFF_BASE (falling back to its tip with a notice if no merge-base exists), gen no longer depends on api-gen until Task 3 wires up oapi-codegen, and api-lint suppresses vacuum's banner. * feat(api): embed the bundled OpenAPI spec and expose its version * feat(api): generate the v1 server interface with oapi-codegen * feat(api): add RFC 9457 problem responses for API v1 * feat(api): add API v1 router with /server discovery and spec routes * fix(api): serve the OpenAPI document without range support * feat(api): mount API v1 behind the DevAPIv1 flag * chore(ci): lint, regenerate and diff the OpenAPI v1 spec * refactor(api): tighten spec version access, lint rules and test naming * refactor(api): simplify spec routes, tests and OpenAPI tooling Share one If-None-Match parser (utils/req) between the image and spec routes, declare the YAML spec response as an object so tests need no decoder override, and reuse ETag/304 spec components. Install the OpenAPI tools only when missing or at a different version, fail api-diff when its base ref does not exist, and in CI cache the tools, fold regeneration into the go generate check, and fetch only the PR base commit for the breaking-change gate. * refactor(api): raise the list limit maximum to 2000 and drop the flag test * feat(api): treat added enum values as non-breaking Enums in API v1 are open: clients must accept unknown values. api-diff now downgrades response-property-enum-value-added to INFO, while removing a value from a request enum stays breaking. * feat(api): gate breaking changes on x-stability-level Every operation declares x-stability-level (alpha, beta, stable). oasdiff ignores breaking changes to alpha operations and rejects lowering a level, so unreleased endpoints can evolve while beta and stable ones stay additive. All current operations start as alpha. * feat(api): declare loginMethods as an enum Prefix generated enum constants with their type name so enums sharing a value (for example password) cannot collide in package apiv1. * feat(api): send Allow on 405 and answer HEAD wherever GET is routed chi only sets Allow in its default 405 handler, so the problem-format handler now builds it by matching each method against the v1 router. HEAD requests fall back to the GET route, as RFC 9110 expects. * refactor(api): hash the spec ETag with xxh3 The bytes are compiled in, and the digest was truncated to 64 bits anyway, so this matches the artwork ETags instead of paying for cryptographic strength we discard. * docs(api): explain the about:blank problem type * feat(api): make code the problem identifier and omit a blank type RFC 9457 says clients switch on the type URI, but no adopter surveyed ships both a populated type and a separate code. Declare code as an enum, and send type only once a problem has semantics of its own. * fix(api): advertise the configured base path in the served OpenAPI spec With BaseURL=/music the API is mounted at /music/api/v1, but the spec told clients to call /api/v1 at the host root. The server now rewrites servers[0].url to BasePath + /api/v1 when it serves the document. Relative server URLs were tested first: "." and "../v1" work in openapi-generator, Swagger UI and Redoc, but Scalar resolves them against the page origin, so it breaks even without a base path. The committed bundle keeps /api/v1, and a test pins that it appears exactly once, which the rewrite relies on.

  • Deluan Quintão(26 Sept 26)

    refactor(scanner): remove the unused legacy ffmpeg metadata extractor (#6231) * refactor(scanner): remove the legacy ffmpeg metadata extractor The ffmpeg extractor in scanner/metadata_old has not been wired into the scanner since the taglib-only rewrite, so it was only exercised by its own tests. Remove the package, the FFmpeg.Probe method and its ffmetadata command that only it used, and the startup fallback for Scanner.Extractor="ffmpeg". Configs that still set it keep working: unknown extractors already fall back to taglib with a warning. * fix(conf): warn and fall back to taglib for an unknown Scanner.Extractor Validate the option when loading the config, so invalid values such as the removed "ffmpeg" extractor are reported once at startup instead of only when a library storage is created.

  • Deluan Quintão(26 Sept 26)

    fix(scanner): register .webm as audio/webm (#6230) Go 1.27 changed the built-in MIME type for .webm from audio/webm to video/webm, so the scanner stopped treating WebM files as audio after the Go bump in 0.64.0. Map .webm to audio/webm in mime_types.yaml so it no longer depends on the Go version.

  • Deluan(25 Sept 26)

    chore: update Go dependencies to latest versions

  • Deluan Quintão(25 Sept 26)

    refactor(persistence): stateless repositories with per-call context (#6149) * refactor(persistence): adopt generic deluan/rest repository API Pin deluan/rest to the refactor branch. REST-facing repository methods take a context and return typed values. Drop DataStore.Resource and ResourceRepository; the native API names typed repositories directly through a per-request adapter that later commits remove. * refactor(persistence): base repository helpers take a context * refactor(persistence): LibraryRepository takes a context per call * refactor(persistence): PropertyRepository takes a context per call * refactor(persistence): UserPropsRepository takes a context per call * refactor(persistence): TranscodingRepository takes a context per call * refactor(persistence): ShareRepository takes a context per call * refactor(persistence): PlayerRepository takes a context per call * refactor(persistence): RadioRepository takes a context per call * refactor(persistence): PlayQueueRepository takes a context per call * refactor(persistence): Tag and Genre repositories take a context per call * refactor(persistence): PluginRepository takes a context per call * refactor(persistence): Scrobble repositories take a context per call * refactor(persistence): FolderRepository takes a context per call * refactor(persistence): Artwork repositories take a context per call * refactor(persistence): UserRepository takes a context per call * refactor(persistence): ArtistRepository takes a context per call ReadAll no longer rewrites the shared sort mappings for the role filter; it works on a per-call copy. * test(persistence): assert artist role sort sanitization in ReadAll * refactor(persistence): AlbumRepository takes a context per call * test(persistence): pass the test context to album repository helpers * refactor(persistence): MediaFileRepository takes a context per call * refactor(persistence): Playlist repositories take a context per call * refactor(persistence): build all repositories once per store * refactor(core): REST repository wrappers are built once * refactor(persistence): repositories are stateless Remove the context field from the base repository and the per-request REST adapter. Enable the containedctx linter so no repository can hold a request context again. * chore(lint): skip containedctx in test files * refactor: share simplifications from the stateless repositories sweep Add deleteOwnedAll on sqlRepository and use it in player/share Delete to remove the duplicated bulk-delete loop; have Share.Repository() return model.ShareRepository so subsonic sharing.go drops its repeated type assertions. * chore(core): assert REST wrappers implement Persistable * chore: reformat imports * perf(persistence): build repositories on first use Each transaction store used to construct all 21 repositories up front, paying for filter and sort mapping setup the block never touched. Fields are now sync.OnceValue thunks, so a store only builds what it uses. * fix(persistence): clean plugin references per deleted user A bulk user delete that fails on a later id had already removed the earlier rows but skipped their plugin cleanup. Cleanup now runs right after each successful delete. * fix(core): unload disabled plugins even when a user delete fails A bulk delete can fail on a later id after earlier users were removed and their plugins auto-disabled. The wrapper returned before unloading, leaving those plugins running until the next successful delete or a restart. * chore(deps): pin deluan/rest to v1.0.1 Replaces the pseudo-version of the refactor branch with the tagged release. REST error messages now name the bare type (Artist, not model.Artist). * test: use the spec context instead of context.Background() Replace the context.Background()/context.TODO() calls this branch added to tests with the spec's ctx, GinkgoT().Context(), or t/b.Context(), so repository calls are bound to the running spec's lifetime. * test: declare the spec context once per Describe Set ctx from GinkgoT().Context() first in each top-level BeforeEach and reuse it, building user contexts on top of it instead of repeating inline calls.

  • Deluan Quintão(25 Sept 26)

    feat(archiver): add folder cover image to downloaded zips (#6224) * feat(archiver): add folder cover image to downloaded zips Album, artist, playlist and share downloads now include the item's cover as folder.<ext>, the image most players and car stereos show for the files next to it. This helps users who copy transcoded downloads to offline devices, since transcoding drops the embedded artwork (#5841). Album folders get the album cover, the artist zip root gets the artist image, and playlist and share zips get the playlist or shared item's cover at the root. The image is the same one getCoverArt serves, resized to 500px (not square), and named by its detected type. Items without artwork get no image, and a cover that fails to load is logged and skipped so it never breaks the archive. The archiver reads covers through a new core.CoverArtReader interface, implemented by artwork.CoverArtReader, to avoid an import cycle between core and core/artwork. * refactor(archiver): read covers through artwork.Artwork directly The archiver no longer needs a local CoverArtReader interface and adapter. The only reason core/artwork imported core was a core.AbsolutePath call in loadArtistFolder, which now reads the library path from the repository and cleans it the same way. With the import cycle gone, the archiver takes artwork.Artwork and treats ErrUnavailable and ErrNotFound as no cover. Covers are now written after each album's tracks (and after all tracks for the archive root), so a slow artwork lookup does not delay the first bytes of the download. * fix(archiver): read share covers as admin so private playlists keep theirs Public share downloads run with an anonymous context, and the playlist repository hides private playlists from anonymous users, so a zip of a shared private playlist silently had no folder image. Like the public image handler, the share itself is the authorization: the cover lookup now runs with an admin user. Only the cover read is elevated; streaming keeps the anonymous context, so the transcode limiter still keys public downloads the same way. * style(archiver): trim comments Shorten the comments added by the folder cover image change and drop the ones the names already explain. * fix(archiver): add one cover per album folder in artist zips Albums with the same name share a zip folder (pre-existing naming), so an artist zip with two such albums wrote two folder.<ext> entries at the same path. Keep the first cover and skip the rest for that folder.

Navidrome Security

4.9/10

Repo Security Summary

Updated 24 Aug 26

  • Code-Review1/10
  • Maintained10/10
  • Dangerous-Workflow10/10
  • CII-Best-Practices0/10
  • Token-Permissions0/10
  • Binary-Artifacts10/10
  • Security-Policy0/10
  • License10/10
  • Fuzzing0/10
  • Branch-ProtectionN/A
  • Signed-Releases0/10
  • Packaging10/10
  • SAST8/10
  • Pinned-Dependencies1/10

Security Advisories (28)

  • mediumPatchedCVSS 6.5

    GHSA-f295-6wp9-qqfgLogin rate limit bypass via spoofed X-Forwarded-For, X-Real-IP and True-Client-IP headers

  • mediumPatchedCVSS 4.3

    GHSA-pcjv-h48m-833gBookmarks, playlist tracks and now-playing skipped the library filter, exposing track metadata from other libraries

  • mediumPatchedCVSS 4.4

    GHSA-pr2j-mfc8-qjccPlugin HTTP and WebSocket SSRF guard bypass via DNS names

  • mediumPatchedCVSS 6.5

    GHSA-vwq6-xrw5-phpgCross-library file read through the M3U `#EXTALBUMARTURL` playlist cover

  • highPatchedCVSS 7.5

    GHSA-f22h-6qxh-rqq2Negative size parameter bypasses the artwork size clamp and allows memory exhaustion DoS via getCoverArt and share images

  • mediumPatchedCVSS 6.5

    GHSA-3rwv-f797-f9p3Share creation validated only the first resource ID, letting restricted users share and download media from other libraries

  • mediumPatchedCVSS 6.4

    GHSA-37h4-53gj-cw8mPlayer records could be overwritten, and their ownership taken over, by any authenticated user

  • highPatchedCVSS 7.1

    GHSA-82gh-4ggp-gfg5Share creation trusts a client-supplied userId, exposing other users' library content

  • highPatchedCVSS 8.8

    GHSA-hm54-32q6-3rcrSQL injection via the artist role parameter when sorting by song count, album count or size

  • mediumPatchedCVSS 6.5

    GHSA-8hjf-6h34-82hrSSRF in remote artwork fetches via M3U `#EXTALBUMARTURL` and agent-supplied image URLs

  • mediumPatchedCVSS 4.3

    GHSA-mpgw-cc94-xrppCross-library playlist import and sharing bypass in Navidrome

  • mediumPatchedCVSS 5.3

    GHSA-r5qr-m328-qcf4Arbitrary file read via symlinks in the music library

  • lowPatchedCVSS 3.1

    GHSA-x65f-m8x9-pjxmNon-admin user could update another user's player via PUT /api/player/{id}

  • mediumPatchedCVSS 4.3

    GHSA-4p3r-6362-833wTranscoding command templates exposed to non-admin users

  • highPatchedCVSS 7.4

    GHSA-p994-r776-mw52Subsonic API allows unauthenticated brute-force of user passwords

  • mediumPatchedCVSS 5.4

    GHSA-8jrh-w926-8rvwLastFM scrobble session hijack via unauthenticated IDOR in /api/lastfm/link/callback

  • mediumPatchedCVSS 6.3

    GHSA-3g4p-jhv2-xrxfMissing ownership checks on share endpoints let any authenticated user read, modify, and delete other users' shares

  • lowPatched

    GHSA-jw24-qqrj-633cSubsonic internet radio management endpoints lacked a route-level admin guard (no security impact)

  • lowPatched

    GHSA-wp9c-pw66-c6j2Public share stream URLs keep working after the share expires or is deleted

  • mediumPatchedCVSS 6.1

    CVE-2026-25578XSS via comment from song metadata

  • criticalPatched

    CVE-2026-25579Denial of Service and disk exhaustion via oversized `size` parameter in `/rest/getCoverArt` and `/share/img/<token>` endpoints

  • criticalPatched

    CVE-2025-48949SQL Injection via role parameter

  • criticalPatched

    CVE-2025-48948Navidrome Transcoding Permission Bypass Vulnerability Report

  • mediumPatched

    CVE-2025-27112Authentication bypass in Subsonic API with non-existent username

  • highPatchedCVSS 7.1

    CVE-2024-56362Plaintext Storage of JWT Secret in navidrome.db

  • criticalPatched

    CVE-2024-47062Multiple SQL Injections and ORM Leak

  • highPatchedCVSS 8.1

    CVE-2024-32963Parameter Tampering vulnerability

  • highPatchedCVSS 8.6

    CVE-2023-51442Authentication bypass vulnerability in navidrome's subsonic endpoint

Navidrome Website

Website

Navidrome

Welcome to Navidrome! Learn More Download Your Personal Streaming Service Navidrome allows you to enjoy your music collection from anywhere, by making it available through a modern Web UI and through a wide range of third-party compatible mobile apps, for both iOS and Android devices.

Redirects

Does not redirect

Security Checks

All 65 security checks passed

Server Details

  • IP Address104.21.43.229
  • LocationSan Francisco,California,United States of America,NA
  • ISPCloudFlare Inc.
  • ASNAS13335

Associated Countries

  • USUS
  • DEDE

Safety Score

Website marked as safe

100%

Trackers

Loads 2 third-party trackers

  • cloudflareinsights.comCloudflare, Inc.
  • googletagmanager.comGoogle Ads

Blacklist Check

www.navidrome.org was found on 0 blacklists

  • AntiSocial Blacklist
  • Artists Against 419
  • Badbitcoin
  • Bambenek Consulting
  • CERT Polska
  • CoinBlockerLists
  • CRDF
  • CryptoScamDB
  • EtherAddressLookup
  • EtherScamDB
  • Fake Website Buster
  • MetaMask EthPhishing
  • NABP Not Recommended Sites
  • OpenPhish
  • PetScams
  • PhishFeed
  • PhishFort
  • Phishing.Database
  • PhishStats
  • PhishTank
  • Phishunt
  • RPiList Not Serious
  • Scam.Directory
  • SecureReload Phishing List
  • Spam404
  • StopGunScams
  • Suspicious Hosting IP
  • ThreatFox
  • ThreatLog
  • TweetFeed
  • URLhaus
  • ViriBack C2 Tracker

Website Preview

Website preview

Navidrome Docker

Container Info

Navidrome

Navidrome is an open source web-based music collection server and streamer. It gives you freedom to listen to your music collection from any browser or mobile device. It's like your personal Spotify!

#Multimedia#Music

Run Command

docker run -d \
  -e PUID=${PUID} \
  -e PGID=${PGID} \
  -e PORT=${PORT} \
  

Compose File

version: 3.8
services:
  navidrome:
    environment:
      PUID: 1000
      PGID: 1000
      PORT: 

Environment Variables

  • Var NameDefault
  • PUID1000
  • PGID1000
  • PORTnull

Navidrome Socials

Navidrome Reviews

More Media Servers

  • Self-hosted server for streaming your own films, TV, music and live TV to apps on most devices. Volunteer-run, with no telemetry or paid tiers. Some media endpoints don't require login, so avoid exposing it directly to the internet.

About the Data: Navidrome

Change History

Edit Navidrome Data

You can edit Navidrome's entry in this section of awesome-privacy.yml by submitting a PR to our GitHub repo.
Note that some of the information shown above has been aggregated from external sources, a list of these can be found data documentation.

Origin Data

Modify Data

API

You can access Navidrome's data programmatically via our API. Simply make a GET request to:

https://api.awesome-privacy.xyz/v1/services/navidrome

The REST API is free, no-auth and CORS-enabled. To learn more, view the API Docs or read the API Usage Guide.

Share Navidrome

Help your friends compare Media Servers, and pick privacy-respecting software and services.
Share Navidrome and Awesome Privacy with your network!