1 { config, lib, pkgs, ... }:
6 uid = config.ids.uids.mpd;
7 gid = config.ids.gids.mpd;
8 cfg = config.services.mpd;
10 credentialsPlaceholder = (creds:
12 placeholders = (lib.imap0
13 (i: c: ''password "{{password-${toString i}}}@${lib.concatStringsSep "," c.permissions}"'')
16 lib.concatStringsSep "\n" placeholders);
18 mpdConf = pkgs.writeText "mpd.conf" ''
19 # This file was automatically generated by NixOS. Edit mpd's configuration
20 # via NixOS' configuration.nix, as this file will be rewritten upon mpd's
23 music_directory "${cfg.musicDirectory}"
24 playlist_directory "${cfg.playlistDirectory}"
25 ${lib.optionalString (cfg.dbFile != null) ''
26 db_file "${cfg.dbFile}"
28 state_file "${cfg.dataDir}/state"
29 sticker_file "${cfg.dataDir}/sticker.sql"
31 ${lib.optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''}
32 ${lib.optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''}
33 ${lib.optionalString (cfg.fluidsynth) ''
36 soundfont "${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2"
40 ${lib.optionalString (cfg.credentials != []) (credentialsPlaceholder cfg.credentials)}
53 enable = lib.mkOption {
54 type = lib.types.bool;
57 Whether to enable MPD, the music player daemon.
61 startWhenNeeded = lib.mkOption {
62 type = lib.types.bool;
65 If set, {command}`mpd` is socket-activated; that
66 is, instead of having it permanently running as a daemon,
67 systemd will start it on the first incoming connection.
71 musicDirectory = lib.mkOption {
72 type = with lib.types; either path (strMatching "(http|https|nfs|smb)://.+");
73 default = "${cfg.dataDir}/music";
74 defaultText = lib.literalExpression ''"''${dataDir}/music"'';
76 The directory or NFS/SMB network share where MPD reads music from. If left
77 as the default value this directory will automatically be created before
78 the MPD server starts, otherwise the sysadmin is responsible for ensuring
79 the directory exists with appropriate ownership and permissions.
83 playlistDirectory = lib.mkOption {
84 type = lib.types.path;
85 default = "${cfg.dataDir}/playlists";
86 defaultText = lib.literalExpression ''"''${dataDir}/playlists"'';
88 The directory where MPD stores playlists. If left as the default value
89 this directory will automatically be created before the MPD server starts,
90 otherwise the sysadmin is responsible for ensuring the directory exists
91 with appropriate ownership and permissions.
95 extraConfig = lib.mkOption {
96 type = lib.types.lines;
99 Extra directives added to to the end of MPD's configuration file,
100 mpd.conf. Basic configuration like file location and uid/gid
101 is added automatically to the beginning of the file. For available
102 options see {manpage}`mpd.conf(5)`.
106 dataDir = lib.mkOption {
107 type = lib.types.path;
108 default = "/var/lib/${name}";
110 The directory where MPD stores its state, tag cache, playlists etc. If
111 left as the default value this directory will automatically be created
112 before the MPD server starts, otherwise the sysadmin is responsible for
113 ensuring the directory exists with appropriate ownership and permissions.
117 user = lib.mkOption {
118 type = lib.types.str;
120 description = "User account under which MPD runs.";
123 group = lib.mkOption {
124 type = lib.types.str;
126 description = "Group account under which MPD runs.";
131 listenAddress = lib.mkOption {
132 type = lib.types.str;
133 default = "127.0.0.1";
136 The address for the daemon to listen on.
137 Use `any` to listen on all addresses.
141 port = lib.mkOption {
142 type = lib.types.port;
145 This setting is the TCP port that is desired for the daemon to get assigned
152 dbFile = lib.mkOption {
153 type = lib.types.nullOr lib.types.str;
154 default = "${cfg.dataDir}/tag_cache";
155 defaultText = lib.literalExpression ''"''${dataDir}/tag_cache"'';
157 The path to MPD's database. If set to `null` the
158 parameter is omitted from the configuration.
162 credentials = lib.mkOption {
163 type = lib.types.listOf (lib.types.submodule {
165 passwordFile = lib.mkOption {
166 type = lib.types.path;
168 Path to file containing the password.
172 perms = ["read" "add" "control" "admin"];
174 type = lib.types.listOf (lib.types.enum perms);
175 default = [ "read" ];
177 List of permissions that are granted with this password.
178 Permissions can be "${lib.concatStringsSep "\", \"" perms}".
184 Credentials and permissions for accessing the mpd server.
188 {passwordFile = "/var/lib/secrets/mpd_readonly_password"; permissions = [ "read" ];}
189 {passwordFile = "/var/lib/secrets/mpd_admin_password"; permissions = ["read" "add" "control" "admin"];}
193 fluidsynth = lib.mkOption {
194 type = lib.types.bool;
197 If set, add fluidsynth soundfont and configure the plugin.
205 ###### implementation
207 config = lib.mkIf cfg.enable {
210 systemd.packages = [ pkgs.mpd ];
212 systemd.sockets.mpd = lib.mkIf cfg.startWhenNeeded {
213 wantedBy = [ "sockets.target" ];
215 "" # Note: this is needed to override the upstream unit
216 (if pkgs.lib.hasPrefix "/" cfg.network.listenAddress
217 then cfg.network.listenAddress
218 else "${lib.optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}")
222 systemd.services.mpd = {
223 wantedBy = lib.optional (!cfg.startWhenNeeded) "multi-user.target";
228 install -m 600 ${mpdConf} /run/mpd/mpd.conf
229 '' + lib.optionalString (cfg.credentials != [])
230 (lib.concatStringsSep "\n"
232 (i: c: ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'')
237 User = "${cfg.user}";
238 # Note: the first "" overrides the ExecStart from the upstream unit
239 ExecStart = [ "" "${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf" ];
240 RuntimeDirectory = "mpd";
242 ++ lib.optionals (cfg.dataDir == "/var/lib/${name}") [ name ]
243 ++ lib.optionals (cfg.playlistDirectory == "/var/lib/${name}/playlists") [ name "${name}/playlists" ]
244 ++ lib.optionals (cfg.musicDirectory == "/var/lib/${name}/music") [ name "${name}/music" ];
248 users.users = lib.optionalAttrs (cfg.user == name) {
252 extraGroups = [ "audio" ];
253 description = "Music Player Daemon user";
254 home = "${cfg.dataDir}";
258 users.groups = lib.optionalAttrs (cfg.group == name) {