summary refs log tree commit diff
path: root/nixos/modules/services/audio/mpdscribble.nix
blob: 333ffb709410ac2a891df5a7beb807e40e2ebe47 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
{ config, lib, options, pkgs, ... }:

with lib;

let
  cfg = config.services.mpdscribble;
  mpdCfg = config.services.mpd;
  mpdOpt = options.services.mpd;

  endpointUrls = {
    "last.fm" = "http://post.audioscrobbler.com";
    "libre.fm" = "http://turtle.libre.fm";
    "jamendo" = "http://postaudioscrobbler.jamendo.com";
    "listenbrainz" = "http://proxy.listenbrainz.org";
  };

  mkSection = secname: secCfg: ''
    [${secname}]
    url      = ${secCfg.url}
    username = ${secCfg.username}
    password = {{${secname}_PASSWORD}}
    journal  = /var/lib/mpdscribble/${secname}.journal
  '';

  endpoints = concatStringsSep "\n" (mapAttrsToList mkSection cfg.endpoints);
  cfgTemplate = pkgs.writeText "mpdscribble.conf" ''
    ## This file was automatically genenrated by NixOS and will be overwritten.
    ## Do not edit. Edit your NixOS configuration instead.

    ## mpdscribble - an audioscrobbler for the Music Player Daemon.
    ## http://mpd.wikia.com/wiki/Client:mpdscribble

    # HTTP proxy URL.
    ${optionalString (cfg.proxy != null) "proxy = ${cfg.proxy}"}

    # The location of the mpdscribble log file.  The special value
    # "syslog" makes mpdscribble use the local syslog daemon.  On most
    # systems, log messages will appear in /var/log/daemon.log then.
    # "-" means log to stderr (the current terminal).
    log = -

    # How verbose mpdscribble's logging should be.  Default is 1.
    verbose = ${toString cfg.verbose}

    # How often should mpdscribble save the journal file? [seconds]
    journal_interval = ${toString cfg.journalInterval}

    # The host running MPD, possibly protected by a password
    # ([PASSWORD@]HOSTNAME).
    host = ${(optionalString (cfg.passwordFile != null) "{{MPD_PASSWORD}}@") + cfg.host}

    # The port that the MPD listens on and mpdscribble should try to
    # connect to.
    port = ${toString cfg.port}

    ${endpoints}
  '';

  cfgFile = "/run/mpdscribble/mpdscribble.conf";

  replaceSecret = secretFile: placeholder: targetFile:
    optionalString (secretFile != null) ''
      ${pkgs.replace-secret}/bin/replace-secret '${placeholder}' '${secretFile}' '${targetFile}' '';

  preStart = pkgs.writeShellScript "mpdscribble-pre-start" ''
    cp -f "${cfgTemplate}" "${cfgFile}"
    ${replaceSecret cfg.passwordFile "{{MPD_PASSWORD}}" cfgFile}
    ${concatStringsSep "\n" (mapAttrsToList (secname: cfg:
      replaceSecret cfg.passwordFile "{{${secname}_PASSWORD}}" cfgFile)
      cfg.endpoints)}
  '';

  localMpd = (cfg.host == "localhost" || cfg.host == "127.0.0.1");

in {
  ###### interface

  options.services.mpdscribble = {

    enable = mkEnableOption "mpdscribble";

    proxy = mkOption {
      default = null;
      type = types.nullOr types.str;
      description = ''
        HTTP proxy URL.
      '';
    };

    verbose = mkOption {
      default = 1;
      type = types.int;
      description = ''
        Log level for the mpdscribble daemon.
      '';
    };

    journalInterval = mkOption {
      default = 600;
      example = 60;
      type = types.int;
      description = ''
        How often should mpdscribble save the journal file? [seconds]
      '';
    };

    host = mkOption {
      default = (if mpdCfg.network.listenAddress != "any" then
        mpdCfg.network.listenAddress
      else
        "localhost");
      defaultText = literalExpression ''
        if config.${mpdOpt.network.listenAddress} != "any"
        then config.${mpdOpt.network.listenAddress}
        else "localhost"
      '';
      type = types.str;
      description = ''
        Host for the mpdscribble daemon to search for a mpd daemon on.
      '';
    };

    passwordFile = mkOption {
      default = if localMpd then
        (findFirst
          (c: any (x: x == "read") c.permissions)
          { passwordFile = null; }
          mpdCfg.credentials).passwordFile
      else
        null;
      defaultText = literalDocBook ''
        The first password file with read access configured for MPD when using a local instance,
        otherwise <literal>null</literal>.
      '';
      type = types.nullOr types.str;
      description = ''
        File containing the password for the mpd daemon.
        If there is a local mpd configured using <option>services.mpd.credentials</option>
        the default is automatically set to a matching passwordFile of the local mpd.
      '';
    };

    port = mkOption {
      default = mpdCfg.network.port;
      defaultText = literalExpression "config.${mpdOpt.network.port}";
      type = types.port;
      description = ''
        Port for the mpdscribble daemon to search for a mpd daemon on.
      '';
    };

    endpoints = mkOption {
      type = (let
        endpoint = { name, ... }: {
          options = {
            url = mkOption {
              type = types.str;
              default = endpointUrls.${name} or "";
              description =
                "The url endpoint where the scrobble API is listening.";
            };
            username = mkOption {
              type = types.str;
              description = ''
                Username for the scrobble service.
              '';
            };
            passwordFile = mkOption {
              type = types.nullOr types.str;
              description =
                "File containing the password, either as MD5SUM or cleartext.";
            };
          };
        };
      in types.attrsOf (types.submodule endpoint));
      default = { };
      example = {
        "last.fm" = {
          username = "foo";
          passwordFile = "/run/secrets/lastfm_password";
        };
      };
      description = ''
        Endpoints to scrobble to.
        If the endpoint is one of "${
          concatStringsSep "\", \"" (attrNames endpointUrls)
        }" the url is set automatically.
      '';
    };

  };

  ###### implementation

  config = mkIf cfg.enable {
    systemd.services.mpdscribble = {
      after = [ "network.target" ] ++ (optional localMpd "mpd.service");
      description = "mpdscribble mpd scrobble client";
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        DynamicUser = true;
        StateDirectory = "mpdscribble";
        RuntimeDirectory = "mpdscribble";
        RuntimeDirectoryMode = "700";
        # TODO use LoadCredential= instead of running preStart with full privileges?
        ExecStartPre = "+${preStart}";
        ExecStart =
          "${pkgs.mpdscribble}/bin/mpdscribble --no-daemon --conf ${cfgFile}";
      };
    };
  };

}