From 68e6fb9603abd209d920c86281e6ccafb32bd2d4 Mon Sep 17 00:00:00 2001 From: Chris Moultrie <821688+tebriel@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:07:08 -0400 Subject: [PATCH 1/5] navidrome: introduce plugin infrastructure --- nixos/modules/services/audio/navidrome.nix | 62 ++++++++++++++++- nixos/tests/navidrome.nix | 4 +- pkgs/by-name/na/navidrome/package.nix | 11 +++ pkgs/by-name/na/navidrome/plugins/README.md | 8 +++ .../na/navidrome/plugins/build-plugin.nix | 67 +++++++++++++++++++ pkgs/top-level/all-packages.nix | 11 +++ 6 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 pkgs/by-name/na/navidrome/plugins/README.md create mode 100644 pkgs/by-name/na/navidrome/plugins/build-plugin.nix diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix index 8fa7e6dc3db5..47f7a17162e9 100644 --- a/nixos/modules/services/audio/navidrome.nix +++ b/nixos/modules/services/audio/navidrome.nix @@ -7,13 +7,18 @@ let inherit (lib) + literalExpression + mkDefault mkEnableOption mkPackageOption mkOption maintainers ; inherit (lib.types) + addCheck bool + listOf + package port str submodule @@ -29,6 +34,36 @@ in package = mkPackageOption pkgs "navidrome" { }; + plugins = mkOption { + type = listOf ( + addCheck package (p: p.isNavidromePlugin or false) + // { + name = "navidrome plugin"; + description = "package that is a navidrome plugin"; + } + ); + default = [ ]; + description = "List of Navidrome plugins"; + example = literalExpression '' + with pkgs.navidromePlugins; [ + ]; + ''; + }; + + finalPackage = mkOption { + type = package; + readOnly = true; + default = cfg.package.override { + inherit (cfg) plugins; + }; + defaultText = literalExpression '' + config.services.navidrome.package.override { + inherit (config.services.navidrome) plugins; + } + ''; + description = "The final navidrome package including all selected plugins."; + }; + settings = mkOption { type = submodule { freeformType = settingsFormat.type; @@ -51,6 +86,29 @@ in description = "Enable anonymous usage data collection, see for details."; type = bool; }; + + Plugins = { + Enabled = mkOption { + default = (builtins.length cfg.plugins) != 0; + defaultText = literalExpression "builtins.length \"\${config.services.navidrome.plugins != 0}\""; + description = '' + Enable plugin support in navidrome. + + This is automatically enabled if {option}`services.navidrome.plugins` is used. + ''; + }; + Folder = mkOption { + default = "${cfg.finalPackage}/share/plugins"; + description = '' + The folder containing navidrome plugins. + + This directory is automatically created from plugins defined in {option}`services.navidrome.plugins`. + ''; + readOnly = true; + type = str; + defaultText = literalExpression "\"\${config.services.navidrome.finalPackage}/share/plugins\""; + }; + }; }; }; default = { }; @@ -78,7 +136,7 @@ in description = "Whether to open the TCP port in the firewall"; }; - environmentFile = lib.mkOption { + environmentFile = mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Environment file, used to set any secret ND_* environment variables."; @@ -114,7 +172,7 @@ in wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = '' - ${getExe cfg.package} --configfile ${settingsFormat.generate "navidrome.json" cfg.settings} + ${getExe cfg.finalPackage} --configfile ${settingsFormat.generate "navidrome.json" cfg.settings} ''; EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ]; User = cfg.user; diff --git a/nixos/tests/navidrome.nix b/nixos/tests/navidrome.nix index e57cec0d2830..1ae2ac19af13 100644 --- a/nixos/tests/navidrome.nix +++ b/nixos/tests/navidrome.nix @@ -5,7 +5,9 @@ nodes.machine = { ... }: { - services.navidrome.enable = true; + services.navidrome = { + enable = true; + }; }; testScript = '' diff --git a/pkgs/by-name/na/navidrome/package.nix b/pkgs/by-name/na/navidrome/package.nix index 242bd807785c..034239e25f58 100644 --- a/pkgs/by-name/na/navidrome/package.nix +++ b/pkgs/by-name/na/navidrome/package.nix @@ -16,6 +16,7 @@ nix-update-script, ffmpegSupport ? true, versionCheckHook, + plugins ? [ ], }: buildGoModule (finalAttrs: { @@ -46,6 +47,8 @@ buildGoModule (finalAttrs: { pkg-config ]; + runtimeInputs = plugins; + overrideModAttrs = oldAttrs: { nativeBuildInputs = lib.filter (drv: drv != npmHooks.npmConfigHook) oldAttrs.nativeBuildInputs; preBuild = null; @@ -77,6 +80,13 @@ buildGoModule (finalAttrs: { make buildjs ''; + postInstall = '' + mkdir -p $out/share/plugins/ + ${lib.concatMapStringsSep "\n" (plugin: '' + ln -s ${plugin}/share/${plugin.pname}.ndp $out/share/plugins/ + '') plugins} + ''; + tags = [ "netgo" "sqlite_fts5" @@ -91,6 +101,7 @@ buildGoModule (finalAttrs: { ''; passthru = { + inherit plugins; tests.navidrome = nixosTests.navidrome; updateScript = nix-update-script { }; }; diff --git a/pkgs/by-name/na/navidrome/plugins/README.md b/pkgs/by-name/na/navidrome/plugins/README.md new file mode 100644 index 000000000000..a59ba3caa74a --- /dev/null +++ b/pkgs/by-name/na/navidrome/plugins/README.md @@ -0,0 +1,8 @@ +# Packaging guidelines + +## Basics + +Each navidrome plugin is a `.zip` file named `${pname}.ndp`. Inside this zip is +two files named exactly: +- `manifest.json` which defines permissions and configuration +- `plugin.wasm` which is the output of the plugin build diff --git a/pkgs/by-name/na/navidrome/plugins/build-plugin.nix b/pkgs/by-name/na/navidrome/plugins/build-plugin.nix new file mode 100644 index 000000000000..ae91384401c5 --- /dev/null +++ b/pkgs/by-name/na/navidrome/plugins/build-plugin.nix @@ -0,0 +1,67 @@ +{ + buildGoModule, + lib, + navidrome, + zip, +}: + +{ + pname, + src, + version, + vendorHash, + meta, + ... +}@args: + +buildGoModule ( + finalAttrs: + { + inherit + pname + version + src + vendorHash + ; + + env = { + CGO_ENABLED = "0"; + } + // (args.env or { }); + + postBuild = '' + GOOS=wasip1 \ + GOARCH=wasm \ + go build \ + -buildmode=c-shared \ + -o $GOPATH/bin/plugin.wasm . + ''; + + postInstall = '' + mkdir $out/share + pushd $(mktemp -d) + cp $GOPATH/bin/plugin.wasm . + cp ${finalAttrs.src}/manifest.json . + ${lib.getExe zip} \ + $out/share/${finalAttrs.pname}.ndp \ + plugin.wasm \ + manifest.json + popd + rm -r $out/bin + ''; + + passthru = { + isNavidromePlugin = true; + } + // args.passthru or { }; + + meta = { + inherit (navidrome.meta) platforms maintainers; + } + // args.meta or { }; + } + // removeAttrs args [ + "meta" + "passthru" + ] +) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index e2857a88f812..1013e783e5db 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -2686,6 +2686,17 @@ with pkgs; nanoemoji = with python3Packages; toPythonApplication nanoemoji; + buildNavidromePlugin = callPackage ../by-name/na/navidrome/plugins/build-plugin.nix { }; + navidromePlugins = recurseIntoAttrs ( + lib.makeExtensible ( + self: + lib.packagesFromDirectoryRecursive { + inherit callPackage; + directory = ../by-name/na/navidrome/plugins; + } + ) + ); + netdata = callPackage ../tools/system/netdata { protobuf = protobuf_21; }; From 57a56c30e09e2daa0e92b73de49dffa36422eac1 Mon Sep 17 00:00:00 2001 From: Chris Moultrie <821688+tebriel@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:16:25 -0400 Subject: [PATCH 2/5] navidromePlugins.listenbrainz-daily-playlist: init at 5.0.2 --- nixos/modules/services/audio/navidrome.nix | 1 + nixos/tests/navidrome.nix | 6 +++++ .../listenbrainz-daily-playlist/package.nix | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 pkgs/by-name/na/navidrome/plugins/listenbrainz-daily-playlist/package.nix diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix index 47f7a17162e9..e7d79f838d93 100644 --- a/nixos/modules/services/audio/navidrome.nix +++ b/nixos/modules/services/audio/navidrome.nix @@ -46,6 +46,7 @@ in description = "List of Navidrome plugins"; example = literalExpression '' with pkgs.navidromePlugins; [ + listenbrainz-daily-playlist ]; ''; }; diff --git a/nixos/tests/navidrome.nix b/nixos/tests/navidrome.nix index 1ae2ac19af13..286fec66da2e 100644 --- a/nixos/tests/navidrome.nix +++ b/nixos/tests/navidrome.nix @@ -7,11 +7,17 @@ { services.navidrome = { enable = true; + plugins = with pkgs.navidromePlugins; [ + listenbrainz-daily-playlist + ]; }; }; testScript = '' machine.wait_for_unit("navidrome") + machine.wait_for_console_text("Starting plugin manager") + # Make sure we saw at least one plugin load + machine.wait_for_console_text("plugin=listenbrainz-daily-playlist") machine.wait_for_open_port(4533) ''; } diff --git a/pkgs/by-name/na/navidrome/plugins/listenbrainz-daily-playlist/package.nix b/pkgs/by-name/na/navidrome/plugins/listenbrainz-daily-playlist/package.nix new file mode 100644 index 000000000000..ae2fc72d2e33 --- /dev/null +++ b/pkgs/by-name/na/navidrome/plugins/listenbrainz-daily-playlist/package.nix @@ -0,0 +1,25 @@ +{ + lib, + pkgs, + buildNavidromePlugin, +}: +buildNavidromePlugin rec { + pname = "listenbrainz-daily-playlist"; + version = "5.0.2"; + + src = pkgs.fetchFromGitHub { + owner = "kgarner7"; + repo = "navidrome-listenbrainz-daily-playlist"; + tag = "v${version}"; + hash = "sha256-DsbnTu+Xi9pAG9fKgtlixxrd3od41TTeZ1hdjyEyGnk="; + }; + + vendorHash = "sha256-zCKLwS85+aC4jfRcC2SjKGK/OYjW+izIhKKKLxNroQg="; + + meta = { + description = "fetch daily/weekly playlists from ListenBrainz"; + homepage = "https://github.com/kgarner7/navidrome-listenbrainz-daily-playlist"; + license = lib.licenses.mit; + sourceProvenance = with lib.sourceTypes; [ fromSource ]; + }; +} From 9b42026d7d06affac6fe159f4a48573933d004af Mon Sep 17 00:00:00 2001 From: Chris Moultrie <821688+tebriel@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:21:33 -0400 Subject: [PATCH 3/5] navidromePlugins.discord-rich-presence: init at 1.0.0 --- .../plugins/discord-rich-presence/package.nix | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 pkgs/by-name/na/navidrome/plugins/discord-rich-presence/package.nix diff --git a/pkgs/by-name/na/navidrome/plugins/discord-rich-presence/package.nix b/pkgs/by-name/na/navidrome/plugins/discord-rich-presence/package.nix new file mode 100644 index 000000000000..07ebe9440af5 --- /dev/null +++ b/pkgs/by-name/na/navidrome/plugins/discord-rich-presence/package.nix @@ -0,0 +1,25 @@ +{ + lib, + pkgs, + buildNavidromePlugin, +}: +buildNavidromePlugin rec { + pname = "discord-rich-presence-plugin"; + version = "1.0.0"; + + src = pkgs.fetchFromGitHub { + owner = "navidrome"; + repo = "discord-rich-presence-plugin"; + tag = "v${version}"; + hash = "sha256-YH1K6uagIloQQ4gdezKMAfx9KbGL9chiTx/i8CiH4io="; + }; + + vendorHash = "sha256-M5dI0gNfy2x9IVN1284pdvUaCui0sgxFCC+9weq2ipM="; + + meta = { + description = "Displays your currently playing track in your Discord status"; + homepage = "https://github.com/navidrome/discord-rich-presence-plugin"; + license = lib.licenses.gpl3Only; + sourceProvenance = with lib.sourceTypes; [ fromSource ]; + }; +} From 072d267c95e9ba6d51ff648867c7dcb40ae7f9a8 Mon Sep 17 00:00:00 2001 From: Chris Moultrie <821688+tebriel@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:22:08 -0400 Subject: [PATCH 4/5] navidromePlugins.apple-music: init at 0.2.0 --- .../navidrome/plugins/apple-music/package.nix | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 pkgs/by-name/na/navidrome/plugins/apple-music/package.nix diff --git a/pkgs/by-name/na/navidrome/plugins/apple-music/package.nix b/pkgs/by-name/na/navidrome/plugins/apple-music/package.nix new file mode 100644 index 000000000000..0796270459da --- /dev/null +++ b/pkgs/by-name/na/navidrome/plugins/apple-music/package.nix @@ -0,0 +1,26 @@ +{ + lib, + pkgs, + buildNavidromePlugin, +}: + +buildNavidromePlugin rec { + pname = "apple-music-plugin"; + version = "0.2.0"; + + src = pkgs.fetchFromGitHub { + owner = "navidrome"; + repo = "apple-music-plugin"; + tag = "v${version}"; + hash = "sha256-45nUrIDXCtkh08TeZoc6j2BcGLQnpVJu23L56nIBN1s="; + }; + + vendorHash = "sha256-G1B6W8ZKoLuNwvOt3z5vSKcQmF2574j41A0lC+u39uI="; + + meta = { + description = "Fetches artist metadata from Apple Music using free iTunes/Apple Music endpoints"; + homepage = "https://github.com/navidrome/apple-music-plugin"; + license = lib.licenses.gpl3Only; + sourceProvenance = with lib.sourceTypes; [ fromSource ]; + }; +} From 0541efdcf6cde2010ce6657b4d77e4e5a6b4b0ac Mon Sep 17 00:00:00 2001 From: Chris Moultrie <821688+tebriel@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:58:32 -0400 Subject: [PATCH 5/5] navidrome: add tebriel as maintainer --- nixos/modules/services/audio/navidrome.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix index e7d79f838d93..8920c2d429ff 100644 --- a/nixos/modules/services/audio/navidrome.nix +++ b/nixos/modules/services/audio/navidrome.nix @@ -239,5 +239,8 @@ in networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.Port ]; }; - meta.maintainers = with maintainers; [ fsnkty ]; + meta.maintainers = with maintainers; [ + fsnkty + tebriel + ]; }