at 25.11-pre 9.2 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 9 name = "mpd"; 10 11 uid = config.ids.uids.mpd; 12 gid = config.ids.gids.mpd; 13 cfg = config.services.mpd; 14 15 credentialsPlaceholder = ( 16 creds: 17 let 18 placeholders = ( 19 lib.imap0 ( 20 i: c: ''password "{{password-${toString i}}}@${lib.concatStringsSep "," c.permissions}"'' 21 ) creds 22 ); 23 in 24 lib.concatStringsSep "\n" placeholders 25 ); 26 27 mpdConf = pkgs.writeText "mpd.conf" '' 28 # This file was automatically generated by NixOS. Edit mpd's configuration 29 # via NixOS' configuration.nix, as this file will be rewritten upon mpd's 30 # restart. 31 32 music_directory "${cfg.musicDirectory}" 33 playlist_directory "${cfg.playlistDirectory}" 34 ${lib.optionalString (cfg.dbFile != null) '' 35 db_file "${cfg.dbFile}" 36 ''} 37 state_file "${cfg.dataDir}/state" 38 sticker_file "${cfg.dataDir}/sticker.sql" 39 40 ${lib.optionalString ( 41 cfg.network.listenAddress != "any" 42 ) ''bind_to_address "${cfg.network.listenAddress}"''} 43 ${lib.optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''} 44 ${lib.optionalString (cfg.fluidsynth) '' 45 decoder { 46 plugin "fluidsynth" 47 soundfont "${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2" 48 } 49 ''} 50 51 ${lib.optionalString (cfg.credentials != [ ]) (credentialsPlaceholder cfg.credentials)} 52 53 ${cfg.extraConfig} 54 ''; 55 56in 57{ 58 59 ###### interface 60 61 options = { 62 63 services.mpd = { 64 65 enable = lib.mkOption { 66 type = lib.types.bool; 67 default = false; 68 description = '' 69 Whether to enable MPD, the music player daemon. 70 ''; 71 }; 72 73 startWhenNeeded = lib.mkOption { 74 type = lib.types.bool; 75 default = false; 76 description = '' 77 If set, {command}`mpd` is socket-activated; that 78 is, instead of having it permanently running as a daemon, 79 systemd will start it on the first incoming connection. 80 ''; 81 }; 82 83 musicDirectory = lib.mkOption { 84 type = with lib.types; either path (strMatching "(http|https|nfs|smb)://.+"); 85 default = "${cfg.dataDir}/music"; 86 defaultText = lib.literalExpression ''"''${dataDir}/music"''; 87 description = '' 88 The directory or NFS/SMB network share where MPD reads music from. If left 89 as the default value this directory will automatically be created before 90 the MPD server starts, otherwise the sysadmin is responsible for ensuring 91 the directory exists with appropriate ownership and permissions. 92 ''; 93 }; 94 95 playlistDirectory = lib.mkOption { 96 type = lib.types.path; 97 default = "${cfg.dataDir}/playlists"; 98 defaultText = lib.literalExpression ''"''${dataDir}/playlists"''; 99 description = '' 100 The directory where MPD stores playlists. If left as the default value 101 this directory will automatically be created before the MPD server starts, 102 otherwise the sysadmin is responsible for ensuring the directory exists 103 with appropriate ownership and permissions. 104 ''; 105 }; 106 107 extraConfig = lib.mkOption { 108 type = lib.types.lines; 109 default = ""; 110 description = '' 111 Extra directives added to to the end of MPD's configuration file, 112 mpd.conf. Basic configuration like file location and uid/gid 113 is added automatically to the beginning of the file. For available 114 options see {manpage}`mpd.conf(5)`. 115 ''; 116 }; 117 118 dataDir = lib.mkOption { 119 type = lib.types.path; 120 default = "/var/lib/${name}"; 121 description = '' 122 The directory where MPD stores its state, tag cache, playlists etc. If 123 left as the default value this directory will automatically be created 124 before the MPD server starts, otherwise the sysadmin is responsible for 125 ensuring the directory exists with appropriate ownership and permissions. 126 ''; 127 }; 128 129 user = lib.mkOption { 130 type = lib.types.str; 131 default = name; 132 description = "User account under which MPD runs."; 133 }; 134 135 group = lib.mkOption { 136 type = lib.types.str; 137 default = name; 138 description = "Group account under which MPD runs."; 139 }; 140 141 network = { 142 143 listenAddress = lib.mkOption { 144 type = lib.types.str; 145 default = "127.0.0.1"; 146 example = "any"; 147 description = '' 148 The address for the daemon to listen on. 149 Use `any` to listen on all addresses. 150 ''; 151 }; 152 153 port = lib.mkOption { 154 type = lib.types.port; 155 default = 6600; 156 description = '' 157 This setting is the TCP port that is desired for the daemon to get assigned 158 to. 159 ''; 160 }; 161 162 }; 163 164 dbFile = lib.mkOption { 165 type = lib.types.nullOr lib.types.str; 166 default = "${cfg.dataDir}/tag_cache"; 167 defaultText = lib.literalExpression ''"''${dataDir}/tag_cache"''; 168 description = '' 169 The path to MPD's database. If set to `null` the 170 parameter is omitted from the configuration. 171 ''; 172 }; 173 174 credentials = lib.mkOption { 175 type = lib.types.listOf ( 176 lib.types.submodule { 177 options = { 178 passwordFile = lib.mkOption { 179 type = lib.types.path; 180 description = '' 181 Path to file containing the password. 182 ''; 183 }; 184 permissions = 185 let 186 perms = [ 187 "read" 188 "add" 189 "control" 190 "admin" 191 ]; 192 in 193 lib.mkOption { 194 type = lib.types.listOf (lib.types.enum perms); 195 default = [ "read" ]; 196 description = '' 197 List of permissions that are granted with this password. 198 Permissions can be "${lib.concatStringsSep "\", \"" perms}". 199 ''; 200 }; 201 }; 202 } 203 ); 204 description = '' 205 Credentials and permissions for accessing the mpd server. 206 ''; 207 default = [ ]; 208 example = [ 209 { 210 passwordFile = "/var/lib/secrets/mpd_readonly_password"; 211 permissions = [ "read" ]; 212 } 213 { 214 passwordFile = "/var/lib/secrets/mpd_admin_password"; 215 permissions = [ 216 "read" 217 "add" 218 "control" 219 "admin" 220 ]; 221 } 222 ]; 223 }; 224 225 fluidsynth = lib.mkOption { 226 type = lib.types.bool; 227 default = false; 228 description = '' 229 If set, add fluidsynth soundfont and configure the plugin. 230 ''; 231 }; 232 }; 233 234 }; 235 236 ###### implementation 237 238 config = lib.mkIf cfg.enable { 239 240 # install mpd units 241 systemd.packages = [ pkgs.mpd ]; 242 243 systemd.sockets.mpd = lib.mkIf cfg.startWhenNeeded { 244 wantedBy = [ "sockets.target" ]; 245 listenStreams = [ 246 "" # Note: this is needed to override the upstream unit 247 ( 248 if pkgs.lib.hasPrefix "/" cfg.network.listenAddress then 249 cfg.network.listenAddress 250 else 251 "${ 252 lib.optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:" 253 }${toString cfg.network.port}" 254 ) 255 ]; 256 }; 257 258 systemd.services.mpd = { 259 wantedBy = lib.optional (!cfg.startWhenNeeded) "multi-user.target"; 260 261 preStart = 262 '' 263 set -euo pipefail 264 install -m 600 ${mpdConf} /run/mpd/mpd.conf 265 '' 266 + lib.optionalString (cfg.credentials != [ ]) ( 267 lib.concatStringsSep "\n" ( 268 lib.imap0 ( 269 i: c: 270 ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'' 271 ) cfg.credentials 272 ) 273 ); 274 275 serviceConfig = { 276 User = "${cfg.user}"; 277 # Note: the first "" overrides the ExecStart from the upstream unit 278 ExecStart = [ 279 "" 280 "${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf" 281 ]; 282 RuntimeDirectory = "mpd"; 283 StateDirectory = 284 [ ] 285 ++ lib.optionals (cfg.dataDir == "/var/lib/${name}") [ name ] 286 ++ lib.optionals (cfg.playlistDirectory == "/var/lib/${name}/playlists") [ 287 name 288 "${name}/playlists" 289 ] 290 ++ lib.optionals (cfg.musicDirectory == "/var/lib/${name}/music") [ 291 name 292 "${name}/music" 293 ]; 294 }; 295 }; 296 297 users.users = lib.optionalAttrs (cfg.user == name) { 298 ${name} = { 299 inherit uid; 300 group = cfg.group; 301 extraGroups = [ "audio" ]; 302 description = "Music Player Daemon user"; 303 home = "${cfg.dataDir}"; 304 }; 305 }; 306 307 users.groups = lib.optionalAttrs (cfg.group == name) { 308 ${name}.gid = gid; 309 }; 310 }; 311 312}