services: Add mympd-service-type.

* gnu/services/audio.scm (mympd-service-type): New variable.
* gnu/tests/audio.scm (%test-mympd): New variable.
* doc/guix.texi: Document it.

Signed-off-by: Liliana Marie Prikler <liliana.prikler@gmail.com>
This commit is contained in:
Bruno Victal 2023-02-04 20:28:16 +00:00 committed by Liliana Marie Prikler
parent 637a9c3b26
commit 139c9a53cb
No known key found for this signature in database
GPG Key ID: 442A84B8C70E2F87
3 changed files with 440 additions and 2 deletions

View File

@ -113,6 +113,7 @@ Copyright @copyright{} 2022 Bruno Victal@*
Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@*
Copyright @copyright{} 2023 Giacomo Leidi@*
Copyright @copyright{} 2022 Antero Mejr@*
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
@ -33396,6 +33397,125 @@ configures some of its plugins and provides a HTTP audio streaming output.
(extra-options `((type . 0)))))))
@end lisp
@subsubheading myMPD
@cindex MPD, web interface
@cindex myMPD service
@uref{https://jcorporation.github.io/myMPD/, myMPD} is a web server
frontend for MPD that provides a mobile friendly web client for MPD.
The following example shows a myMPD instance listening on port 80,
with album cover caching disabled.
@lisp
(service mympd-service-type
(mympd-configuration
(port 80)
(covercache-ttl 0)))
@end lisp
@defvar mympd-service-type
The service type for @command{mympd}.
@end defvar
@c %start of fragment
@deftp {Data Type} mympd-configuration
Available @code{mympd-configuration} fields are:
@table @asis
@item @code{package} (default: @code{mympd}) (type: file-like)
The package object of the myMPD server.
@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{user} (default: @code{"mympd"}) (type: string)
Owner of the @command{mympd} process.
@item @code{group} (default: @code{"nogroup"}) (type: string)
Owner group of the @command{mympd} process.
@item @code{work-directory} (default: @code{"/var/lib/mympd"}) (type: string)
Where myMPD will store its data.
@item @code{cache-directory} (default: @code{"/var/cache/mympd"}) (type: string)
Where myMPD will store its cache.
@item @code{acl} (type: maybe-mympd-ip-acl)
ACL to access the myMPD webserver.
@item @code{covercache-ttl} (default: @code{31}) (type: maybe-integer)
How long to keep cached covers, @code{0} disables cover caching.
@item @code{http?} (default: @code{#t}) (type: boolean)
HTTP support.
@item @code{host} (default: @code{"[::]"}) (type: string)
Host name to listen on.
@item @code{port} (default: @code{80}) (type: maybe-port)
HTTP port to listen on.
@item @code{log-level} (default: @code{5}) (type: integer)
How much detail to include in logs, possible values: @code{0} to
@code{7}.
@item @code{log-to} (default: @code{"/var/log/mympd/log"}) (type: string-or-symbol)
Where to send logs. By default, the service logs to
@file{/var/log/mympd.log}. The alternative is @code{'syslog}, which
sends output to the running syslog service under the @samp{daemon}
facility.
@item @code{lualibs} (default: @code{"all"}) (type: maybe-string)
See
@uref{https://jcorporation.github.io/myMPD/scripting/#lua-standard-libraries}.
@item @code{uri} (type: maybe-string)
Override URI to myMPD. See
@uref{https://github.com/jcorporation/myMPD/issues/950}.
@item @code{script-acl} (default: @code{(mympd-ip-acl (allow '("127.0.0.1")))}) (type: maybe-mympd-ip-acl)
ACL to access the myMPD script backend.
@item @code{ssl?} (default: @code{#f}) (type: boolean)
SSL/TLS support.
@item @code{ssl-port} (default: @code{443}) (type: maybe-port)
Port to listen for HTTPS.
@item @code{ssl-cert} (type: maybe-string)
Path to PEM encoded X.509 SSL/TLS certificate (public key).
@item @code{ssl-key} (type: maybe-string)
Path to PEM encoded SSL/TLS private key.
@item @code{pin-hash} (type: maybe-string)
SHA-256 hashed pin used by myMPD to control settings access by prompting
a pin from the user.
@item @code{save-caches?} (type: maybe-boolean)
Whether to preserve caches between service restarts.
@end table
@end deftp
@c %end of fragment
@c %start of fragment
@deftp {Data Type} mympd-ip-acl
Available @code{mympd-ip-acl} fields are:
@table @asis
@item @code{allow} (default: @code{()}) (type: list-of-string)
Allowed IP addresses.
@item @code{deny} (default: @code{()}) (type: list-of-string)
Disallowed IP addresses.
@end table
@end deftp
@c %end of fragment
@node Virtualization Services
@subsection Virtualization Services

View File

@ -25,6 +25,7 @@
#:use-module (guix diagnostics)
#:use-module (guix i18n)
#:use-module (gnu services)
#:use-module (gnu services admin)
#:use-module (gnu services configuration)
#:use-module (gnu services shepherd)
#:use-module (gnu services admin)
@ -32,6 +33,7 @@
#:use-module (gnu packages admin)
#:use-module (gnu packages mpd)
#:use-module (guix records)
#:use-module (ice-9 format)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
@ -90,7 +92,37 @@
mpd-configuration-outputs
mpd-configuration-playlist-plugins
mpd-configuration-extra-options
mpd-service-type))
mpd-service-type
mympd-service-type
mympd-configuration
mympd-configuration?
mympd-configuration-package
mympd-configuration-shepherd-requirement
mympd-configuration-user
mympd-configuration-group
mympd-configuration-work-directory
mympd-configuration-cache-directory
mympd-configuration-acl
mympd-configuration-covercache-ttl
mympd-configuration-http?
mympd-configuration-host
mympd-configuration-port
mympd-configuration-log-level
mympd-configuration-log-to
mympd-configuration-lualibs
mympd-configuration-uri
mympd-configuration-script-acl
mympd-configuration-ssl?
mympd-configuration-ssl-port
mympd-configuration-ssl-cert
mympd-configuration-ssl-key
mympd-configuration-pin-hash
mympd-configuration-save-caches?
mympd-ip-acl
mympd-ip-acl?
mympd-ip-acl-allow
mympd-ip-acl-deny))
;;; Commentary:
;;;
@ -538,3 +570,238 @@ appended to the configuration.")
(service-extension rottlog-service-type
(compose list mpd-log-rotation))))
(default-value (mpd-configuration))))
;;;
;;; myMPD
;;;
(define (string-or-symbol? x)
(or (symbol? x) (string? x)))
(define-configuration/no-serialization mympd-ip-acl
(allow
(list-of-string '())
"Allowed IP addresses.")
(deny
(list-of-string '())
"Disallowed IP addresses."))
(define-maybe/no-serialization integer)
(define-maybe/no-serialization mympd-ip-acl)
;; XXX: The serialization procedures are insufficient since we require
;; access to multiple fields at once.
;; Fields marked with empty-serializer are never serialized and are
;; used for command-line arguments or by the service definition.
(define-configuration/no-serialization mympd-configuration
(package
(file-like mympd)
"The package object of the myMPD server."
empty-serializer)
(shepherd-requirement
(list-of-symbol '())
"This is a list of symbols naming Shepherd services that this service
will depend on."
empty-serializer)
(user
(string "mympd")
"Owner of the @command{mympd} process."
empty-serializer)
(group
(string "nogroup")
"Owner group of the @command{mympd} process."
empty-serializer)
(work-directory
(string "/var/lib/mympd")
"Where myMPD will store its data."
empty-serializer)
(cache-directory
(string "/var/cache/mympd")
"Where myMPD will store its cache."
empty-serializer)
(acl
maybe-mympd-ip-acl
"ACL to access the myMPD webserver.")
(covercache-ttl
(maybe-integer 31)
"How long to keep cached covers, @code{0} disables cover caching.")
(http?
(boolean #t)
"HTTP support.")
(host
(string "[::]")
"Host name to listen on.")
(port
(maybe-port 80)
"HTTP port to listen on.")
(log-level
(integer 5)
"How much detail to include in logs, possible values: @code{0} to @code{7}.")
(log-to
(string-or-symbol "/var/log/mympd/log")
"Where to send logs. By default, the service logs to
@file{/var/log/mympd.log}. The alternative is @code{'syslog}, which
sends output to the running syslog service under the @samp{daemon} facility."
empty-serializer)
(lualibs
(maybe-string "all")
"See
@url{https://jcorporation.github.io/myMPD/scripting/#lua-standard-libraries}.")
(uri
maybe-string
"Override URI to myMPD.
See @url{https://github.com/jcorporation/myMPD/issues/950}.")
(script-acl
(maybe-mympd-ip-acl (mympd-ip-acl
(allow '("127.0.0.1"))))
"ACL to access the myMPD script backend.")
(ssl?
(boolean #f)
"SSL/TLS support.")
(ssl-port
(maybe-port 443)
"Port to listen for HTTPS.")
(ssl-cert
maybe-string
"Path to PEM encoded X.509 SSL/TLS certificate (public key).")
(ssl-key
maybe-string
"Path to PEM encoded SSL/TLS private key.")
(pin-hash
maybe-string
"SHA-256 hashed pin used by myMPD to control settings access by
prompting a pin from the user.")
(save-caches?
maybe-boolean
"Whether to preserve caches between service restarts."))
(define (mympd-serialize-configuration config)
(define serialize-value
(match-lambda
((? boolean? val) (if val "true" "false"))
((? integer? val) (number->string val))
((? mympd-ip-acl? val) (ip-acl-serialize-configuration val))
((? string? val) val)))
(define (ip-acl-serialize-configuration config)
(define (serialize-list-of-string prefix lst)
(map (cut format #f "~a~a" prefix <>) lst))
(string-join
(append
(serialize-list-of-string "+" (mympd-ip-acl-allow config))
(serialize-list-of-string "-" (mympd-ip-acl-deny config))) ","))
;; myMPD configuration fields are serialized as individual files under
;; <work-directory>/config/.
(match-record config <mympd-configuration> (work-directory acl
covercache-ttl http? host port
log-level lualibs uri script-acl
ssl? ssl-port ssl-cert ssl-key
pin-hash save-caches?)
(define (serialize-field filename value)
(when (maybe-value-set? value)
(list (format #f "~a/config/~a" work-directory filename)
(mixed-text-file filename (serialize-value value)))))
(let ((filename-to-field `(("acl" . ,acl)
("covercache_keep_days" . ,covercache-ttl)
("http" . ,http?)
("http_host" . ,host)
("http_port" . ,port)
("loglevel" . ,log-level)
("lualibs" . ,lualibs)
("mympd_uri" . ,uri)
("scriptacl" . ,script-acl)
("ssl" . ,ssl?)
("ssl_port" . ,ssl-port)
("ssl_cert" . ,ssl-cert)
("ssl_key" . ,ssl-key)
("pin_hash" . ,pin-hash)
("save_caches" . ,save-caches?))))
(filter list?
(generic-serialize-alist list serialize-field
filename-to-field)))))
(define (mympd-shepherd-service config)
(match-record config <mympd-configuration> (package shepherd-requirement
user work-directory
cache-directory log-level log-to)
(let ((log-level* (format #f "MYMPD_LOGLEVEL=~a" log-level)))
(shepherd-service
(documentation "Run the myMPD daemon.")
(requirement `(loopback user-processes ,@shepherd-requirement))
(provision '(mympd))
(start #~(begin
(let* ((pw (getpwnam #$user))
(uid (passwd:uid pw))
(gid (passwd:gid pw)))
(for-each (lambda (dir)
(mkdir-p dir)
(chown dir uid gid))
(list #$work-directory #$cache-directory)))
(make-forkexec-constructor
`(#$(file-append package "/bin/mympd")
"--user" #$user
#$@(if (eqv? log-to 'syslog) '("--syslog") '())
"--workdir" #$work-directory
"--cachedir" #$cache-directory)
#:environment-variables (list #$log-level*)
#:log-file #$(if (string? log-to) log-to #f))))
(stop #~(make-kill-destructor))))))
(define (mympd-accounts config)
(match-record config <mympd-configuration> (user group)
(list (user-group (name group)
(system? #t))
(user-account (name user)
(group group)
(system? #t)
(comment "myMPD user")
(home-directory "/var/empty")
(shell (file-append shadow "/sbin/nologin"))))))
(define (mympd-log-rotation config)
(match-record config <mympd-configuration> (log-to)
(if (string? log-to)
(list (log-rotation
(files (list log-to))))
'())))
(define mympd-service-type
(service-type
(name 'mympd)
(extensions
(list (service-extension shepherd-root-service-type
(compose list mympd-shepherd-service))
(service-extension account-service-type
mympd-accounts)
(service-extension special-files-service-type
mympd-serialize-configuration)
(service-extension rottlog-service-type
mympd-log-rotation)))
(description "Run myMPD, a frontend for MPD. (Music Player Daemon)")
(default-value (mympd-configuration))))

View File

@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Peter Mikkelsen <petermikkelsen10@gmail.com>
;;; Copyright © 2022 Bruno Victal <mirai@makinata.eu>
;;;
;;; This file is part of GNU Guix.
;;;
@ -22,9 +23,11 @@
#:use-module (gnu system vm)
#:use-module (gnu services)
#:use-module (gnu services audio)
#:use-module (gnu services networking)
#:use-module (gnu packages mpd)
#:use-module (guix gexp)
#:export (%test-mpd))
#:export (%test-mpd
%test-mympd))
(define %mpd-os
(simple-operating-system
@ -76,3 +79,51 @@
(name "mpd")
(description "Test that the mpd can run and be connected to.")
(value (run-mpd-test))))
(define (run-mympd-test)
(define os (marionette-operating-system
(simple-operating-system (service dhcp-client-service-type)
(service mympd-service-type))
#:imported-modules '((gnu services herd))))
(define vm
(virtual-machine
(operating-system os)
(port-forwardings '((8080 . 80)))))
(define test
(with-imported-modules '((gnu build marionette))
#~(begin
(use-modules (srfi srfi-64)
(srfi srfi-8)
(web client)
(web response)
(gnu build marionette))
(define marionette
(make-marionette (list #$vm)))
(test-runner-current (system-test-runner #$output))
(test-begin "mympd")
(test-assert "service is running"
(marionette-eval '(begin
(use-modules (gnu services herd))
(start-service 'mympd))
marionette))
(test-assert "HTTP port ready"
(wait-for-tcp-port 80 marionette))
(test-equal "http-head"
200
(receive (x _) (http-head "http://localhost:8080") (response-code x)))
(test-end))))
(gexp->derivation "mympd-test" test))
(define %test-mympd
(system-test
(name "mympd")
(description "Connect to a running myMPD service.")
(value (run-mympd-test))))