From ce287a5cd3ef78203bc78021447f937a988d9f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Madeleine=20Sydney=20=C5=9Alaga?= <65362461+msyds@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:44:04 -0600 Subject: [PATCH] mpdscribble: add module (#6259) --- modules/lib/maintainers.nix | 6 + modules/misc/news.nix | 11 + modules/modules.nix | 1 + modules/services/mpdscribble.nix | 222 ++++++++++++++++++ tests/default.nix | 1 + .../mpdscribble/basic-configuration.nix | 28 +++ .../mpdscribble/basic-configuration.service | 14 ++ .../modules/services/mpdscribble/default.nix | 1 + 8 files changed, 284 insertions(+) create mode 100644 modules/services/mpdscribble.nix create mode 100644 tests/modules/services/mpdscribble/basic-configuration.nix create mode 100644 tests/modules/services/mpdscribble/basic-configuration.service create mode 100644 tests/modules/services/mpdscribble/default.nix diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix index e6a43034..9fb20cdd 100644 --- a/modules/lib/maintainers.nix +++ b/modules/lib/maintainers.nix @@ -310,6 +310,12 @@ github = "mifom"; githubId = 23462908; }; + msyds = { + name = "Madeleine Sydney Ĺšlaga"; + email = "65362461+msyds@users.noreply.github.com"; + github = "msyds"; + githubId = 65362461; + }; nikp123 = { name = "nikp123"; email = "nikp123@users.noreply.github.com"; diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 67293088..51c35976 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -2148,6 +2148,7 @@ in { under '$XDG_CONFIG_HOME/easyeffects/{input,output}/'. ''; } + { time = "2025-02-12T15:56:00+00:00"; message = '' @@ -2169,6 +2170,16 @@ in { - programs.zellij.enableZshIntegration ''; } + + { + time = "2025-01-02T11:21:19+00:00"; + message = '' + A new module is available: 'services.mpdscribble'. + + A MPD client which submits information about tracks being played to a + scrobbler (e.g. last.fm) + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index e28d3322..b48ef31a 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -363,6 +363,7 @@ let ./services/mopidy.nix ./services/mpd.nix ./services/mpdris2.nix + ./services/mpdscribble.nix ./services/mpd-discord-rpc.nix ./services/mpd-mpris.nix ./services/mpris-proxy.nix diff --git a/modules/services/mpdscribble.nix b/modules/services/mpdscribble.nix new file mode 100644 index 00000000..d868f24b --- /dev/null +++ b/modules/services/mpdscribble.nix @@ -0,0 +1,222 @@ +{ config, lib, options, pkgs, ... }: + +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"; + }; +in { + options.services.mpdscribble = { + + enable = lib.mkEnableOption '' + mpdscribble, an MPD client which submits info about tracks being played to + Last.fm (formerly AudioScrobbler) + ''; + + proxy = lib.mkOption { + default = null; + type = lib.types.nullOr lib.types.str; + description = '' + HTTP proxy URL. + ''; + }; + + verbose = lib.mkOption { + default = 1; + type = lib.types.int; + description = '' + Log level for the mpdscribble daemon. + ''; + }; + + journalInterval = lib.mkOption { + default = 600; + example = 60; + type = lib.types.int; + description = '' + How often should mpdscribble save the journal file? [seconds] + ''; + }; + + host = lib.mkOption { + default = (if mpdCfg.network.listenAddress != "any" then + mpdCfg.network.listenAddress + else + "localhost"); + defaultText = lib.literalExpression '' + if config.${mpdOpt.network.listenAddress} != "any" + then config.${mpdOpt.network.listenAddress} + else "localhost" + ''; + type = lib.types.str; + description = '' + Host for the mpdscribble daemon to search for a mpd daemon on. + ''; + }; + + package = lib.mkPackageOption pkgs "mpdscribble" { }; + + passwordFile = lib.mkOption { + default = null; + type = lib.types.nullOr lib.types.str; + description = '' + File containing the password for the mpd daemon. + ''; + }; + + port = lib.mkOption { + default = mpdCfg.network.port; + defaultText = lib.literalExpression "config.${mpdOpt.network.port}"; + type = lib.types.port; + description = '' + Port for the mpdscribble daemon to search for a mpd daemon on. + ''; + }; + + endpoints = lib.mkOption { + type = let + endpoint = { name, ... }: { + options = { + url = lib.mkOption { + type = lib.types.str; + default = endpointUrls.${name} or ""; + description = + "The url endpoint where the scrobble API is listening."; + }; + username = lib.mkOption { + type = lib.types.str; + description = '' + Username for the scrobble service. + ''; + }; + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = + "File containing the password, either as MD5SUM or cleartext."; + }; + }; + }; + in lib.types.attrsOf (lib.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 "${ + lib.concatStringsSep ''", "'' (builtins.attrNames endpointUrls) + }" the url is set automatically. + ''; + }; + + }; + + config = lib.mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "services.mpdscribble" pkgs + lib.platforms.linux) + ]; + systemd.user.services.mpdscribble = let + localMpd = (cfg.host == "localhost" || cfg.host == "127.0.0.1"); + + mkSection = secname: secCfg: '' + [${secname}] + url = ${secCfg.url} + username = ${secCfg.username} + password = {{${secname}_PASSWORD}} + journal = /var/lib/mpdscribble/${secname}.journal + ''; + + endpoints = + lib.concatStringsSep "\n" (lib.mapAttrsToList mkSection cfg.endpoints); + cfgTemplate = pkgs.writeText "mpdscribble.conf" '' + ## This file was automatically genenrated by home-manager and will be + ## overwritten. Do not edit. Edit your home-manager configuration instead. + + ## mpdscribble - an audioscrobbler for the Music Player Daemon. + ## http://mpd.wikia.com/wiki/Client:mpdscribble + + # HTTP proxy URL. + ${lib.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 = ${ + (lib.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} + ''; + + configFile = + "\${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/mpdscribble/mpdscribble.conf"; + + replaceSecret = secretFile: placeholder: targetFile: + lib.optionalString (secretFile != null) '' + ${pkgs.replace-secret}/bin/replace-secret '${placeholder}' '${secretFile}' "${targetFile}" + ''; + + preStart = pkgs.writeShellApplication { + name = "mpdscribble-pre-start"; + runtimeInputs = [ pkgs.replace-secret pkgs.coreutils ]; + text = '' + configFile="${configFile}" + mkdir -p "$(dirname "$configFile")" + cp --no-preserve=mode,ownership -f "${cfgTemplate}" "$configFile" + ${replaceSecret cfg.passwordFile "{{MPD_PASSWORD}}" "$configFile"} + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (secname: cfg: + replaceSecret cfg.passwordFile "{{${secname}_PASSWORD}}" + "$configFile") cfg.endpoints)} + ''; + }; + + start = pkgs.writeShellScript "mpdscribble-start" '' + configFile="${configFile}" + exec "${lib.getExe cfg.package}" --no-daemon --conf "$configFile" + ''; + + in { + Unit = { + Description = "mpdscribble mpd scrobble client"; + After = [ "network.target" ] ++ lib.optional localMpd "mpd.service"; + }; + Install.WantedBy = [ "default.target" ]; + Service = { + StateDirectory = "mpdscribble"; + RuntimeDirectory = "mpdscribble"; + RuntimeDirectoryMode = "700"; + # TODO use LoadCredential= instead of running preStart with full privileges? + ExecStartPre = "+${preStart}/bin/mpdscribble-pre-start"; + ExecStart = "${start}"; + }; + }; + }; + + meta.maintainers = [ lib.hm.maintainers.msyds ]; +} diff --git a/tests/default.nix b/tests/default.nix index 233587c4..04d7b1c5 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -525,6 +525,7 @@ in import nmtSrc { ./modules/services/mpd ./modules/services/mpd-mpris ./modules/services/mpdris2 + ./modules/services/mpdscribble ./modules/services/nix-gc ./modules/services/ollama/linux ./modules/services/osmscout-server diff --git a/tests/modules/services/mpdscribble/basic-configuration.nix b/tests/modules/services/mpdscribble/basic-configuration.nix new file mode 100644 index 00000000..18c02585 --- /dev/null +++ b/tests/modules/services/mpdscribble/basic-configuration.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + services.mpdscribble = { + enable = true; + endpoints = { + "libre.fm" = { + username = "musicfan1992"; + passwordFile = toString ./password-file; + }; + "https://music.com/" = { + username = "musicHATER1000"; + passwordFile = toString ./password-file; + }; + }; + }; + + home.stateVersion = "22.11"; + + test.stubs.mpd = { }; + + nmt.script = '' + serviceFile=$(normalizeStorePaths home-files/.config/systemd/user/mpdscribble.service) + assertFileContent "$serviceFile" ${./basic-configuration.service} + ''; +} diff --git a/tests/modules/services/mpdscribble/basic-configuration.service b/tests/modules/services/mpdscribble/basic-configuration.service new file mode 100644 index 00000000..efa7da9a --- /dev/null +++ b/tests/modules/services/mpdscribble/basic-configuration.service @@ -0,0 +1,14 @@ +[Install] +WantedBy=default.target + +[Service] +ExecStart=/nix/store/00000000000000000000000000000000-mpdscribble-start +ExecStartPre=+/nix/store/00000000000000000000000000000000-mpdscribble-pre-start/bin/mpdscribble-pre-start +RuntimeDirectory=mpdscribble +RuntimeDirectoryMode=700 +StateDirectory=mpdscribble + +[Unit] +After=network.target +After=mpd.service +Description=mpdscribble mpd scrobble client diff --git a/tests/modules/services/mpdscribble/default.nix b/tests/modules/services/mpdscribble/default.nix new file mode 100644 index 00000000..b44dae9d --- /dev/null +++ b/tests/modules/services/mpdscribble/default.nix @@ -0,0 +1 @@ +{ mpdscribble-basic-configuration = ./basic-configuration.nix; }