navidrome: introduce plugin infrastructure (#424464)

This commit is contained in:
Martin Weinelt
2026-05-07 15:24:06 +00:00
committed by GitHub
9 changed files with 247 additions and 4 deletions
+65 -3
View File
@@ -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,37 @@ 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; [
listenbrainz-daily-playlist
];
'';
};
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 +87,29 @@ in
description = "Enable anonymous usage data collection, see <https://www.navidrome.org/docs/getting-started/insights/> 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 +137,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 +173,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;
@@ -180,5 +239,8 @@ in
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.Port ];
};
meta.maintainers = with maintainers; [ fsnkty ];
meta.maintainers = with maintainers; [
fsnkty
tebriel
];
}
+9 -1
View File
@@ -5,11 +5,19 @@
nodes.machine =
{ ... }:
{
services.navidrome.enable = true;
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)
'';
}
+11
View File
@@ -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 { };
};
@@ -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
@@ -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 ];
};
}
@@ -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"
]
)
@@ -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 ];
};
}
@@ -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 ];
};
}
+11
View File
@@ -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;
};