diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1f365b61e..9a2ee5931 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -346,6 +346,9 @@ /modules/services/mbsync.nix @pjones +/modules/services/mopidy.nix @foo-dogsquared +/tests/modules/services/mopidy @foo-dogsquared + /modules/services/mpdris2.nix @pjones /modules/services/mpd-discord-rpc.nix @Kranzes diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix index 07acce2d5..a485d6cdd 100644 --- a/modules/lib/maintainers.nix +++ b/modules/lib/maintainers.nix @@ -55,6 +55,12 @@ github = "chisui"; githubId = 4526429; }; + foo-dogsquared = { + name = "Gabriel Arazas"; + email = "foo.dogsquared@gmail.com"; + github = "foo-dogsquared"; + githubId = 34962634; + }; olmokramer = { name = "Olmo Kramer"; email = "olmokramer@users.noreply.github.com"; diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 74c92a39b..e82fa8e58 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -2471,6 +2471,14 @@ in A new module is available: 'programs.tealdeer'. ''; } + + { + time = "2022-05-18T22:09:45+00:00"; + condition = hostPlatform.isLinux; + message = '' + A new module is available: 'services.mopidy'. + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index fdeaf9410..22eedc5a9 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -212,6 +212,7 @@ let ./services/lorri.nix ./services/mako.nix ./services/mbsync.nix + ./services/mopidy.nix ./services/mpd.nix ./services/mpdris2.nix ./services/mpd-discord-rpc.nix diff --git a/modules/services/mopidy.nix b/modules/services/mopidy.nix new file mode 100644 index 000000000..32862542b --- /dev/null +++ b/modules/services/mopidy.nix @@ -0,0 +1,136 @@ +{ config, options, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.mopidy; + + # The configuration format of Mopidy. It seems to use configparser with + # some quirky handling of its types. You can see how they're handled in + # `mopidy/config/types.py` from the source code. + toMopidyConf = generators.toINI { + mkKeyValue = generators.mkKeyValueDefault { + mkValueString = v: + if isList v then + "\n " + concatStringsSep "\n " v + else + generators.mkValueStringDefault { } v; + } " = "; + }; + + mopidyEnv = pkgs.buildEnv { + name = "mopidy-with-extensions-${pkgs.mopidy.version}"; + paths = closePropagation cfg.extensionPackages; + pathsToLink = [ "/${pkgs.mopidyPackages.python.sitePackages}" ]; + buildInputs = [ pkgs.makeWrapper ]; + postBuild = '' + makeWrapper ${pkgs.mopidy}/bin/mopidy $out/bin/mopidy \ + --prefix PYTHONPATH : $out/${pkgs.mopidyPackages.python.sitePackages} + ''; + }; + + # Nix-representable format for Mopidy config. + mopidyConfFormat = { }: { + type = with types; + let + valueType = nullOr (oneOf [ bool float int str (listOf valueType) ]) + // { + description = "Mopidy config value"; + }; + in attrsOf (attrsOf valueType); + + generate = name: value: pkgs.writeText name (toMopidyConf value); + }; + + settingsFormat = mopidyConfFormat { }; + +in { + meta.maintainers = [ hm.maintainers.foo-dogsquared ]; + + options.services.mopidy = { + enable = mkEnableOption "Mopidy music player daemon"; + + extensionPackages = mkOption { + type = with types; listOf package; + default = [ ]; + example = literalExpression + "with pkgs; [ mopidy-spotify mopidy-mpd mopidy-mpris ]"; + description = '' + Mopidy extensions that should be loaded by the service. + ''; + }; + + settings = mkOption { + type = settingsFormat.type; + default = { }; + example = literalExpression '' + { + file = { + media_dirs = [ + "$XDG_MUSIC_DIR|Music" + "~/library|Library" + ]; + follow_symlinks = true; + excluded_file_extensions = [ + ".html" + ".zip" + ".jpg" + ".jpeg" + ".png" + ]; + }; + + # Please don't put your mopidy-spotify configuration in the public. :) + # Think of your Spotify Premium subscription! + spotify = { + client_id = "CLIENT_ID"; + client_secret = "CLIENT_SECRET"; + }; + } + ''; + description = '' + Configuration written to + $XDG_CONFIG_HOME/mopidy/mopidy.conf. + + See for + more details. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = + [ (hm.assertions.assertPlatform "services.mopidy" pkgs platforms.linux) ]; + + xdg.configFile."mopidy/mopidy.conf".source = + settingsFormat.generate "mopidy-${config.home.username}" cfg.settings; + + systemd.user.services.mopidy = { + Unit = { + Description = "mopidy music player daemon"; + Documentation = [ "https://mopidy.com/" ]; + After = [ "network.target" "sound.target" ]; + }; + + Service = { ExecStart = "${mopidyEnv}/bin/mopidy"; }; + + Install.WantedBy = [ "default.target" ]; + }; + + systemd.user.services.mopidy-scan = { + Unit = { + Description = "mopidy local files scanner"; + Documentation = [ "https://mopidy.com/" ]; + After = [ "network.target" "sound.target" ]; + }; + + Service = { + ExecStart = "${mopidyEnv}/bin/mopidy local scan"; + Type = "oneshot"; + }; + + Install.WantedBy = [ "default.target" ]; + }; + }; +} diff --git a/tests/default.nix b/tests/default.nix index d89ebfbdb..c420ae68c 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -153,6 +153,7 @@ import nmt { ./modules/services/home-manager-auto-upgrade ./modules/services/kanshi ./modules/services/lieer + ./modules/services/mopidy ./modules/services/mpd ./modules/services/pantalaimon ./modules/services/pbgopy diff --git a/tests/modules/services/mopidy/basic-configuration.conf b/tests/modules/services/mopidy/basic-configuration.conf new file mode 100644 index 000000000..3654b9610 --- /dev/null +++ b/tests/modules/services/mopidy/basic-configuration.conf @@ -0,0 +1,10 @@ +[file] +enabled = true +media_dirs = + $XDG_MUSIC_DIR|Music + ~/Downloads|Downloads + +[spotify] +client_id = TOTALLY_NOT_A_FAKE_CLIENT_ID +client_secret = YOU_CAN_USE_ME_FOR_YOUR_SPOTIFY_PREMIUM_SUBSCRIPTION +enabled = true diff --git a/tests/modules/services/mopidy/basic-configuration.nix b/tests/modules/services/mopidy/basic-configuration.nix new file mode 100644 index 000000000..418bfde1f --- /dev/null +++ b/tests/modules/services/mopidy/basic-configuration.nix @@ -0,0 +1,38 @@ +{ config, pkgs, ... }: + +{ + services.mopidy = { + enable = true; + settings = { + file = { + enabled = true; + media_dirs = [ "$XDG_MUSIC_DIR|Music" "~/Downloads|Downloads" ]; + }; + + spotify = { + enabled = true; + client_id = "TOTALLY_NOT_A_FAKE_CLIENT_ID"; + client_secret = "YOU_CAN_USE_ME_FOR_YOUR_SPOTIFY_PREMIUM_SUBSCRIPTION"; + }; + }; + }; + + test.stubs.mopidy = { + version = "0"; + outPath = null; + buildScript = '' + mkdir -p $out/bin + touch $out/bin/mopidy + chmod +x $out/bin/mopidy + ''; + }; + + nmt.script = '' + assertFileExists home-files/.config/systemd/user/mopidy.service + assertFileExists home-files/.config/systemd/user/mopidy-scan.service + + assertFileExists home-files/.config/mopidy/mopidy.conf + assertFileContent home-files/.config/mopidy/mopidy.conf \ + ${./basic-configuration.conf} + ''; +} diff --git a/tests/modules/services/mopidy/default.nix b/tests/modules/services/mopidy/default.nix new file mode 100644 index 000000000..2f2c33d23 --- /dev/null +++ b/tests/modules/services/mopidy/default.nix @@ -0,0 +1 @@ +{ mopidy-basic-configuration = ./basic-configuration.nix; }