summary refs log tree commit diff
path: root/nixos/modules/services/audio/mpd.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/audio/mpd.nix')
-rw-r--r--nixos/modules/services/audio/mpd.nix127
1 files changed, 99 insertions, 28 deletions
diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix
index 1d2a982ac53..e33e860d883 100644
--- a/nixos/modules/services/audio/mpd.nix
+++ b/nixos/modules/services/audio/mpd.nix
@@ -10,7 +10,19 @@ let
   gid = config.ids.gids.mpd;
   cfg = config.services.mpd;
 
+  credentialsPlaceholder = (creds:
+    let
+      placeholders = (imap0
+        (i: c: ''password "{{password-${toString i}}}@${concatStringsSep "," c.permissions}"'')
+        creds);
+    in
+      concatStringsSep "\n" placeholders);
+
   mpdConf = pkgs.writeText "mpd.conf" ''
+    # This file was automatically generated by NixOS. Edit mpd's configuration
+    # via NixOS' configuration.nix, as this file will be rewritten upon mpd's
+    # restart.
+
     music_directory     "${cfg.musicDirectory}"
     playlist_directory  "${cfg.playlistDirectory}"
     ${lib.optionalString (cfg.dbFile != null) ''
@@ -28,6 +40,8 @@ let
       }
     ''}
 
+    ${optionalString (cfg.credentials != []) (credentialsPlaceholder cfg.credentials)}
+
     ${cfg.extraConfig}
   '';
 
@@ -60,18 +74,24 @@ in {
       musicDirectory = mkOption {
         type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
         default = "${cfg.dataDir}/music";
-        defaultText = ''''${dataDir}/music'';
+        defaultText = "\${dataDir}/music";
         description = ''
-          The directory or NFS/SMB network share where mpd reads music from.
+          The directory or NFS/SMB network share where MPD reads music from. If left
+          as the default value this directory will automatically be created before
+          the MPD server starts, otherwise the sysadmin is responsible for ensuring
+          the directory exists with appropriate ownership and permissions.
         '';
       };
 
       playlistDirectory = mkOption {
         type = types.path;
         default = "${cfg.dataDir}/playlists";
-        defaultText = ''''${dataDir}/playlists'';
+        defaultText = "\${dataDir}/playlists";
         description = ''
-          The directory where mpd stores playlists.
+          The directory where MPD stores playlists. If left as the default value
+          this directory will automatically be created before the MPD server starts,
+          otherwise the sysadmin is responsible for ensuring the directory exists
+          with appropriate ownership and permissions.
         '';
       };
 
@@ -90,8 +110,10 @@ in {
         type = types.path;
         default = "/var/lib/${name}";
         description = ''
-          The directory where MPD stores its state, tag cache,
-          playlists etc.
+          The directory where MPD stores its state, tag cache, playlists etc. If
+          left as the default value this directory will automatically be created
+          before the MPD server starts, otherwise the sysadmin is responsible for
+          ensuring the directory exists with appropriate ownership and permissions.
         '';
       };
 
@@ -133,13 +155,44 @@ in {
       dbFile = mkOption {
         type = types.nullOr types.str;
         default = "${cfg.dataDir}/tag_cache";
-        defaultText = ''''${dataDir}/tag_cache'';
+        defaultText = "\${dataDir}/tag_cache";
         description = ''
           The path to MPD's database. If set to <literal>null</literal> the
           parameter is omitted from the configuration.
         '';
       };
 
+      credentials = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            passwordFile = mkOption {
+              type = types.path;
+              description = ''
+                Path to file containing the password.
+              '';
+            };
+            permissions = let
+              perms = ["read" "add" "control" "admin"];
+            in mkOption {
+              type = types.listOf (types.enum perms);
+              default = [ "read" ];
+              description = ''
+                List of permissions that are granted with this password.
+                Permissions can be "${concatStringsSep "\", \"" perms}".
+              '';
+            };
+          };
+        });
+        description = ''
+          Credentials and permissions for accessing the mpd server.
+        '';
+        default = [];
+        example = [
+          {passwordFile = "/var/lib/secrets/mpd_readonly_password"; permissions = [ "read" ];}
+          {passwordFile = "/var/lib/secrets/mpd_admin_password"; permissions = ["read" "add" "control" "admin"];}
+        ];
+      };
+
       fluidsynth = mkOption {
         type = types.bool;
         default = false;
@@ -160,7 +213,9 @@ in {
       description = "Music Player Daemon Socket";
       wantedBy = [ "sockets.target" ];
       listenStreams = [
-        "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}"
+        (if pkgs.lib.hasPrefix "/" cfg.network.listenAddress
+          then cfg.network.listenAddress
+          else "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}")
       ];
       socketConfig = {
         Backlog = 5;
@@ -169,31 +224,47 @@ in {
       };
     };
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.playlistDirectory}' - ${cfg.user} ${cfg.group} - -"
-    ];
-
     systemd.services.mpd = {
       after = [ "network.target" "sound.target" ];
       description = "Music Player Daemon";
       wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
 
-      serviceConfig = {
-        User = "${cfg.user}";
-        ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}";
-        Type = "notify";
-        LimitRTPRIO = 50;
-        LimitRTTIME = "infinity";
-        ProtectSystem = true;
-        NoNewPrivileges = true;
-        ProtectKernelTunables = true;
-        ProtectControlGroups = true;
-        ProtectKernelModules = true;
-        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
-        RestrictNamespaces = true;
-        Restart = "always";
-      };
+      serviceConfig = mkMerge [
+        {
+          User = "${cfg.user}";
+          ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon /run/mpd/mpd.conf";
+          ExecStartPre = pkgs.writeShellScript "mpd-start-pre" (''
+            set -euo pipefail
+            install -m 600 ${mpdConf} /run/mpd/mpd.conf
+          '' + optionalString (cfg.credentials != [])
+            (concatStringsSep "\n"
+              (imap0
+                (i: c: ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'')
+                cfg.credentials))
+          );
+          RuntimeDirectory = "mpd";
+          Type = "notify";
+          LimitRTPRIO = 50;
+          LimitRTTIME = "infinity";
+          ProtectSystem = true;
+          NoNewPrivileges = true;
+          ProtectKernelTunables = true;
+          ProtectControlGroups = true;
+          ProtectKernelModules = true;
+          RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
+          RestrictNamespaces = true;
+          Restart = "always";
+        }
+        (mkIf (cfg.dataDir == "/var/lib/${name}") {
+          StateDirectory = [ name ];
+        })
+        (mkIf (cfg.playlistDirectory == "/var/lib/${name}/playlists") {
+          StateDirectory = [ name "${name}/playlists" ];
+        })
+        (mkIf (cfg.musicDirectory == "/var/lib/${name}/music") {
+          StateDirectory = [ name "${name}/music" ];
+        })
+      ];
     };
 
     users.users = optionalAttrs (cfg.user == name) {