1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.mpdscribble;
7 mpdCfg = config.services.mpd;
8
9 endpointUrls = {
10 "last.fm" = "http://post.audioscrobbler.com";
11 "libre.fm" = "http://turtle.libre.fm";
12 "jamendo" = "http://postaudioscrobbler.jamendo.com";
13 "listenbrainz" = "http://proxy.listenbrainz.org";
14 };
15
16 mkSection = secname: secCfg: ''
17 [${secname}]
18 url = ${secCfg.url}
19 username = ${secCfg.username}
20 password = {{${secname}_PASSWORD}}
21 journal = /var/lib/mpdscribble/${secname}.journal
22 '';
23
24 endpoints = concatStringsSep "\n" (mapAttrsToList mkSection cfg.endpoints);
25 cfgTemplate = pkgs.writeText "mpdscribble.conf" ''
26 ## This file was automatically genenrated by NixOS and will be overwritten.
27 ## Do not edit. Edit your NixOS configuration instead.
28
29 ## mpdscribble - an audioscrobbler for the Music Player Daemon.
30 ## http://mpd.wikia.com/wiki/Client:mpdscribble
31
32 # HTTP proxy URL.
33 ${optionalString (cfg.proxy != null) "proxy = ${cfg.proxy}"}
34
35 # The location of the mpdscribble log file. The special value
36 # "syslog" makes mpdscribble use the local syslog daemon. On most
37 # systems, log messages will appear in /var/log/daemon.log then.
38 # "-" means log to stderr (the current terminal).
39 log = -
40
41 # How verbose mpdscribble's logging should be. Default is 1.
42 verbose = ${toString cfg.verbose}
43
44 # How often should mpdscribble save the journal file? [seconds]
45 journal_interval = ${toString cfg.journalInterval}
46
47 # The host running MPD, possibly protected by a password
48 # ([PASSWORD@]HOSTNAME).
49 host = ${(optionalString (cfg.passwordFile != null) "{{MPD_PASSWORD}}@") + cfg.host}
50
51 # The port that the MPD listens on and mpdscribble should try to
52 # connect to.
53 port = ${toString cfg.port}
54
55 ${endpoints}
56 '';
57
58 cfgFile = "/run/mpdscribble/mpdscribble.conf";
59
60 replaceSecret = secretFile: placeholder: targetFile:
61 optionalString (secretFile != null) ''
62 ${pkgs.replace-secret}/bin/replace-secret '${placeholder}' '${secretFile}' '${targetFile}' '';
63
64 preStart = pkgs.writeShellScript "mpdscribble-pre-start" ''
65 cp -f "${cfgTemplate}" "${cfgFile}"
66 ${replaceSecret cfg.passwordFile "{{MPD_PASSWORD}}" cfgFile}
67 ${concatStringsSep "\n" (mapAttrsToList (secname: cfg:
68 replaceSecret cfg.passwordFile "{{${secname}_PASSWORD}}" cfgFile)
69 cfg.endpoints)}
70 '';
71
72 localMpd = (cfg.host == "localhost" || cfg.host == "127.0.0.1");
73
74in {
75 ###### interface
76
77 options.services.mpdscribble = {
78
79 enable = mkEnableOption "mpdscribble";
80
81 proxy = mkOption {
82 default = null;
83 type = types.nullOr types.str;
84 description = ''
85 HTTP proxy URL.
86 '';
87 };
88
89 verbose = mkOption {
90 default = 1;
91 type = types.int;
92 description = ''
93 Log level for the mpdscribble daemon.
94 '';
95 };
96
97 journalInterval = mkOption {
98 default = 600;
99 example = 60;
100 type = types.int;
101 description = ''
102 How often should mpdscribble save the journal file? [seconds]
103 '';
104 };
105
106 host = mkOption {
107 default = (if mpdCfg.network.listenAddress != "any" then
108 mpdCfg.network.listenAddress
109 else
110 "localhost");
111 type = types.str;
112 description = ''
113 Host for the mpdscribble daemon to search for a mpd daemon on.
114 '';
115 };
116
117 passwordFile = mkOption {
118 default = if localMpd then
119 (findFirst
120 (c: any (x: x == "read") c.permissions)
121 { passwordFile = null; }
122 mpdCfg.credentials).passwordFile
123 else
124 null;
125 type = types.nullOr types.str;
126 description = ''
127 File containing the password for the mpd daemon.
128 If there is a local mpd configured using <option>services.mpd.credentials</option>
129 the default is automatically set to a matching passwordFile of the local mpd.
130 '';
131 };
132
133 port = mkOption {
134 default = mpdCfg.network.port;
135 type = types.port;
136 description = ''
137 Port for the mpdscribble daemon to search for a mpd daemon on.
138 '';
139 };
140
141 endpoints = mkOption {
142 type = (let
143 endpoint = { name, ... }: {
144 options = {
145 url = mkOption {
146 type = types.str;
147 default = endpointUrls.${name} or "";
148 description =
149 "The url endpoint where the scrobble API is listening.";
150 };
151 username = mkOption {
152 type = types.str;
153 description = ''
154 Username for the scrobble service.
155 '';
156 };
157 passwordFile = mkOption {
158 type = types.nullOr types.str;
159 description =
160 "File containing the password, either as MD5SUM or cleartext.";
161 };
162 };
163 };
164 in types.attrsOf (types.submodule endpoint));
165 default = { };
166 example = {
167 "last.fm" = {
168 username = "foo";
169 passwordFile = "/run/secrets/lastfm_password";
170 };
171 };
172 description = ''
173 Endpoints to scrobble to.
174 If the endpoint is one of "${
175 concatStringsSep "\", \"" (attrNames endpointUrls)
176 }" the url is set automatically.
177 '';
178 };
179
180 };
181
182 ###### implementation
183
184 config = mkIf cfg.enable {
185 systemd.services.mpdscribble = {
186 after = [ "network.target" ] ++ (optional localMpd "mpd.service");
187 description = "mpdscribble mpd scrobble client";
188 wantedBy = [ "multi-user.target" ];
189 serviceConfig = {
190 DynamicUser = true;
191 StateDirectory = "mpdscribble";
192 RuntimeDirectory = "mpdscribble";
193 RuntimeDirectoryMode = "700";
194 # TODO use LoadCredential= instead of running preStart with full privileges?
195 ExecStartPre = "+${preStart}";
196 ExecStart =
197 "${pkgs.mpdscribble}/bin/mpdscribble --no-daemon --conf ${cfgFile}";
198 };
199 };
200 };
201
202}