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>
538 lines
17 KiB
Scheme
538 lines
17 KiB
Scheme
;;; GNU Guix --- Functional package management for GNU
|
||
;;; Copyright © 2017 Peter Mikkelsen <petermikkelsen10@gmail.com>
|
||
;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
|
||
;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
|
||
;;; Copyright © 2022 Bruno Victal <mirai@makinata.eu>
|
||
;;;
|
||
;;; This file is part of GNU Guix.
|
||
;;;
|
||
;;; GNU Guix is free software; you can redistribute it and/or modify it
|
||
;;; under the terms of the GNU General Public License as published by
|
||
;;; the Free Software Foundation; either version 3 of the License, or (at
|
||
;;; your option) any later version.
|
||
;;;
|
||
;;; GNU Guix is distributed in the hope that it will be useful, but
|
||
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
;;; GNU General Public License for more details.
|
||
;;;
|
||
;;; You should have received a copy of the GNU General Public License
|
||
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
(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 (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:
|
||
;;;
|
||
;;; Audio related services
|
||
;;;
|
||
;;; Code:
|
||
|
||
(define (uglify-field-name field-name)
|
||
(let ((str (symbol->string field-name)))
|
||
(string-join (string-split (if (string-suffix? "?" str)
|
||
(string-drop-right str 1)
|
||
str)
|
||
#\-) "_")))
|
||
|
||
(define list-of-string?
|
||
(list-of string?))
|
||
|
||
(define list-of-symbol?
|
||
(list-of symbol?))
|
||
|
||
(define (mpd-serialize-field field-name 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-list-of-string field-name value)
|
||
#~(string-concatenate #$(map (cut mpd-serialize-string field-name <>) value)))
|
||
|
||
(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 . 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
|
||
(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 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/strings to string values
|
||
to be appended to the audio output configuration.")
|
||
|
||
(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)
|
||
(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.")
|
||
|
||
(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."
|
||
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."
|
||
mpd-serialize-deprecated-field)
|
||
|
||
(db-file
|
||
maybe-string
|
||
"The location of the music database.")
|
||
|
||
(state-file
|
||
maybe-string
|
||
"The location of the file that stores current MPD's state.")
|
||
|
||
(sticker-file
|
||
maybe-string
|
||
"The location of the sticker database.")
|
||
|
||
(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-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-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-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)
|
||
(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
|
||
(name 'mpd)
|
||
(description "Run the Music Player Daemon (MPD).")
|
||
(extensions
|
||
(list (service-extension shepherd-root-service-type
|
||
(compose list mpd-shepherd-service))
|
||
(service-extension account-service-type
|
||
mpd-accounts)
|
||
(service-extension rottlog-service-type
|
||
(compose list mpd-log-rotation))))
|
||
(default-value (mpd-configuration))))
|