summaryrefslogtreecommitdiff
path: root/gnu/services/audio.scm
diff options
context:
space:
mode:
authorBruno Victal <mirai@makinata.eu>2023-02-02 20:07:37 +0000
committerLiliana Marie Prikler <liliana.prikler@gmail.com>2023-02-05 06:56:43 +0100
commit5c5f0fc1135ff15f9c4adfc5f27eadd9a592b5d1 (patch)
tree0d3e2f98243c439ba00e1ef25e47cbee3c03eac8 /gnu/services/audio.scm
parentd7fd9ec209f72e9cfff04a48bf16e092f258d8ff (diff)
services: mpd: Refactor MPD service.
Refactor mpd-service-type to support additional mpd.conf directives and move activation-service-extension into service constructor. * gnu/services/audio.scm (mpd-plugin, mpd-partition): New records. (mpd-serialize-boolean): Alias to and integrate logic into... (mpd-serialize-field): ... this. (mpd-serialize-list-of-string): New variable. (mpd-plugin?, mpd-partition?, list-of-string?, list-of-symbol?) (list-of-mpd-plugin?, list-of-mpd-partition?) (list-of-mpd-plugin-or-output?, port?): New variables. (mpd-file-name, mpd-service-activation): Remove variables. (mpd-configuration) [package, group, shepherd-requirement, log-file, log-level, music-directory] [playlist-directory, endpoints, database, partitions, neighbors, inputs] [archive-plugins, input-cache-size, decoders, resampler, filters] [playlist-plugins, extra-options]: New fields. [music-dir, playlist-dir, address]: Deprecate shorthand fields. [db-file, state-file, sticker-file, port, outputs]: Change admissible type. (mpd-shepherd-service) [actions]: New shepherd actions: 'reopen' and 'configuration'. [requirement]: Splice with 'shepherd-requirement' field. [start]: Use 'package' field. Remove #:log-file parameter. Move activation-service extension into constructor. (mpd-accounts): Honor user and group names from configuration. (mpd-log-rotation): New procedure. (mpd-service-type)[extensions]: Add rottlog-service-type extension. Remove activation-service-type extension. (mpd-output-name, mpd-output-type, mpd-output-enabled?, mpd-output-format) (mpd-output-tags?, mpd-output-always-on?, mpd-output-mixer-type) (mpd-output-replay-gain-handler, mpd-output-extra-options) (mpd-plugin-plugin, mpd-plugin-name, mpd-plugin-enabled?) (mpd-plugin-extra-options) (mpd-partition-name, mpd-partition-extra-options) (mpd-configuration-package, mpd-configuration-user) (mpd-configuration-group, mpd-configuration-shepherd-requirement) (mpd-configuration-log-file, mpd-configuration-log-level) (mpd-configuration-music-directory, mpd-configuration-music-dir) (mpd-configuration-playlist-directory, mpd-configuration-playlist-dir) (mpd-configuration-db-file, mpd-configuration-state-file) (mpd-configuration-sticker-file, mpd-configuration-default-port) (mpd-configuration-endpoints, mpd-configuration-address) (mpd-configuration-database, mpd-configuration-partitions) (mpd-configuration-neighbors, mpd-configuration-inputs) (mpd-configuration-archive-plugins, mpd-configuration-input-cache-size) (mpd-configuration-decoders, mpd-configuration-resampler) (mpd-configuration-filters, mpd-configuration-outputs) (mpd-configuration-playlist-plugins, mpd-configuration-extra-options): Export accessors. * doc/guix.texi (Audio Services)[Music Player Daemon]: Update documentation. Signed-off-by: Liliana Marie Prikler <liliana.prikler@gmail.com>
Diffstat (limited to 'gnu/services/audio.scm')
-rw-r--r--gnu/services/audio.scm493
1 files changed, 394 insertions, 99 deletions
diff --git a/gnu/services/audio.scm b/gnu/services/audio.scm
index b7cb0ebe38..2bb8f66c96 100644
--- a/gnu/services/audio.scm
+++ b/gnu/services/audio.scm
@@ -21,9 +21,13 @@
(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)
@@ -31,10 +35,61 @@
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-71)
#:export (mpd-output
mpd-output?
+ mpd-output-name
+ mpd-output-type
+ mpd-output-enabled?
+ mpd-output-format
+ mpd-output-tags?
+ mpd-output-always-on?
+ mpd-output-mixer-type
+ mpd-output-replay-gain-handler
+ mpd-output-extra-options
+
+ mpd-plugin
+ mpd-plugin?
+ mpd-plugin-plugin
+ mpd-plugin-name
+ mpd-plugin-enabled?
+ mpd-plugin-extra-options
+
+ mpd-partition
+ mpd-partition?
+ mpd-partition-name
+ mpd-partition-extra-options
+
mpd-configuration
mpd-configuration?
+ mpd-configuration-package
+ mpd-configuration-user
+ mpd-configuration-group
+ mpd-configuration-shepherd-requirement
+ mpd-configuration-log-file
+ mpd-configuration-log-level
+ mpd-configuration-music-directory
+ mpd-configuration-music-dir
+ mpd-configuration-playlist-directory
+ mpd-configuration-playlist-dir
+ mpd-configuration-db-file
+ mpd-configuration-state-file
+ mpd-configuration-sticker-file
+ mpd-configuration-default-port
+ mpd-configuration-endpoints
+ mpd-configuration-address
+ mpd-configuration-database
+ mpd-configuration-partitions
+ mpd-configuration-neighbors
+ mpd-configuration-inputs
+ mpd-configuration-archive-plugins
+ mpd-configuration-input-cache-size
+ mpd-configuration-decoders
+ mpd-configuration-resampler
+ mpd-configuration-filters
+ mpd-configuration-outputs
+ mpd-configuration-playlist-plugins
+ mpd-configuration-extra-options
mpd-service-type))
;;; Commentary:
@@ -50,34 +105,125 @@
str)
#\-) "_")))
+(define list-of-string?
+ (list-of string?))
+
+(define list-of-symbol?
+ (list-of symbol?))
+
(define (mpd-serialize-field field-name value)
- #~(format #f "~a ~s~%" #$(if (string? field-name)
- field-name
- (uglify-field-name field-name))
- #$(if (string? value)
- value
- (object->string value))))
+ (let ((field (if (string? field-name) field-name
+ (uglify-field-name field-name)))
+ (value (cond
+ ((boolean? value) (if value "yes" "no"))
+ ((string? value) value)
+ (else (object->string value)))))
+ #~(format #f "~a ~s~%" #$field #$value)))
(define (mpd-serialize-alist field-name value)
#~(string-append #$@(generic-serialize-alist list mpd-serialize-field
value)))
(define mpd-serialize-string mpd-serialize-field)
+(define mpd-serialize-boolean mpd-serialize-field)
-(define (mpd-serialize-boolean field-name value)
- (mpd-serialize-field field-name (if value "yes" "no")))
+(define (mpd-serialize-list-of-string field-name value)
+ #~(string-concatenate #$(map (cut mpd-serialize-string field-name <>) value)))
-(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-maybe string (prefix mpd-))
+(define-maybe list-of-string (prefix mpd-))
+(define-maybe boolean (prefix mpd-))
-(define (mpd-serialize-configuration configuration)
- (mixed-text-file
- "mpd.conf"
- (serialize-configuration configuration mpd-configuration-fields)))
+;;; TODO: Procedures for deprecated fields, to be removed.
+
+(define mpd-deprecated-fields '((music-dir . music-directory)
+ (playlist-dir . playlist-directory)
+ (address . endpoints)))
+
+(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 "port" 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 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
@@ -95,6 +241,12 @@ 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
@@ -109,125 +261,268 @@ 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
(alist '())
- "An association list of option symbols to string values to be appended to
-the audio output configuration.")
+ "An association list of option symbols/strings to string values
+to be appended to the audio output configuration.")
(prefix mpd-))
-(define list-of-mpd-output?
- (list-of mpd-output?))
+(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)
+ (let ((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
+ (string "mpd")
+ "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)))
+ mpd-serialize-deprecated-field)
+
+ (playlist-directory
+ maybe-string
+ "The directory to store playlists.")
- (playlist-dir
- (string "~/.mpd/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")
- "The port to run mpd on.")
-
- (address
- (string "any")
+ (default-port
+ (maybe-port 6600)
+ "The default port to run mpd on.")
+
+ (endpoints
+ maybe-list-of-string
+ "The addresses that mpd will bind to. A port different from
+@var{default-port} may be specified, e.g. @code{localhost:6602} and
+IPv6 addresses must be enclosed in square brackets when a different
+port is used.
+To use a Unix domain socket, an absolute path or a path starting with @code{~}
+can be specified here."
+ (lambda (_ endpoints)
+ (if (maybe-value-set? endpoints)
+ (mpd-serialize-list-of-string "bind_to_address" endpoints)
+ "")))
+
+ (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-field "bind_to_address" 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-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 <mpd-configuration> (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))))
-
-(define (mpd-service-activation config)
- (with-imported-modules '((guix build utils))
- #~(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")
-
- (shell (file-append shadow "/sbin/nologin")))))
+ (match-record config <mpd-configuration> (user package shepherd-requirement
+ log-file playlist-directory
+ db-file state-file sticker-file)
+ (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 #~(begin
+ (and=> #$(maybe-value log-file)
+ (compose mkdir-p dirname))
+
+ (let ((user (getpw #$user)))
+ (for-each
+ (lambda (x)
+ (when (and x (not (file-exists? x)))
+ (mkdir-p x)
+ (chown x (passwd:uid user) (passwd:gid user))))
+ (list #$(maybe-value playlist-directory)
+ (and=> #$(maybe-value db-file) dirname)
+ (and=> #$(maybe-value state-file) dirname)
+ (and=> #$(maybe-value sticker-file) dirname))))
+
+ (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-accounts config)
+ (match-record config <mpd-configuration> (user group)
+ (list (user-group
+ (name group)
+ (system? #t))
+ (user-account
+ (name user)
+ (group group)
+ (system? #t)
+ (comment "Music Player Daemon (MPD) user")
+ ;; MPD can use $HOME (or $XDG_CONFIG_HOME) to place its data
+ (home-directory "/var/lib/mpd")
+ (shell (file-append shadow "/sbin/nologin"))))))
(define mpd-service-type
(service-type
@@ -237,7 +532,7 @@ By default this is a single output using pulseaudio.")
(list (service-extension shepherd-root-service-type
(compose list mpd-shepherd-service))
(service-extension account-service-type
- (const %mpd-accounts))
- (service-extension activation-service-type
- mpd-service-activation)))
+ mpd-accounts)
+ (service-extension rottlog-service-type
+ (compose list mpd-log-rotation))))
(default-value (mpd-configuration))))