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