diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 1f365b61..9a2ee593 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 07acce2d..a485d6cd 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 74c92a39..e82fa8e5 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 fdeaf941..22eedc5a 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 00000000..32862542
--- /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 d89ebfbd..c420ae68 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 00000000..3654b961
--- /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 00000000..418bfde1
--- /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 00000000..2f2c33d2
--- /dev/null
+++ b/tests/modules/services/mopidy/default.nix
@@ -0,0 +1 @@
+{ mopidy-basic-configuration = ./basic-configuration.nix; }