at 23.11-pre 8.6 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 = lib.mdDoc '' 60 Whether to enable MPD, the music player daemon. 61 ''; 62 }; 63 64 startWhenNeeded = mkOption { 65 type = types.bool; 66 default = false; 67 description = lib.mdDoc '' 68 If set, {command}`mpd` 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 {manpage}`mpd.conf(5)`. 106 ''; 107 }; 108 109 dataDir = mkOption { 110 type = types.path; 111 default = "/var/lib/${name}"; 112 description = lib.mdDoc '' 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 = lib.mdDoc "User account under which MPD runs."; 124 }; 125 126 group = mkOption { 127 type = types.str; 128 default = name; 129 description = lib.mdDoc "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 = lib.mdDoc '' 139 The address for the daemon to listen on. 140 Use `any` to listen on all addresses. 141 ''; 142 }; 143 144 port = mkOption { 145 type = types.port; 146 default = 6600; 147 description = lib.mdDoc '' 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 = lib.mdDoc '' 160 The path to MPD's database. If set to `null` 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 = lib.mdDoc '' 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 = lib.mdDoc '' 180 List of permissions that are granted with this password. 181 Permissions can be "${concatStringsSep "\", \"" perms}". 182 ''; 183 }; 184 }; 185 }); 186 description = lib.mdDoc '' 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 = lib.mdDoc '' 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 # install mpd units 213 systemd.packages = [ pkgs.mpd ]; 214 215 systemd.sockets.mpd = mkIf cfg.startWhenNeeded { 216 wantedBy = [ "sockets.target" ]; 217 listenStreams = [ 218 "" # Note: this is needed to override the upstream unit 219 (if pkgs.lib.hasPrefix "/" cfg.network.listenAddress 220 then cfg.network.listenAddress 221 else "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}") 222 ]; 223 }; 224 225 systemd.services.mpd = { 226 wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; 227 228 preStart = 229 '' 230 set -euo pipefail 231 install -m 600 ${mpdConf} /run/mpd/mpd.conf 232 '' + optionalString (cfg.credentials != []) 233 (concatStringsSep "\n" 234 (imap0 235 (i: c: ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'') 236 cfg.credentials)); 237 238 serviceConfig = 239 { 240 User = "${cfg.user}"; 241 # Note: the first "" overrides the ExecStart from the upstream unit 242 ExecStart = [ "" "${pkgs.mpd}/bin/mpd --systemd /run/mpd/mpd.conf" ]; 243 RuntimeDirectory = "mpd"; 244 StateDirectory = [] 245 ++ optionals (cfg.dataDir == "/var/lib/${name}") [ name ] 246 ++ optionals (cfg.playlistDirectory == "/var/lib/${name}/playlists") [ name "${name}/playlists" ] 247 ++ optionals (cfg.musicDirectory == "/var/lib/${name}/music") [ name "${name}/music" ]; 248 }; 249 }; 250 251 users.users = optionalAttrs (cfg.user == name) { 252 ${name} = { 253 inherit uid; 254 group = cfg.group; 255 extraGroups = [ "audio" ]; 256 description = "Music Player Daemon user"; 257 home = "${cfg.dataDir}"; 258 }; 259 }; 260 261 users.groups = optionalAttrs (cfg.group == name) { 262 ${name}.gid = gid; 263 }; 264 }; 265 266}