diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c2a9e0f3201..e05b6dff88d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -226,6 +226,7 @@ ./services/audio/icecast.nix ./services/audio/liquidsoap.nix ./services/audio/mpd.nix + ./services/audio/mpdscribble.nix ./services/audio/mopidy.nix ./services/audio/roon-server.nix ./services/audio/slimserver.nix diff --git a/nixos/modules/services/audio/mpdscribble.nix b/nixos/modules/services/audio/mpdscribble.nix new file mode 100644 index 00000000000..642d8743935 --- /dev/null +++ b/nixos/modules/services/audio/mpdscribble.nix @@ -0,0 +1,202 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.mpdscribble; + mpdCfg = config.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}/bin/replace-literal -ef ${placeholder} "$(cat ${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"); + 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; + type = types.nullOr types.str; + description = '' + File containing the password for the mpd daemon. + If there is a local mpd configured using + the default is automatically set to a matching passwordFile of the local mpd. + ''; + }; + + port = mkOption { + default = mpdCfg.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}"; + }; + }; + }; + +}