From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp12.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id GFJ4NoIXo2OFCQAAbAwnHQ (envelope-from ) for ; Wed, 21 Dec 2022 15:26:10 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp12.migadu.com with LMTPS id eH9ZNoIXo2OjaAEAauVa8A (envelope-from ) for ; Wed, 21 Dec 2022 15:26:10 +0100 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 21E08F191 for ; Wed, 21 Dec 2022 15:26:10 +0100 (CET) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1p802m-0003Dp-BA; Wed, 21 Dec 2022 09:26:04 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p802k-0003Df-TV for guix-patches@gnu.org; Wed, 21 Dec 2022 09:26:02 -0500 Received: from debbugs.gnu.org ([209.51.188.43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1p802k-0001x7-LN for guix-patches@gnu.org; Wed, 21 Dec 2022 09:26:02 -0500 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1p802k-0006do-2F for guix-patches@gnu.org; Wed, 21 Dec 2022 09:26:02 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#59866] [PATCH v4 2/2] services: mpd: Refactor MPD service. Resent-From: mirai@makinata.eu Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 21 Dec 2022 14:26:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 59866 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 59866@debbugs.gnu.org Cc: Bruno Victal Received: via spool by 59866-submit@debbugs.gnu.org id=B59866.167163271425518 (code B ref 59866); Wed, 21 Dec 2022 14:26:01 +0000 Received: (at 59866) by debbugs.gnu.org; 21 Dec 2022 14:25:14 +0000 Received: from localhost ([127.0.0.1]:52183 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1p801v-0006dW-Tl for submit@debbugs.gnu.org; Wed, 21 Dec 2022 09:25:14 -0500 Received: from smtpm4.myservices.hosting ([185.26.105.235]:60734) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1p801r-0006dO-Kq for 59866@debbugs.gnu.org; Wed, 21 Dec 2022 09:25:10 -0500 Received: from mail1.netim.hosting (unknown [185.26.106.172]) by smtpm4.myservices.hosting (Postfix) with ESMTP id 5CA0520BEC for <59866@debbugs.gnu.org>; Wed, 21 Dec 2022 15:25:06 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by mail1.netim.hosting (Postfix) with ESMTP id D346680098; Wed, 21 Dec 2022 15:16:01 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at mail1.netim.hosting Received: from mail1.netim.hosting ([127.0.0.1]) by localhost (mail1-1.netim.hosting [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id qwO8616BuW0h; Wed, 21 Dec 2022 15:15:58 +0100 (CET) Received: from guix-nuc.home.arpa (bl12-93-156.dsl.telepac.pt [85.245.93.156]) (Authenticated sender: lumen@makinata.eu) by mail1.netim.hosting (Postfix) with ESMTPSA id 3478580096; Wed, 21 Dec 2022 15:15:58 +0100 (CET) From: mirai@makinata.eu Date: Wed, 21 Dec 2022 14:15:13 +0000 Message-Id: <22745f1abf008ad389638c984e51b12630a18307.1671632111.git.mirai@makinata.eu> X-Mailer: git-send-email 2.38.1 In-Reply-To: <248f6adc7d434e3d8df791311744a38e0d965744.1671632111.git.mirai@makinata.eu> References: <248f6adc7d434e3d8df791311744a38e0d965744.1671632111.git.mirai@makinata.eu> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: guix-patches-bounces+larch=yhetil.org@gnu.org X-Migadu-Country: US X-Migadu-Flow: FLOW_IN ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1671632770; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:resent-cc: resent-from:resent-sender:resent-message-id:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post; bh=afiaS2hLI1DizbCnZW8UIAlMOPka+YR6UKluEh+VUT0=; b=Qjl0MdvsQW2savZFdoACg60yMiAsimsp7z+ldKMEOwih5kFXsrE8rNui1fywBhs8j2dSji EGCk7KFGtAto4pI/mFeOhauSHkyFCbdmsSb19mQbZkCZ8TH0eJASsFw1quAn4yIVoo39Be 7HvBxRiIZmXajg7Ebbevq7ZKyDFGVAlVwHmVk0+SGpFHHA2jFuzNwsc7q2KDNjjld6+/8+ 93QbIERf6nPWEzU1g7OcPUjdk2sB1wRqOnVgcs8wDvwgWiGXtANNyxqHY0RIxyazUXjBqM 70wv35i4HTusqo3C4R7yXK8hbY80iFvIPZmVc92YbkeEVJ5vtZIjM5+ka27g4w== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org"; dmarc=none ARC-Seal: i=1; s=key1; d=yhetil.org; t=1671632770; a=rsa-sha256; cv=none; b=UuYud8BEIdmLw7TZSSi8eBijC5La+41uL+DkdEIip3GWRPV3O4Q2VWschpIpDiMi4v/Yko 5UHgDEQTO2sevCdFraeBehxXtBaD9OLhvVZ3IZ1TPgIgdbzoKYxY5rr7mQTwlHTkJEGH43 h9WtZxI4IuMmZalTPUcMfaUeduNEx7uZy4nhgFbPU4g7UQGLJ9eo00Tm+pNL+3y1LwZ3aE f098JIFqgBz8FSUzDnR0y1WqmrGBl63EmIhK1GFkUCFDUZ9apS6Nm5KK+h9ltzkyQtPCLl Na/gdtQ/LqXNS9VHd5kOg+OviOv7GZzz1xMbpiP2Bv8pTQXlN2neD6z7rwPvHA== X-Spam-Score: -1.99 X-Migadu-Queue-Id: 21E08F191 Authentication-Results: aspmx1.migadu.com; dkim=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org"; dmarc=none X-Migadu-Scanner: scn1.migadu.com X-Migadu-Spam-Score: -1.99 X-TUID: kapzcYEf0tq6 From: Bruno Victal Introduces 'mpd-plugin' and 'mpd-partition' records. Expands 'mpd-output' and 'mpd-configuration' records. Deprecates redundant abbreviated fields in 'mpd-configuration' and avoids serializing unused fields that may introduce undesired behavior. Implement log-rotation via rottlog. Implements Shepherd actions: 'reopen' and 'configuration'. --- doc/guix.texi | 172 +++++++++++++--- gnu/services/audio.scm | 434 +++++++++++++++++++++++++++++++---------- 2 files changed, 476 insertions(+), 130 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index fd03da8c97..3daec7dfaa 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -109,6 +109,7 @@ Copyright @copyright{} 2022 Reily Siegel@* Copyright @copyright{} 2022 Simon Streit@* Copyright @copyright{} 2022 (@* Copyright @copyright{} 2022 John Kehayias@* +Copyright @copyright{} 2022 Bruno Victal@* Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or @@ -33012,79 +33013,184 @@ The service type for @command{mpd} Data type representing the configuration of @command{mpd}. @table @asis -@item @code{user} (default: @code{"mpd"}) +@item @code{package} (default: @code{mpd}) (type: file-like) +The MPD package. + +@item @code{user} (default: @code{"mpd"}) (type: string) The user to run mpd as. -@item @code{music-dir} (default: @code{"~/Music"}) +@item @code{group} (type: maybe-string) +The group to run mpd as. + +@item @code{shepherd-requirement} (default: @code{()}) (type: list-of-symbol) +This is a list of symbols naming Shepherd services that this service +will depend on. + +@item @code{log-file} (default: @code{"/var/log/mpd/log"}) (type: maybe-string) +The location of the log file. Set to @code{syslog} to use the local +syslog daemon or @code{%unset-value} to omit this directive from the +configuration file. + +@item @code{log-level} (type: maybe-string) +Supress any messages below this threshold. Available values: +@code{notice}, @code{info}, @code{verbose}, @code{warning} and +@code{error}. + +@item @code{music-directory} (type: maybe-string) The directory to scan for music files. -@item @code{playlist-dir} (default: @code{"~/.mpd/playlists"}) +@item @code{playlist-directory} (type: maybe-string) The directory to store playlists. -@item @code{db-file} (default: @code{"~/.mpd/tag_cache"}) +@item @code{db-file} (type: maybe-string) The location of the music database. -@item @code{state-file} (default: @code{"~/.mpd/state"}) +@item @code{state-file} (type: maybe-string) The location of the file that stores current MPD's state. -@item @code{sticker-file} (default: @code{"~/.mpd/sticker.sql"}) +@item @code{sticker-file} (type: maybe-string) The location of the sticker database. -@item @code{port} (default: @code{"6600"}) +@item @code{port} (default: @code{6600}) (type: maybe-integer) The port to run mpd on. -@item @code{address} (default: @code{"any"}) -The address that mpd will bind to. To use a Unix domain socket, -an absolute path can be specified here. +@item @code{endpoints} (type: maybe-list-of-string) +The addresses that mpd will bind to. To use a Unix domain socket, an +absolute path can be specified here. + +@item @code{database} (type: maybe-mpd-plugin) +MPD database plugin configuration. + +@item @code{partitions} (default: @code{()}) (type: list-of-mpd-partition) +List of MPD "partitions". + +@item @code{neighbors} (default: @code{()}) (type: list-of-mpd-plugin) +List of MPD neighbor plugin configurations. -@item @code{outputs} (default: @code{"(list (mpd-output))"}) -The audio outputs that MPD can use. By default this is a single output using pulseaudio. +@item @code{inputs} (default: @code{()}) (type: list-of-mpd-plugin) +List of MPD input plugin configurations. + +@item @code{archive-plugins} (default: @code{()}) (type: list-of-mpd-plugin) +List of MPD archive plugin configurations. + +@item @code{input-cache-size} (type: maybe-string) +MPD input cache size. + +@item @code{decoders} (default: @code{()}) (type: list-of-mpd-plugin) +List of MPD decoder plugin configurations. + +@item @code{resampler} (type: maybe-mpd-plugin) +MPD resampler plugin configuration. + +@item @code{filters} (default: @code{()}) (type: list-of-mpd-plugin) +List of MPD filter plugin configurations. + +@item @code{outputs} (type: list-of-mpd-plugin-or-output) +The audio outputs that MPD can use. By default this is a single output +using pulseaudio. + +@item @code{playlist-plugins} (default: @code{()}) (type: list-of-mpd-plugin) +List of MPD playlist plugin configurations. + +@item @code{extra-options} (default: @code{()}) (type: alist) +An association list of option symbols/strings to string values to be +appended to the configuration. + +@end table +@end deftp + +@deftp {Data Type} mpd-plugin +Data type representing a @command{mpd} plugin. + +@table @asis +@item @code{plugin} (type: maybe-string) +Plugin name. + +@item @code{name} (type: maybe-string) +Name. + +@item @code{enabled?} (type: maybe-boolean) +Whether the plugin is enabled/disabled. + +@item @code{extra-options} (default: @code{()}) (type: alist) +An association list of option symbols/strings to string values to be +appended to the plugin configuration. See +@uref{https://mpd.readthedocs.io/en/latest/plugins.html,MPD plugin +reference} for available options. + +@end table +@end deftp + +@deftp {Data Type} mpd-partition +Data type representing a @command{mpd} partition. + +@table @asis +@item @code{name} (type: string) +Partition name. + +@item @code{extra-options} (default: @code{()}) (type: alist) +An association list of option symbols/strings to string values to be +appended to the partition configuration. See +@uref{https://mpd.readthedocs.io/en/latest/user.html#configuring-partitions,Configuring +Partitions} for available options. @end table @end deftp @deftp {Data Type} mpd-output -Data type representing an @command{mpd} audio output. +Data type representing a @command{mpd} audio output. @table @asis -@item @code{name} (default: @code{"MPD"}) +@item @code{name} (default: @code{"MPD"}) (type: string) The name of the audio output. -@item @code{type} (default: @code{"pulse"}) +@item @code{type} (default: @code{"pulse"}) (type: string) The type of audio output. -@item @code{enabled?} (default: @code{#t}) +@item @code{enabled?} (default: @code{#t}) (type: boolean) Specifies whether this audio output is enabled when MPD is started. By default, all audio outputs are enabled. This is just the default setting when there is no state file; with a state file, the previous state is restored. -@item @code{tags?} (default: @code{#t}) +@item @code{format} (type: maybe-string) +Force a specific audio format on output. See +@uref{https://mpd.readthedocs.io/en/latest/user.html#audio-output-format,Global +Audio Format} for a more detailed description. + +@item @code{tags?} (default: @code{#t}) (type: boolean) If set to @code{#f}, then MPD will not send tags to this output. This is only useful for output plugins that can receive tags, for example the @code{httpd} output plugin. -@item @code{always-on?} (default: @code{#f}) +@item @code{always-on?} (default: @code{#f}) (type: boolean) If set to @code{#t}, then MPD attempts to keep this audio output always -open. This may be useful for streaming servers, when you don’t want to +open. This may be useful for streaming servers, when you don?t want to disconnect all listeners even when playback is accidentally stopped. -@item @code{mixer-type} -This field accepts a symbol that specifies which mixer should be used +@item @code{mixer-type} (default: @code{"none"}) (type: string) +This field accepts a string that specifies which mixer should be used for this audio output: the @code{hardware} mixer, the @code{software} mixer, the @code{null} mixer (allows setting the volume, but with no effect; this can be used as a trick to implement an external mixer External Mixer) or no mixer (@code{none}). -@item @code{extra-options} (default: @code{'()}) -An association list of option symbols to string values to be appended to -the audio output configuration. +@item @code{replay-gain-handler} (type: maybe-string) +This field accepts a string that specifies how +@uref{https://mpd.readthedocs.io/en/latest/user.html#replay-gain,Replay +Gain} is to be applied. @code{software} uses an internal software +volume control, @code{mixer} uses the configured (hardware) mixer +control and @code{none} disables replay gain on this audio output. + +@item @code{extra-options} (default: @code{()}) (type: alist) +An association list of option symbols/strings to string values to be +appended to the audio output configuration. @end table @end deftp -The following example shows a configuration of @code{mpd} that provides -an HTTP audio streaming output. +The following example shows a configuration of @command{mpd} that +configures some of its plugins and provides a HTTP audio streaming output. @lisp (service mpd-service-type @@ -33096,7 +33202,19 @@ an HTTP audio streaming output. (mixer-type 'null) (extra-options `((encoder . "vorbis") - (port . "8080")))))))) + (port . "8080")))))) + (decoders + (list (mpd-plugin + (plugin "mikmod") + (enabled? #f)) + (mpd-plugin + (plugin "openmpt") + (enabled? #t) + (extra-options `((repeat-count . -1) + (interpolation-filter . 1)))))) + (resampler (mpd-plugin + (plugin "libsamplerate") + (extra-options `((type . 0))))))) @end lisp diff --git a/gnu/services/audio.scm b/gnu/services/audio.scm index 2351db8a4a..2b0447fd09 100644 --- a/gnu/services/audio.scm +++ b/gnu/services/audio.scm @@ -2,6 +2,7 @@ ;;; Copyright © 2017 Peter Mikkelsen ;;; Copyright © 2019 Ricardo Wurmus ;;; Copyright © 2020 Ludovic Courtès +;;; Copyright © 2022 Bruno Victal ;;; ;;; This file is part of GNU Guix. ;;; @@ -20,19 +21,27 @@ (define-module (gnu services audio) #:use-module (guix gexp) + #:use-module (guix deprecation) + #:use-module (guix diagnostics) + #:use-module (guix i18n) #:use-module (gnu services) #:use-module (gnu services configuration) #:use-module (gnu services shepherd) + #:use-module (gnu services admin) #:use-module (gnu system shadow) #:use-module (gnu packages admin) #:use-module (gnu packages mpd) #:use-module (guix records) #:use-module (ice-9 match) - #:use-module (ice-9 format) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-8) #:use-module (srfi srfi-26) #:export (mpd-output mpd-output? + mpd-plugin + mpd-plugin? + mpd-partition + mpd-partition? mpd-configuration mpd-configuration? mpd-service-type)) @@ -51,180 +60,397 @@ (define (uglify-field-name field-name) #\-) "_"))) -(define (free-form-args? val) - (match val - (() #t) - ((((? symbol?) . (? string?)) . val) (free-form-args? val)) - (_ #f))) +(define list-of-string? + (list-of string?)) -(define* (mpd-serialize-field field-name value #:optional (indent-level 0)) - #~(begin - (use-modules ((ice-9 format))) - (format #f "~v/~a \"~a\"~%" #$indent-level #$(if (string? field-name) - field-name - (uglify-field-name field-name)) #$value))) +(define list-of-symbol? + (list-of symbol?)) -(define* (mpd-serialize-free-form-args field-name value #:optional (indent-level 0)) - (generic-serialize-alist string-append (cut mpd-serialize-field <> <> indent-level) value)) +(define (mpd-serialize-field field-name value) + (let ((field (if (string? field-name) field-name + (uglify-field-name field-name))) + (value (if (boolean? value) (if value "yes" "no") value))) + #~(format #f "~a \"~a\"~%" #$field #$value))) (define mpd-serialize-number mpd-serialize-field) (define mpd-serialize-string mpd-serialize-field) -(define* (mpd-serialize-boolean field-name value #:optional (indent-level 0)) - (mpd-serialize-field field-name (if value "yes" "no") indent-level)) +(define mpd-serialize-boolean mpd-serialize-field) -(define (mpd-serialize-list-of-mpd-output field-name value) - #~(string-append "\naudio_output {\n" - #$@(map (cut serialize-configuration <> - mpd-output-fields) - value) - "}\n")) +(define (mpd-serialize-alist field-name value) + #~(string-append #$@(generic-serialize-alist list mpd-serialize-field value))) -(define (mpd-serialize-configuration configuration) - (mixed-text-file - "mpd.conf" - (serialize-configuration configuration mpd-configuration-fields))) +(define-maybe string (prefix mpd-)) +(define-maybe list-of-string (prefix mpd-)) +(define-maybe boolean (prefix mpd-)) + +;;; TODO: Procedures for deprecated fields, to be removed. + +(define mpd-deprecated-fields '((music-dir . music-directory) + (playlist-dir . playlist-directory) + (address . addresses))) + +(define (port? value) (or (string? value) (integer? value))) + +(define (mpd-serialize-deprecated-field field-name value) + (if (maybe-value-set? value) + (begin (warn-about-deprecation field-name #f + #:replacement (assoc-ref mpd-deprecated-fields field-name)) + (match field-name + ('playlist-dir (mpd-serialize-string "playlist_directory" value)) + ('music-dir (mpd-serialize-string "music_directory" value)) + ('address (mpd-serialize-string "bind_to_address" value)))) + "")) + +(define (mpd-serialize-port field-name value) + (when (string? value) + (warning (G_ "string value for '~a' is deprecated, use integer instead~%") field-name)) + (mpd-serialize-field field-name value)) + +(define-maybe port (prefix mpd-)) + +;;; + +;; Generic MPD plugin record, lists only the most prevalent fields. +(define-configuration mpd-plugin + (plugin + maybe-string + "Plugin name.") + + (name + maybe-string + "Name.") + + (enabled? + maybe-boolean + "Whether the plugin is enabled/disabled.") + + (extra-options + (alist '()) + "An association list of option symbols/strings to string values to be appended to +the plugin configuration. See +@uref{https://mpd.readthedocs.io/en/latest/plugins.html,MPD plugin reference} +for available options.") + + (prefix mpd-)) + +(define (mpd-serialize-mpd-plugin field-name value) + #~(format #f "~a {~%~a}~%" + '#$field-name #$(serialize-configuration value mpd-plugin-fields))) + +(define (mpd-serialize-list-of-mpd-plugin field-name value) + #~(string-append #$@(map (cut mpd-serialize-mpd-plugin field-name <>) value))) -(define mpd-subsystem-serialize-field (cut mpd-serialize-field <> <> 1)) -(define mpd-subsystem-serialize-string (cut mpd-serialize-string <> <> 1)) -(define mpd-subsystem-serialize-number (cut mpd-serialize-number <> <> 1)) -(define mpd-subsystem-serialize-boolean (cut mpd-serialize-boolean <> <> 1)) -(define mpd-subsystem-serialize-free-form-args (cut mpd-serialize-free-form-args <> <> 1)) +(define list-of-mpd-plugin? (list-of mpd-plugin?)) + +(define-maybe mpd-plugin (prefix mpd-)) + +(define-configuration mpd-partition + (name + string + "Partition name.") + + (extra-options + (alist '()) + "An association list of option symbols/strings to string values to be appended to +the partition configuration. See +@uref{https://mpd.readthedocs.io/en/latest/user.html#configuring-partitions,Configuring Partitions} +for available options.") + + (prefix mpd-)) + +(define (mpd-serialize-mpd-partition field-name value) + #~(format #f "partition {~%~a}~%" + #$(serialize-configuration value mpd-partition-fields))) + +(define (mpd-serialize-list-of-mpd-partition field-name value) + #~(string-append #$@(map (cut mpd-serialize-mpd-partition #f <>) value))) + +(define list-of-mpd-partition? + (list-of mpd-partition?)) (define-configuration mpd-output (name (string "MPD") "The name of the audio output.") + (type (string "pulse") "The type of audio output.") + (enabled? (boolean #t) "Specifies whether this audio output is enabled when MPD is started. By default, all audio outputs are enabled. This is just the default setting when there is no state file; with a state file, the previous state is restored.") + + (format + maybe-string + "Force a specific audio format on output. See +@uref{https://mpd.readthedocs.io/en/latest/user.html#audio-output-format,Global Audio Format} +for a more detailed description.") + (tags? (boolean #t) "If set to @code{#f}, then MPD will not send tags to this output. This is only useful for output plugins that can receive tags, for example the @code{httpd} output plugin.") + (always-on? (boolean #f) "If set to @code{#t}, then MPD attempts to keep this audio output always open. This may be useful for streaming servers, when you don’t want to disconnect all listeners even when playback is accidentally stopped.") + (mixer-type (string "none") - "This field accepts a symbol that specifies which mixer should be used + "This field accepts a string that specifies which mixer should be used for this audio output: the @code{hardware} mixer, the @code{software} mixer, the @code{null} mixer (allows setting the volume, but with no effect; this can be used as a trick to implement an external mixer External Mixer) or no mixer (@code{none}).") + + (replay-gain-handler + maybe-string + "This field accepts a string that specifies how +@uref{https://mpd.readthedocs.io/en/latest/user.html#replay-gain,Replay Gain} is +to be applied. @code{software} uses an internal software volume control, +@code{mixer} uses the configured (hardware) mixer control and @code{none} +disables replay gain on this audio output.") + (extra-options - (free-form-args '()) - "An association list of option symbols to string values to be appended to + (alist '()) + "An association list of option symbols/strings to string values to be appended to the audio output configuration.") - (prefix mpd-subsystem-)) -(define list-of-mpd-output? - (list-of mpd-output?)) + (prefix mpd-)) + +(define (mpd-serialize-mpd-output field-name value) + #~(format #f "audio_output {~%~a}~%" + #$(serialize-configuration value mpd-output-fields))) + +(define (mpd-serialize-list-of-mpd-plugin-or-output field-name value) + (receive (plugins outputs) + (partition mpd-plugin? value) + #~(string-append #$@(map (cut mpd-serialize-mpd-plugin "audio_output" <>) plugins) + #$@(map (cut mpd-serialize-mpd-output #f <>) outputs)))) + +(define list-of-mpd-plugin-or-output? + (list-of (lambda (x) + (or (mpd-output? x) (mpd-plugin? x))))) (define-configuration mpd-configuration + (package + (file-like mpd) + "The MPD package." + empty-serializer) + (user (string "mpd") "The user to run mpd as.") - (music-dir - (string "~/Music") + + (group + maybe-string + "The group to run mpd as.") + + (shepherd-requirement + (list-of-symbol '()) + "This is a list of symbols naming Shepherd services that this service +will depend on." + empty-serializer) + + (log-file + (maybe-string "/var/log/mpd/log") + "The location of the log file. Set to @code{syslog} to use the local syslog daemon or +@code{%unset-value} to omit this directive from the configuration file.") + + (log-level + maybe-string + "Supress any messages below this threshold. Available values: @code{notice}, +@code{info}, @code{verbose}, @code{warning} and @code{error}.") + + (music-directory + maybe-string + "The directory to scan for music files.") + + (music-dir ; TODO: deprecated, remove later + maybe-string "The directory to scan for music files." - (lambda (_ x) - (mpd-serialize-field "music_directory" x))) - (playlist-dir - (string "~/.mpd/playlists") + mpd-serialize-deprecated-field) + + (playlist-directory + maybe-string + "The directory to store playlists.") + + (playlist-dir ; TODO: deprecated, remove later + maybe-string "The directory to store playlists." - (lambda (_ x) - (mpd-serialize-field "playlist_directory" x))) + mpd-serialize-deprecated-field) + (db-file - (string "~/.mpd/tag_cache") + maybe-string "The location of the music database.") + (state-file - (string "~/.mpd/state") + maybe-string "The location of the file that stores current MPD's state.") + (sticker-file - (string "~/.mpd/sticker.sql") + maybe-string "The location of the sticker database.") + (port - (string "6600") + (maybe-port 6600) ; TODO: switch to integer "The port to run mpd on.") - (address - (string "any") + + (endpoints + maybe-list-of-string + "The addresses that mpd will bind to. +To use a Unix domain socket, an absolute path can be specified here." + (lambda (_ x) + (if (maybe-value-set? x) + #~(string-append #$@(map + (cut mpd-serialize-field "bind_to_address" <>) + x)) ""))) + + (address ; TODO: deprecated, remove later + maybe-string "The address that mpd will bind to. To use a Unix domain socket, an absolute path can be specified here." + mpd-serialize-deprecated-field) + + (database + maybe-mpd-plugin + "MPD database plugin configuration.") + + (partitions + (list-of-mpd-partition '()) + "List of MPD \"partitions\".") + + (neighbors + (list-of-mpd-plugin '()) + "List of MPD neighbor plugin configurations.") + + (inputs + (list-of-mpd-plugin '()) + "List of MPD input plugin configurations." + (lambda (_ x) + (mpd-serialize-list-of-mpd-plugin "input" x))) + + (archive-plugins + (list-of-mpd-plugin '()) + "List of MPD archive plugin configurations." (lambda (_ x) - (mpd-serialize-field "bind_to_address" x))) + (mpd-serialize-list-of-mpd-plugin "archive_plugin" x))) + + (input-cache-size + maybe-string + "MPD input cache size." + (lambda (_ x) + (if (maybe-value-set? x) + #~(string-append "\ninput_cache {\n" + #$(mpd-serialize-string "size" x) + "}\n") ""))) + + (decoders + (list-of-mpd-plugin '()) + "List of MPD decoder plugin configurations." + (lambda (_ x) + (mpd-serialize-list-of-mpd-plugin "decoder" x))) + + (resampler + maybe-mpd-plugin + "MPD resampler plugin configuration.") + + (filters + (list-of-mpd-plugin '()) + "List of MPD filter plugin configurations." + (lambda (_ x) + (mpd-serialize-list-of-mpd-plugin "filter" x))) + (outputs - (list-of-mpd-output (list (mpd-output))) + (list-of-mpd-plugin-or-output (list (mpd-output))) "The audio outputs that MPD can use. By default this is a single output using pulseaudio.") + + (playlist-plugins + (list-of-mpd-plugin '()) + "List of MPD playlist plugin configurations." + (lambda (_ x) + (mpd-serialize-list-of-mpd-plugin "playlist_plugin" x))) + + (extra-options + (alist '()) + "An association list of option symbols/strings to string values to be appended to +the configuration.") + (prefix mpd-)) -(define (mpd-file-name config file) - "Return a path in /var/run/mpd/ that is writable - by @code{user} from @code{config}." - (string-append "/var/run/mpd/" - (mpd-configuration-user config) - "/" file)) +(define (mpd-serialize-configuration configuration) + (mixed-text-file + "mpd.conf" + (serialize-configuration configuration mpd-configuration-fields))) + +(define (mpd-log-rotation config) + (match-record config (log-file) + (log-rotation + (files (list log-file)) + (post-rotate #~(begin + (use-modules (gnu services herd)) + (with-shepherd-action 'mpd ('reopen) #f)))))) (define (mpd-shepherd-service config) - (shepherd-service - (documentation "Run the MPD (Music Player Daemon)") - (requirement '(user-processes)) - (provision '(mpd)) - (start #~(make-forkexec-constructor - (list #$(file-append mpd "/bin/mpd") - "--no-daemon" - #$(mpd-serialize-configuration config)) - #:environment-variables - ;; Required to detect PulseAudio when run under a user account. - (list (string-append - "XDG_RUNTIME_DIR=/run/user/" - (number->string - (passwd:uid - (getpwnam #$(mpd-configuration-user config)))))) - #:log-file #$(mpd-file-name config "log"))) - (stop #~(make-kill-destructor)))) + (match-record config (user package shepherd-requirement) + (let* ((config-file (mpd-serialize-configuration config))) + (shepherd-service + (documentation "Run the MPD (Music Player Daemon)") + (requirement `(user-processes loopback ,@shepherd-requirement)) + (provision '(mpd)) + (start #~(make-forkexec-constructor + (list #$(file-append package "/bin/mpd") + "--no-daemon" + #$config-file) + #:environment-variables + ;; Required to detect PulseAudio when run under a user account. + (list (string-append "XDG_RUNTIME_DIR=/run/user/" + (number->string (passwd:uid (getpwnam #$user))))))) + (stop #~(make-kill-destructor)) + (actions + (list (shepherd-configuration-action config-file) + (shepherd-action + (name 'reopen) + (documentation "Re-open log files and flush caches.") + (procedure #~(lambda (pid) + (if pid + (begin (kill pid SIGHUP) + (format #t "Issued SIGHUP to Service MPD (PID ~a)." pid)) + (format #t "Service MPD is not running."))))))))))) (define (mpd-service-activation config) - (with-imported-modules '((guix build utils)) + (match-record config (user log-file) #~(begin (use-modules (guix build utils)) - (define %user - (getpw #$(mpd-configuration-user config))) - - (let ((directory #$(mpd-file-name config ".mpd"))) - (mkdir-p directory) - (chown directory (passwd:uid %user) (passwd:gid %user)) - - ;; Make /var/run/mpd/USER user-owned as well. - (chown (dirname directory) - (passwd:uid %user) (passwd:gid %user)))))) - - -(define %mpd-accounts - ;; Default account and group for MPD. - (list (user-group (name "mpd") (system? #t)) - (user-account - (name "mpd") - (group "mpd") - (system? #t) - (comment "Music Player Daemon (MPD) user") - ;; Note: /var/run/mpd hosts one sub-directory per user, of which - ;; /var/run/mpd/mpd corresponds to the "mpd" user. - (home-directory "/var/run/mpd/mpd") + (let* ((user (getpw #$user)) + (deprecated-directory (string-append "/var/run/mpd/" #$user "/.mpd")) + (new-directory (string-append (passwd:dir user) "/.config/mpd"))) + ;; TODO: remove me, migrates from the old location at /var/run/mpd to the new one at /var/lib/mpd. + (when (and (file-exists? deprecated-directory) (not (file-exists? new-directory))) + (rename-file deprecated-directory new-directory) + (chown new-directory (passwd:uid user) (passwd:gid user))) + (mkdir-p (dirname #$log-file)))))) - (shell (file-append shadow "/sbin/nologin"))))) +(define (mpd-accounts config) + (match-record config (user) + (list (user-account + (name user) + (group "nogroup") + (system? #t) + (comment "Music Player Daemon (MPD) user") + (home-directory "/var/lib/mpd") ; MPD can use $HOME (or $XDG_CONFIG_HOME) to place its data + (shell (file-append shadow "/sbin/nologin")))))) (define mpd-service-type (service-type @@ -234,7 +460,9 @@ (define mpd-service-type (list (service-extension shepherd-root-service-type (compose list mpd-shepherd-service)) (service-extension account-service-type - (const %mpd-accounts)) + mpd-accounts) (service-extension activation-service-type - mpd-service-activation))) + mpd-service-activation) + (service-extension rottlog-service-type + (compose list mpd-log-rotation)))) (default-value (mpd-configuration)))) -- 2.38.1