audio.scm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2017 Peter Mikkelsen <petermikkelsen10@gmail.com>
  3. ;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
  4. ;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
  5. ;;; Copyright © 2022⁠–⁠2023 Bruno Victal <mirai@makinata.eu>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (gnu services audio)
  22. #:use-module (guix gexp)
  23. #:use-module (guix deprecation)
  24. #:use-module (guix diagnostics)
  25. #:use-module (guix i18n)
  26. #:use-module (gnu services)
  27. #:use-module (gnu services admin)
  28. #:use-module (gnu services configuration)
  29. #:use-module (gnu services shepherd)
  30. #:use-module (gnu services admin)
  31. #:use-module (gnu system shadow)
  32. #:use-module (gnu packages admin)
  33. #:use-module (gnu packages mpd)
  34. #:use-module (guix records)
  35. #:use-module (ice-9 format)
  36. #:use-module (ice-9 match)
  37. #:use-module (srfi srfi-1)
  38. #:use-module (srfi srfi-26)
  39. #:use-module (srfi srfi-71)
  40. #:export (mpd-output
  41. mpd-output?
  42. mpd-output-name
  43. mpd-output-type
  44. mpd-output-enabled?
  45. mpd-output-format
  46. mpd-output-tags?
  47. mpd-output-always-on?
  48. mpd-output-mixer-type
  49. mpd-output-replay-gain-handler
  50. mpd-output-extra-options
  51. mpd-plugin
  52. mpd-plugin?
  53. mpd-plugin-plugin
  54. mpd-plugin-name
  55. mpd-plugin-enabled?
  56. mpd-plugin-extra-options
  57. mpd-partition
  58. mpd-partition?
  59. mpd-partition-name
  60. mpd-partition-extra-options
  61. mpd-configuration
  62. mpd-configuration?
  63. mpd-configuration-package
  64. mpd-configuration-user
  65. mpd-configuration-group
  66. mpd-configuration-shepherd-requirement
  67. mpd-configuration-log-file
  68. mpd-configuration-log-level
  69. mpd-configuration-music-directory
  70. mpd-configuration-music-dir
  71. mpd-configuration-playlist-directory
  72. mpd-configuration-playlist-dir
  73. mpd-configuration-db-file
  74. mpd-configuration-state-file
  75. mpd-configuration-sticker-file
  76. mpd-configuration-default-port
  77. mpd-configuration-endpoints
  78. mpd-configuration-address
  79. mpd-configuration-database
  80. mpd-configuration-partitions
  81. mpd-configuration-neighbors
  82. mpd-configuration-inputs
  83. mpd-configuration-archive-plugins
  84. mpd-configuration-input-cache-size
  85. mpd-configuration-decoders
  86. mpd-configuration-resampler
  87. mpd-configuration-filters
  88. mpd-configuration-outputs
  89. mpd-configuration-playlist-plugins
  90. mpd-configuration-extra-options
  91. mpd-service-type
  92. mympd-service-type
  93. mympd-configuration
  94. mympd-configuration?
  95. mympd-configuration-package
  96. mympd-configuration-shepherd-requirement
  97. mympd-configuration-user
  98. mympd-configuration-group
  99. mympd-configuration-work-directory
  100. mympd-configuration-cache-directory
  101. mympd-configuration-acl
  102. mympd-configuration-covercache-ttl
  103. mympd-configuration-http?
  104. mympd-configuration-host
  105. mympd-configuration-port
  106. mympd-configuration-log-level
  107. mympd-configuration-log-to
  108. mympd-configuration-lualibs
  109. mympd-configuration-uri
  110. mympd-configuration-script-acl
  111. mympd-configuration-ssl?
  112. mympd-configuration-ssl-port
  113. mympd-configuration-ssl-cert
  114. mympd-configuration-ssl-key
  115. mympd-configuration-pin-hash
  116. mympd-configuration-save-caches?
  117. mympd-ip-acl
  118. mympd-ip-acl?
  119. mympd-ip-acl-allow
  120. mympd-ip-acl-deny))
  121. ;;; Commentary:
  122. ;;;
  123. ;;; Audio related services
  124. ;;;
  125. ;;; Code:
  126. (define (uglify-field-name field-name)
  127. (let ((str (symbol->string field-name)))
  128. (string-join (string-split (if (string-suffix? "?" str)
  129. (string-drop-right str 1)
  130. str)
  131. #\-) "_")))
  132. (define list-of-symbol?
  133. (list-of symbol?))
  134. ;; Helpers for deprecated field types, to be removed later.
  135. (define %lazy-group (make-symbol "%lazy-group"))
  136. (define (%set-user-group user group)
  137. (user-account
  138. (inherit user)
  139. (group (user-group-name group))))
  140. ;;;
  141. ;;; MPD
  142. ;;;
  143. (define (mpd-serialize-field field-name value)
  144. (let ((field (if (string? field-name) field-name
  145. (uglify-field-name field-name)))
  146. (value (cond
  147. ((boolean? value) (if value "yes" "no"))
  148. ((string? value) value)
  149. (else (object->string value)))))
  150. #~(format #f "~a ~s~%" #$field #$value)))
  151. (define (mpd-serialize-alist field-name value)
  152. #~(string-append #$@(generic-serialize-alist list mpd-serialize-field
  153. value)))
  154. (define mpd-serialize-string mpd-serialize-field)
  155. (define mpd-serialize-boolean mpd-serialize-field)
  156. (define (mpd-serialize-list-of-strings field-name value)
  157. #~(string-append #$@(map (cut mpd-serialize-string field-name <>) value)))
  158. (define (mpd-serialize-user-account field-name value)
  159. (mpd-serialize-string field-name (user-account-name value)))
  160. (define (mpd-serialize-user-group field-name value)
  161. (mpd-serialize-string field-name (user-group-name value)))
  162. (define-maybe string (prefix mpd-))
  163. (define-maybe list-of-strings (prefix mpd-))
  164. (define-maybe boolean (prefix mpd-))
  165. (define %mpd-user
  166. (user-account
  167. (name "mpd")
  168. (group %lazy-group)
  169. (system? #t)
  170. (comment "Music Player Daemon (MPD) user")
  171. ;; MPD can use $HOME (or $XDG_CONFIG_HOME) to place its data
  172. (home-directory "/var/lib/mpd")
  173. (shell (file-append shadow "/sbin/nologin"))))
  174. (define %mpd-group
  175. (user-group
  176. (name "mpd")
  177. (system? #t)))
  178. ;;; TODO: Procedures for deprecated fields, to be removed.
  179. (define mpd-deprecated-fields '((music-dir . music-directory)
  180. (playlist-dir . playlist-directory)
  181. (address . endpoints)))
  182. (define (port? value) (or (string? value) (integer? value)))
  183. (define (mpd-serialize-deprecated-field field-name value)
  184. (if (maybe-value-set? value)
  185. (begin
  186. (warn-about-deprecation
  187. field-name #f
  188. #:replacement (assoc-ref mpd-deprecated-fields field-name))
  189. (match field-name
  190. ('playlist-dir (mpd-serialize-string "playlist_directory" value))
  191. ('music-dir (mpd-serialize-string "music_directory" value))
  192. ('address (mpd-serialize-string "bind_to_address" value))))
  193. ""))
  194. (define (mpd-serialize-port field-name value)
  195. (when (string? value)
  196. (warning
  197. (G_ "string value for '~a' is deprecated, use integer instead~%")
  198. field-name))
  199. (mpd-serialize-field "port" value))
  200. (define-maybe port (prefix mpd-))
  201. ;;; Procedures for unsupported value types, to be removed.
  202. (define (mpd-user-sanitizer value)
  203. (cond ((user-account? value) value)
  204. ((string? value)
  205. (warning (G_ "string value for 'user' is deprecated, use \
  206. user-account instead~%"))
  207. (user-account
  208. (inherit %mpd-user)
  209. (name value)
  210. ;; XXX: This is to be lazily substituted in (…-accounts)
  211. ;; with the value from 'group'.
  212. (group %lazy-group)))
  213. (else
  214. (configuration-field-error #f 'user value))))
  215. (define (mpd-group-sanitizer value)
  216. (cond ((user-group? value) value)
  217. ((string? value)
  218. (warning (G_ "string value for 'group' is deprecated, use \
  219. user-group instead~%"))
  220. (user-group
  221. (inherit %mpd-group)
  222. (name value)))
  223. (else
  224. (configuration-field-error #f 'group value))))
  225. ;;;
  226. ;; Generic MPD plugin record, lists only the most prevalent fields.
  227. (define-configuration mpd-plugin
  228. (plugin
  229. maybe-string
  230. "Plugin name.")
  231. (name
  232. maybe-string
  233. "Name.")
  234. (enabled?
  235. maybe-boolean
  236. "Whether the plugin is enabled/disabled.")
  237. (extra-options
  238. (alist '())
  239. "An association list of option symbols/strings to string values
  240. to be appended to the plugin configuration. See
  241. @uref{https://mpd.readthedocs.io/en/latest/plugins.html,MPD plugin reference}
  242. for available options.")
  243. (prefix mpd-))
  244. (define (mpd-serialize-mpd-plugin field-name value)
  245. #~(format #f "~a {~%~a}~%"
  246. '#$field-name
  247. #$(serialize-configuration value mpd-plugin-fields)))
  248. (define (mpd-serialize-list-of-mpd-plugin field-name value)
  249. #~(string-append #$@(map (cut mpd-serialize-mpd-plugin field-name <>)
  250. value)))
  251. (define list-of-mpd-plugin? (list-of mpd-plugin?))
  252. (define-maybe mpd-plugin (prefix mpd-))
  253. (define-configuration mpd-partition
  254. (name
  255. string
  256. "Partition name.")
  257. (extra-options
  258. (alist '())
  259. "An association list of option symbols/strings to string values
  260. to be appended to the partition configuration. See
  261. @uref{https://mpd.readthedocs.io/en/latest/user.html#configuring-partitions,Configuring Partitions}
  262. for available options.")
  263. (prefix mpd-))
  264. (define (mpd-serialize-mpd-partition field-name value)
  265. #~(format #f "partition {~%~a}~%"
  266. #$(serialize-configuration value mpd-partition-fields)))
  267. (define (mpd-serialize-list-of-mpd-partition field-name value)
  268. #~(string-append #$@(map (cut mpd-serialize-mpd-partition #f <>) value)))
  269. (define list-of-mpd-partition?
  270. (list-of mpd-partition?))
  271. (define-configuration mpd-output
  272. (name
  273. (string "MPD")
  274. "The name of the audio output.")
  275. (type
  276. (string "pulse")
  277. "The type of audio output.")
  278. (enabled?
  279. (boolean #t)
  280. "Specifies whether this audio output is enabled when MPD is started. By
  281. default, all audio outputs are enabled. This is just the default
  282. setting when there is no state file; with a state file, the previous
  283. state is restored.")
  284. (format
  285. maybe-string
  286. "Force a specific audio format on output. See
  287. @uref{https://mpd.readthedocs.io/en/latest/user.html#audio-output-format,Global Audio Format}
  288. for a more detailed description.")
  289. (tags?
  290. (boolean #t)
  291. "If set to @code{#f}, then MPD will not send tags to this output. This
  292. is only useful for output plugins that can receive tags, for example the
  293. @code{httpd} output plugin.")
  294. (always-on?
  295. (boolean #f)
  296. "If set to @code{#t}, then MPD attempts to keep this audio output always
  297. open. This may be useful for streaming servers, when you don’t want to
  298. disconnect all listeners even when playback is accidentally stopped.")
  299. (mixer-type
  300. (string "none")
  301. "This field accepts a string that specifies which mixer should be used
  302. for this audio output: the @code{hardware} mixer, the @code{software}
  303. mixer, the @code{null} mixer (allows setting the volume, but with no
  304. effect; this can be used as a trick to implement an external mixer
  305. External Mixer) or no mixer (@code{none})."
  306. (sanitizer
  307. (lambda (x) ; TODO: deprecated, remove me later.
  308. (cond
  309. ((symbol? x)
  310. (warning (G_ "symbol value for 'mixer-type' is deprecated, \
  311. use string instead~%"))
  312. (symbol->string x))
  313. ((string? x) x)
  314. (else
  315. (configuration-field-error #f 'mixer-type x))))))
  316. (replay-gain-handler
  317. maybe-string
  318. "This field accepts a string that specifies how
  319. @uref{https://mpd.readthedocs.io/en/latest/user.html#replay-gain,Replay Gain}
  320. is to be applied. @code{software} uses an internal software volume control,
  321. @code{mixer} uses the configured (hardware) mixer control and @code{none}
  322. disables replay gain on this audio output.")
  323. (extra-options
  324. (alist '())
  325. "An association list of option symbols/strings to string values
  326. to be appended to the audio output configuration.")
  327. (prefix mpd-))
  328. (define (mpd-serialize-mpd-output field-name value)
  329. #~(format #f "audio_output {~%~a}~%"
  330. #$(serialize-configuration value mpd-output-fields)))
  331. (define (mpd-serialize-list-of-mpd-plugin-or-output field-name value)
  332. (let ((plugins outputs (partition mpd-plugin? value)))
  333. #~(string-append #$@(map (cut mpd-serialize-mpd-plugin "audio_output" <>)
  334. plugins)
  335. #$@(map (cut mpd-serialize-mpd-output #f <>) outputs))))
  336. (define list-of-mpd-plugin-or-output?
  337. (list-of (lambda (x)
  338. (or (mpd-output? x) (mpd-plugin? x)))))
  339. (define-configuration mpd-configuration
  340. (package
  341. (file-like mpd)
  342. "The MPD package."
  343. empty-serializer)
  344. (user
  345. (user-account %mpd-user)
  346. "The user to run mpd as."
  347. (sanitizer mpd-user-sanitizer))
  348. (group
  349. (user-group %mpd-group)
  350. "The group to run mpd as."
  351. (sanitizer mpd-group-sanitizer))
  352. (shepherd-requirement
  353. (list-of-symbol '())
  354. "This is a list of symbols naming Shepherd services that this service
  355. will depend on."
  356. empty-serializer)
  357. (environment-variables
  358. (list-of-strings '("PULSE_CLIENTCONFIG=/etc/pulse/client.conf"
  359. "PULSE_CONFIG=/etc/pulse/daemon.conf"))
  360. "A list of strings specifying environment variables."
  361. empty-serializer)
  362. (log-file
  363. (maybe-string "/var/log/mpd/log")
  364. "The location of the log file. Set to @code{syslog} to use the
  365. local syslog daemon or @code{%unset-value} to omit this directive
  366. from the configuration file.")
  367. (log-level
  368. maybe-string
  369. "Supress any messages below this threshold.
  370. Available values: @code{notice}, @code{info}, @code{verbose},
  371. @code{warning} and @code{error}.")
  372. (music-directory
  373. maybe-string
  374. "The directory to scan for music files.")
  375. (music-dir ; TODO: deprecated, remove later
  376. maybe-string
  377. "The directory to scan for music files."
  378. (serializer mpd-serialize-deprecated-field))
  379. (playlist-directory
  380. maybe-string
  381. "The directory to store playlists.")
  382. (playlist-dir ; TODO: deprecated, remove later
  383. maybe-string
  384. "The directory to store playlists."
  385. (serializer mpd-serialize-deprecated-field))
  386. (db-file
  387. maybe-string
  388. "The location of the music database.")
  389. (state-file
  390. maybe-string
  391. "The location of the file that stores current MPD's state.")
  392. (sticker-file
  393. maybe-string
  394. "The location of the sticker database.")
  395. (default-port
  396. (maybe-port 6600)
  397. "The default port to run mpd on.")
  398. (endpoints
  399. maybe-list-of-strings
  400. "The addresses that mpd will bind to. A port different from
  401. @var{default-port} may be specified, e.g. @code{localhost:6602} and
  402. IPv6 addresses must be enclosed in square brackets when a different
  403. port is used.
  404. To use a Unix domain socket, an absolute path or a path starting with @code{~}
  405. can be specified here."
  406. (serializer
  407. (lambda (_ endpoints)
  408. (if (maybe-value-set? endpoints)
  409. (mpd-serialize-list-of-strings "bind_to_address" endpoints)
  410. ""))))
  411. (address ; TODO: deprecated, remove later
  412. maybe-string
  413. "The address that mpd will bind to.
  414. To use a Unix domain socket, an absolute path can be specified here."
  415. (serializer mpd-serialize-deprecated-field))
  416. (database
  417. maybe-mpd-plugin
  418. "MPD database plugin configuration.")
  419. (partitions
  420. (list-of-mpd-partition '())
  421. "List of MPD \"partitions\".")
  422. (neighbors
  423. (list-of-mpd-plugin '())
  424. "List of MPD neighbor plugin configurations.")
  425. (inputs
  426. (list-of-mpd-plugin '())
  427. "List of MPD input plugin configurations."
  428. (serializer (lambda (_ x)
  429. (mpd-serialize-list-of-mpd-plugin "input" x))))
  430. (archive-plugins
  431. (list-of-mpd-plugin '())
  432. "List of MPD archive plugin configurations."
  433. (serializer (lambda (_ x)
  434. (mpd-serialize-list-of-mpd-plugin "archive_plugin" x))))
  435. (input-cache-size
  436. maybe-string
  437. "MPD input cache size."
  438. (serializer (lambda (_ x)
  439. (if (maybe-value-set? x)
  440. #~(string-append "\ninput_cache {\n"
  441. #$(mpd-serialize-string "size" x)
  442. "}\n") ""))))
  443. (decoders
  444. (list-of-mpd-plugin '())
  445. "List of MPD decoder plugin configurations."
  446. (serializer (lambda (_ x)
  447. (mpd-serialize-list-of-mpd-plugin "decoder" x))))
  448. (resampler
  449. maybe-mpd-plugin
  450. "MPD resampler plugin configuration.")
  451. (filters
  452. (list-of-mpd-plugin '())
  453. "List of MPD filter plugin configurations."
  454. (serializer (lambda (_ x)
  455. (mpd-serialize-list-of-mpd-plugin "filter" x))))
  456. (outputs
  457. (list-of-mpd-plugin-or-output (list (mpd-output)))
  458. "The audio outputs that MPD can use.
  459. By default this is a single output using pulseaudio.")
  460. (playlist-plugins
  461. (list-of-mpd-plugin '())
  462. "List of MPD playlist plugin configurations."
  463. (serializer (lambda (_ x)
  464. (mpd-serialize-list-of-mpd-plugin "playlist_plugin" x))))
  465. (extra-options
  466. (alist '())
  467. "An association list of option symbols/strings to string values to be
  468. appended to the configuration.")
  469. (prefix mpd-))
  470. (define (mpd-serialize-configuration configuration)
  471. (mixed-text-file
  472. "mpd.conf"
  473. (serialize-configuration configuration mpd-configuration-fields)))
  474. (define (mpd-log-rotation config)
  475. (match-record config <mpd-configuration> (log-file)
  476. (log-rotation
  477. (files (list log-file))
  478. (post-rotate #~(begin
  479. (use-modules (gnu services herd))
  480. (with-shepherd-action 'mpd ('reopen) #f))))))
  481. (define (mpd-shepherd-service config)
  482. (match-record config <mpd-configuration> (user package shepherd-requirement
  483. log-file playlist-directory
  484. db-file state-file sticker-file
  485. environment-variables)
  486. (let ((config-file (mpd-serialize-configuration config))
  487. (username (user-account-name user)))
  488. (shepherd-service
  489. (documentation "Run the MPD (Music Player Daemon)")
  490. (requirement `(user-processes loopback ,@shepherd-requirement))
  491. (provision '(mpd))
  492. (start #~(begin
  493. (and=> #$(maybe-value log-file)
  494. (compose mkdir-p dirname))
  495. (let ((user (getpw #$username)))
  496. (for-each
  497. (lambda (x)
  498. (when (and x (not (file-exists? x)))
  499. (mkdir-p x)
  500. (chown x (passwd:uid user) (passwd:gid user))))
  501. (list #$(maybe-value playlist-directory)
  502. (and=> #$(maybe-value db-file) dirname)
  503. (and=> #$(maybe-value state-file) dirname)
  504. (and=> #$(maybe-value sticker-file) dirname))))
  505. (make-forkexec-constructor
  506. (list #$(file-append package "/bin/mpd")
  507. "--no-daemon"
  508. #$config-file)
  509. #:environment-variables '#$environment-variables)))
  510. (stop #~(make-kill-destructor))
  511. (actions
  512. (list (shepherd-configuration-action config-file)
  513. (shepherd-action
  514. (name 'reopen)
  515. (documentation "Re-open log files and flush caches.")
  516. (procedure
  517. #~(lambda (pid)
  518. (if pid
  519. (begin
  520. (kill pid SIGHUP)
  521. (format #t
  522. "Issued SIGHUP to Service MPD (PID ~a)."
  523. pid))
  524. (format #t "Service MPD is not running.")))))))))))
  525. (define (mpd-accounts config)
  526. (match-record config <mpd-configuration> (user group)
  527. ;; TODO: Deprecation code, to be removed.
  528. (let ((user (if (eq? (user-account-group user) %lazy-group)
  529. (%set-user-group user group)
  530. user)))
  531. (list user group))))
  532. (define mpd-service-type
  533. (service-type
  534. (name 'mpd)
  535. (description "Run the Music Player Daemon (MPD).")
  536. (extensions
  537. (list (service-extension shepherd-root-service-type
  538. (compose list mpd-shepherd-service))
  539. (service-extension account-service-type
  540. mpd-accounts)
  541. (service-extension rottlog-service-type
  542. (compose list mpd-log-rotation))))
  543. (default-value (mpd-configuration))))
  544. ;;;
  545. ;;; myMPD
  546. ;;;
  547. (define (string-or-symbol? x)
  548. (or (symbol? x) (string? x)))
  549. (define-configuration/no-serialization mympd-ip-acl
  550. (allow
  551. (list-of-strings '())
  552. "Allowed IP addresses.")
  553. (deny
  554. (list-of-strings '())
  555. "Disallowed IP addresses."))
  556. (define-maybe/no-serialization integer)
  557. (define-maybe/no-serialization mympd-ip-acl)
  558. (define %mympd-user
  559. (user-account
  560. (name "mympd")
  561. (group %lazy-group)
  562. (system? #t)
  563. (comment "myMPD user")
  564. (home-directory "/var/empty")
  565. (shell (file-append shadow "/sbin/nologin"))))
  566. (define %mympd-group
  567. (user-group
  568. (name "mympd")
  569. (system? #t)))
  570. ;;; TODO: Procedures for unsupported value types, to be removed.
  571. (define (mympd-user-sanitizer value)
  572. (cond ((user-account? value) value)
  573. ((string? value)
  574. (warning (G_ "string value for 'user' is not supported, use \
  575. user-account instead~%"))
  576. (user-account
  577. (inherit %mympd-user)
  578. (name value)
  579. ;; XXX: this is to be lazily substituted in (…-accounts)
  580. ;; with the value from 'group'.
  581. (group %lazy-group)))
  582. (else
  583. (configuration-field-error #f 'user value))))
  584. (define (mympd-group-sanitizer value)
  585. (cond ((user-group? value) value)
  586. ((string? value)
  587. (warning (G_ "string value for 'group' is not supported, use \
  588. user-group instead~%"))
  589. (user-group
  590. (inherit %mympd-group)
  591. (name value)))
  592. (else
  593. (configuration-field-error #f 'group value))))
  594. ;;;
  595. ;; XXX: The serialization procedures are insufficient since we require
  596. ;; access to multiple fields at once.
  597. ;; Fields marked with empty-serializer are never serialized and are
  598. ;; used for command-line arguments or by the service definition.
  599. (define-configuration/no-serialization mympd-configuration
  600. (package
  601. (file-like mympd)
  602. "The package object of the myMPD server."
  603. empty-serializer)
  604. (shepherd-requirement
  605. (list-of-symbol '())
  606. "This is a list of symbols naming Shepherd services that this service
  607. will depend on."
  608. empty-serializer)
  609. (user
  610. (user-account %mympd-user)
  611. "Owner of the @command{mympd} process."
  612. (sanitizer mympd-user-sanitizer)
  613. empty-serializer)
  614. (group
  615. (user-group %mympd-group)
  616. "Owner group of the @command{mympd} process."
  617. (sanitizer mympd-group-sanitizer)
  618. empty-serializer)
  619. (work-directory
  620. (string "/var/lib/mympd")
  621. "Where myMPD will store its data."
  622. empty-serializer)
  623. (cache-directory
  624. (string "/var/cache/mympd")
  625. "Where myMPD will store its cache."
  626. empty-serializer)
  627. (acl
  628. maybe-mympd-ip-acl
  629. "ACL to access the myMPD webserver.")
  630. (covercache-ttl
  631. (maybe-integer 31)
  632. "How long to keep cached covers, @code{0} disables cover caching.")
  633. (http?
  634. (boolean #t)
  635. "HTTP support.")
  636. (host
  637. (string "[::]")
  638. "Host name to listen on.")
  639. (port
  640. (maybe-port 80)
  641. "HTTP port to listen on.")
  642. (log-level
  643. (integer 5)
  644. "How much detail to include in logs, possible values: @code{0} to @code{7}.")
  645. (log-to
  646. (string-or-symbol "/var/log/mympd/log")
  647. "Where to send logs. By default, the service logs to
  648. @file{/var/log/mympd.log}. The alternative is @code{'syslog}, which
  649. sends output to the running syslog service under the @samp{daemon} facility."
  650. empty-serializer)
  651. (lualibs
  652. (maybe-string "all")
  653. "See
  654. @url{https://jcorporation.github.io/myMPD/scripting/#lua-standard-libraries}.")
  655. (uri
  656. maybe-string
  657. "Override URI to myMPD.
  658. See @url{https://github.com/jcorporation/myMPD/issues/950}.")
  659. (script-acl
  660. (maybe-mympd-ip-acl (mympd-ip-acl
  661. (allow '("127.0.0.1"))))
  662. "ACL to access the myMPD script backend.")
  663. (ssl?
  664. (boolean #f)
  665. "SSL/TLS support.")
  666. (ssl-port
  667. (maybe-port 443)
  668. "Port to listen for HTTPS.")
  669. (ssl-cert
  670. maybe-string
  671. "Path to PEM encoded X.509 SSL/TLS certificate (public key).")
  672. (ssl-key
  673. maybe-string
  674. "Path to PEM encoded SSL/TLS private key.")
  675. (pin-hash
  676. maybe-string
  677. "SHA-256 hashed pin used by myMPD to control settings access by
  678. prompting a pin from the user.")
  679. (save-caches?
  680. maybe-boolean
  681. "Whether to preserve caches between service restarts."))
  682. (define (mympd-serialize-configuration config)
  683. (define serialize-value
  684. (match-lambda
  685. ((? boolean? val) (if val "true" "false"))
  686. ((? integer? val) (number->string val))
  687. ((? mympd-ip-acl? val) (ip-acl-serialize-configuration val))
  688. ((? string? val) val)))
  689. (define (ip-acl-serialize-configuration config)
  690. (define (serialize-list-of-strings prefix lst)
  691. (map (cut format #f "~a~a" prefix <>) lst))
  692. (string-join
  693. (append
  694. (serialize-list-of-strings "+" (mympd-ip-acl-allow config))
  695. (serialize-list-of-strings "-" (mympd-ip-acl-deny config))) ","))
  696. ;; myMPD configuration fields are serialized as individual files under
  697. ;; <work-directory>/config/.
  698. (match-record config <mympd-configuration> (work-directory acl
  699. covercache-ttl http? host port
  700. log-level lualibs uri script-acl
  701. ssl? ssl-port ssl-cert ssl-key
  702. pin-hash save-caches?)
  703. (define (serialize-field filename value)
  704. (when (maybe-value-set? value)
  705. (list (format #f "~a/config/~a" work-directory filename)
  706. (mixed-text-file filename (serialize-value value)))))
  707. (let ((filename-to-field `(("acl" . ,acl)
  708. ("covercache_keep_days" . ,covercache-ttl)
  709. ("http" . ,http?)
  710. ("http_host" . ,host)
  711. ("http_port" . ,port)
  712. ("loglevel" . ,log-level)
  713. ("lualibs" . ,lualibs)
  714. ("mympd_uri" . ,uri)
  715. ("scriptacl" . ,script-acl)
  716. ("ssl" . ,ssl?)
  717. ("ssl_port" . ,ssl-port)
  718. ("ssl_cert" . ,ssl-cert)
  719. ("ssl_key" . ,ssl-key)
  720. ("pin_hash" . ,pin-hash)
  721. ("save_caches" . ,save-caches?))))
  722. (filter list?
  723. (generic-serialize-alist list serialize-field
  724. filename-to-field)))))
  725. (define (mympd-shepherd-service config)
  726. (match-record config <mympd-configuration> (package shepherd-requirement
  727. user work-directory
  728. cache-directory log-level log-to)
  729. (let ((log-level* (format #f "MYMPD_LOGLEVEL=~a" log-level))
  730. (username (user-account-name user)))
  731. (shepherd-service
  732. (documentation "Run the myMPD daemon.")
  733. (requirement `(loopback user-processes
  734. ,@(if (eq? log-to 'syslog)
  735. '(syslog)
  736. '())
  737. ,@shepherd-requirement))
  738. (provision '(mympd))
  739. (start #~(begin
  740. (let* ((pw (getpwnam #$username))
  741. (uid (passwd:uid pw))
  742. (gid (passwd:gid pw)))
  743. (for-each (lambda (dir)
  744. (mkdir-p dir)
  745. (chown dir uid gid))
  746. (list #$work-directory #$cache-directory)))
  747. (make-forkexec-constructor
  748. `(#$(file-append package "/bin/mympd")
  749. "--user" #$username
  750. #$@(if (eq? log-to 'syslog) '("--syslog") '())
  751. "--workdir" #$work-directory
  752. "--cachedir" #$cache-directory)
  753. #:environment-variables (list #$log-level*)
  754. #:log-file #$(if (string? log-to) log-to #f))))
  755. (stop #~(make-kill-destructor))))))
  756. (define (mympd-accounts config)
  757. (match-record config <mympd-configuration> (user group)
  758. ;; TODO: Deprecation code, to be removed.
  759. (let ((user (if (eq? (user-account-group user) %lazy-group)
  760. (%set-user-group user group)
  761. user)))
  762. (list user group))))
  763. (define (mympd-log-rotation config)
  764. (match-record config <mympd-configuration> (log-to)
  765. (if (string? log-to)
  766. (list (log-rotation
  767. (files (list log-to))))
  768. '())))
  769. (define mympd-service-type
  770. (service-type
  771. (name 'mympd)
  772. (extensions
  773. (list (service-extension shepherd-root-service-type
  774. (compose list mympd-shepherd-service))
  775. (service-extension account-service-type
  776. mympd-accounts)
  777. (service-extension special-files-service-type
  778. mympd-serialize-configuration)
  779. (service-extension rottlog-service-type
  780. mympd-log-rotation)))
  781. (description "Run myMPD, a frontend for MPD. (Music Player Daemon)")
  782. (default-value (mympd-configuration))))