diff options
| author | Christopher Baines <mail@cbaines.net> | 2026-08-09 10:20:02 +0100 |
|---|---|---|
| committer | Christopher Baines <mail@cbaines.net> | 2026-08-22 20:03:01 +0100 |
| commit | 9ceab34e9bd42c878bfeab15c71687ab754e4cf4 (patch) | |
| tree | 5eb50180c5a7b577f461b2c89d777213308d2c2e | |
| parent | 239327961888f109b0a66bda099a5ac23ce9f9ef (diff) | |
gnu: services: audio: Add snapcast snapserver service.
This is just the server component, another service definition is needed for
the snapclient.
* gnu/services/audio.scm: (snapserver-stream, snapserver-stream?,
snapserver-stream-name, snapserver-stream-type, snapserver-stream-location,
snapserver-stream-codec, snapserver-stream-sampleformat,
snapserver-stream-chunk-ms, snapserver-stream-parameters,
snapserver-configuration, snapserver-configuration?,
snapserver-configuration-package, snapserver-configuration-user,
snapserver-configuration-group, snapserver-configuration-shepherd-requirement,
snapserver-configuration-log-file, snapserver-configuration-log-filter,
snapserver-configuration-data-directory, snapserver-configuration-mdns?,
snapserver-configuration-http?, snapserver-configuration-http-port,
snapserver-configuration-http-bind-address, snapserver-configuration-doc-root,
snapserver-configuration-tcp-control?,
snapserver-configuration-tcp-control-port,
snapserver-configuration-tcp-streaming?,
snapserver-configuration-tcp-streaming-port, snapserver-configuration-streams,
snapserver-configuration-codec, snapserver-configuration-sampleformat,
snapserver-configuration-buffer-ms, snapserver-configuration-chunk-ms,
snapserver-configuration-send-to-muted?,
snapserver-configuration-extra-content): New procedures.
(snapserver-service-type): New variable.
* doc/guix.texi (Audio Services): Document it.
Change-Id: I5dd2c15ff8c1b560abd481147312a8d6fc844776
| -rw-r--r-- | doc/guix.texi | 174 | ||||
| -rw-r--r-- | gnu/services/audio.scm | 361 |
2 files changed, 530 insertions, 5 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index 555fd082471..39544d9acff 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -456,7 +456,7 @@ Services | |||
| 456 | * Samba Services:: Samba services. | 456 | * Samba Services:: Samba services. |
| 457 | * Continuous Integration:: Cuirass and Laminar services. | 457 | * Continuous Integration:: Cuirass and Laminar services. |
| 458 | * Power Management Services:: Extending battery life, etc. | 458 | * Power Management Services:: Extending battery life, etc. |
| 459 | * Audio Services:: The MPD. | 459 | * Audio Services:: The MPD and Snapcast. |
| 460 | * Virtualization Services:: Virtualization services. | 460 | * Virtualization Services:: Virtualization services. |
| 461 | * Version Control Services:: Software version control servers. | 461 | * Version Control Services:: Software version control servers. |
| 462 | * Game Services:: Game servers. | 462 | * Game Services:: Game servers. |
| @@ -20622,7 +20622,7 @@ declaration. | |||
| 20622 | * Samba Services:: Samba services. | 20622 | * Samba Services:: Samba services. |
| 20623 | * Continuous Integration:: Cuirass and Laminar services. | 20623 | * Continuous Integration:: Cuirass and Laminar services. |
| 20624 | * Power Management Services:: Extending battery life, etc. | 20624 | * Power Management Services:: Extending battery life, etc. |
| 20625 | * Audio Services:: The MPD. | 20625 | * Audio Services:: The MPD and Snapcast. |
| 20626 | * Virtualization Services:: Virtualization services. | 20626 | * Virtualization Services:: Virtualization services. |
| 20627 | * Version Control Services:: Software version control servers. | 20627 | * Version Control Services:: Software version control servers. |
| 20628 | * Game Services:: Game servers. | 20628 | * Game Services:: Game servers. |
| @@ -41763,8 +41763,9 @@ The handler for the battattach event. | |||
| 41763 | @node Audio Services | 41763 | @node Audio Services |
| 41764 | @subsection Audio Services | 41764 | @subsection Audio Services |
| 41765 | 41765 | ||
| 41766 | The @code{(gnu services audio)} module provides a service to start MPD | 41766 | The @code{(gnu services audio)} module provides services to start MPD |
| 41767 | (the Music Player Daemon). | 41767 | (the Music Player Daemon) and Snapcast, a multi-room audio streaming |
| 41768 | system. | ||
| 41768 | 41769 | ||
| 41769 | @cindex mpd | 41770 | @cindex mpd |
| 41770 | @subsubheading Music Player Daemon | 41771 | @subsubheading Music Player Daemon |
| @@ -42167,6 +42168,171 @@ Disallowed IP addresses. | |||
| 42167 | @end deftp | 42168 | @end deftp |
| 42168 | @c %end of fragment | 42169 | @c %end of fragment |
| 42169 | 42170 | ||
| 42171 | @subsubheading Snapcast | ||
| 42172 | |||
| 42173 | @cindex Snapcast | ||
| 42174 | @cindex multi-room audio | ||
| 42175 | |||
| 42176 | @uref{https://github.com/badaix/snapcast, Snapcast} is a multi-room | ||
| 42177 | audio streaming system: @command{snapserver} receives audio from one or | ||
| 42178 | more sources and streams it to @command{snapclient} instances, which | ||
| 42179 | play it back in sync. | ||
| 42180 | |||
| 42181 | The following example runs @command{snapserver} with a single named pipe | ||
| 42182 | as its audio source, in addition to the default HTTP interface on port | ||
| 42183 | 1780 and the client-facing streaming interface on port 1704. | ||
| 42184 | |||
| 42185 | @lisp | ||
| 42186 | (service snapserver-service-type | ||
| 42187 | (snapserver-configuration | ||
| 42188 | (streams (list (snapserver-stream | ||
| 42189 | (name "mpd") | ||
| 42190 | (location "/run/snapserver/mpd")))))) | ||
| 42191 | @end lisp | ||
| 42192 | |||
| 42193 | @command{snapserver} creates the named pipes of its @code{pipe} sources | ||
| 42194 | when it starts, as the @samp{snapserver} user; make sure the program | ||
| 42195 | feeding such a pipe---the @code{fifo} output of MPD, for instance---is | ||
| 42196 | allowed to write to it. | ||
| 42197 | |||
| 42198 | @defvar snapserver-service-type | ||
| 42199 | The service type for @command{snapserver}. | ||
| 42200 | @end defvar | ||
| 42201 | |||
| 42202 | @c %start of fragment | ||
| 42203 | @deftp {Data Type} snapserver-configuration | ||
| 42204 | Available @code{snapserver-configuration} fields are: | ||
| 42205 | |||
| 42206 | @table @asis | ||
| 42207 | @item @code{package} (default: @code{snapcast}) (type: file-like) | ||
| 42208 | The Snapcast package to use. | ||
| 42209 | |||
| 42210 | @item @code{user} (default: @code{%snapserver-user}) (type: user-account) | ||
| 42211 | Owner of the @command{snapserver} process. | ||
| 42212 | |||
| 42213 | @item @code{group} (default: @code{%snapserver-group}) (type: user-group) | ||
| 42214 | Owner group of the @command{snapserver} process. | ||
| 42215 | |||
| 42216 | @item @code{shepherd-requirement} (default: @code{'()}) (type: list-of-symbols) | ||
| 42217 | This is a list of symbols naming Shepherd services that this service | ||
| 42218 | will depend on. | ||
| 42219 | |||
| 42220 | @item @code{log-file} (default: @code{"/var/log/snapserver.log"}) (type: string) | ||
| 42221 | File where @command{snapserver} writes its log. | ||
| 42222 | |||
| 42223 | @item @code{log-filter} (default: @code{"*:info"}) (type: string) | ||
| 42224 | Log filter, given as @code{@var{tag}:@var{level}} pairs separated by | ||
| 42225 | commas, where @var{tag} is @code{*} or a log tag and @var{level} is one | ||
| 42226 | of @code{trace}, @code{debug}, @code{info}, @code{notice}, | ||
| 42227 | @code{warning}, @code{error} or @code{fatal}. | ||
| 42228 | |||
| 42229 | @item @code{data-directory} (default: @code{"/var/lib/snapserver"}) (type: string) | ||
| 42230 | Directory where @command{snapserver} stores persistent data, such as | ||
| 42231 | client settings. | ||
| 42232 | |||
| 42233 | @item @code{mdns?} (default: @code{#t}) (type: boolean) | ||
| 42234 | Whether to publish the Snapcast services over mDNS. This requires | ||
| 42235 | @code{avahi-service-type} (@pxref{Networking Services}). | ||
| 42236 | |||
| 42237 | @item @code{http?} (default: @code{#t}) (type: boolean) | ||
| 42238 | Whether to enable the HTTP JSON-RPC interface, which also serves the | ||
| 42239 | Snapweb web interface. | ||
| 42240 | |||
| 42241 | @item @code{http-port} (default: @code{1780}) (type: integer) | ||
| 42242 | Port the HTTP server listens on. | ||
| 42243 | |||
| 42244 | @item @code{http-bind-address} (default: @code{"::"}) (type: string) | ||
| 42245 | Address the HTTP server listens on. | ||
| 42246 | |||
| 42247 | @item @code{doc-root} (type: maybe-file-like) | ||
| 42248 | Directory served by the HTTP server. It defaults to the Snapweb web | ||
| 42249 | interface bundled with the @code{package} field. | ||
| 42250 | |||
| 42251 | @item @code{tcp-control?} (default: @code{#t}) (type: boolean) | ||
| 42252 | Whether to enable the TCP JSON-RPC control interface. | ||
| 42253 | |||
| 42254 | @item @code{tcp-control-port} (default: @code{1705}) (type: integer) | ||
| 42255 | Port the TCP control interface listens on. | ||
| 42256 | |||
| 42257 | @item @code{tcp-streaming?} (default: @code{#t}) (type: boolean) | ||
| 42258 | Whether to enable TCP streaming, which is what Snapcast clients connect | ||
| 42259 | to. | ||
| 42260 | |||
| 42261 | @item @code{tcp-streaming-port} (default: @code{1704}) (type: integer) | ||
| 42262 | Port the TCP streaming interface listens on. | ||
| 42263 | |||
| 42264 | @item @code{streams} (type: list-of-snapserver-stream) | ||
| 42265 | List of @code{snapserver-stream} records describing the audio sources | ||
| 42266 | @command{snapserver} reads from. | ||
| 42267 | |||
| 42268 | @item @code{codec} (default: @code{"flac"}) (type: string) | ||
| 42269 | Default codec used to send audio to clients, given as | ||
| 42270 | @code{@var{codec}[:@var{options}]} where @var{codec} is one of | ||
| 42271 | @code{flac}, @code{ogg}, @code{opus} or @code{pcm}. | ||
| 42272 | |||
| 42273 | @item @code{sampleformat} (default: @code{"48000:16:2"}) (type: string) | ||
| 42274 | Default sample format of the streams, given as | ||
| 42275 | @code{@var{rate}:@var{bits}:@var{channels}}. | ||
| 42276 | |||
| 42277 | @item @code{buffer-ms} (default: @code{1000}) (type: integer) | ||
| 42278 | End-to-end latency, in milliseconds. | ||
| 42279 | |||
| 42280 | @item @code{chunk-ms} (default: @code{20}) (type: integer) | ||
| 42281 | Default duration, in milliseconds, of the audio chunks read from the | ||
| 42282 | sources. | ||
| 42283 | |||
| 42284 | @item @code{send-to-muted?} (default: @code{#f}) (type: boolean) | ||
| 42285 | Whether to send audio to muted clients. | ||
| 42286 | |||
| 42287 | @item @code{extra-content} (type: maybe-string) | ||
| 42288 | Content appended verbatim to @file{snapserver.conf}, for options that | ||
| 42289 | are not covered by the fields above, such as the @code{[ssl]} and | ||
| 42290 | @code{[authorization]} sections. | ||
| 42291 | |||
| 42292 | @end table | ||
| 42293 | @end deftp | ||
| 42294 | @c %end of fragment | ||
| 42295 | |||
| 42296 | @c %start of fragment | ||
| 42297 | @deftp {Data Type} snapserver-stream | ||
| 42298 | Available @code{snapserver-stream} fields are: | ||
| 42299 | |||
| 42300 | @table @asis | ||
| 42301 | @item @code{name} (default: @code{"default"}) (type: string) | ||
| 42302 | Name of the stream, as shown by Snapcast clients. | ||
| 42303 | |||
| 42304 | @item @code{type} (default: @code{pipe}) (type: snapserver-stream-type) | ||
| 42305 | Type of the stream source: one of @code{pipe}, @code{file}, | ||
| 42306 | @code{process}, @code{librespot}, @code{airplay}, @code{tcp}, | ||
| 42307 | @code{alsa} or @code{meta}. | ||
| 42308 | |||
| 42309 | @item @code{location} (default: @code{"/run/snapserver/snapfifo"}) (type: string) | ||
| 42310 | Location of the stream source: a file name for @code{pipe}, @code{file} | ||
| 42311 | and @code{process} streams, a host name for @code{tcp} streams, a device | ||
| 42312 | name for @code{alsa} streams, and a slash-separated list of stream names | ||
| 42313 | for @code{meta} streams. | ||
| 42314 | |||
| 42315 | @item @code{codec} (type: maybe-string) | ||
| 42316 | Codec to use for this stream, overriding the @code{codec} field of | ||
| 42317 | @code{snapserver-configuration}. | ||
| 42318 | |||
| 42319 | @item @code{sampleformat} (type: maybe-string) | ||
| 42320 | Sample format of this stream, given as | ||
| 42321 | @code{@var{rate}:@var{bits}:@var{channels}}, overriding the | ||
| 42322 | @code{sampleformat} field of @code{snapserver-configuration}. | ||
| 42323 | |||
| 42324 | @item @code{chunk-ms} (type: maybe-integer) | ||
| 42325 | Duration, in milliseconds, of the audio chunks read from this source, | ||
| 42326 | overriding the @code{chunk-ms} field of @code{snapserver-configuration}. | ||
| 42327 | |||
| 42328 | @item @code{parameters} (default: @code{'()}) (type: alist) | ||
| 42329 | Additional source parameters, given as an association list, for example | ||
| 42330 | @code{'(("controlscript" . "meta_mpd.py"))}. | ||
| 42331 | |||
| 42332 | @end table | ||
| 42333 | @end deftp | ||
| 42334 | @c %end of fragment | ||
| 42335 | |||
| 42170 | @node Virtualization Services | 42336 | @node Virtualization Services |
| 42171 | @subsection Virtualization Services | 42337 | @subsection Virtualization Services |
| 42172 | 42338 | ||
diff --git a/gnu/services/audio.scm b/gnu/services/audio.scm index 518f8f2c0fa..2981b89ed8c 100644 --- a/gnu/services/audio.scm +++ b/gnu/services/audio.scm | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #:use-module (gnu services admin) | 32 | #:use-module (gnu services admin) |
| 33 | #:use-module (gnu system shadow) | 33 | #:use-module (gnu system shadow) |
| 34 | #:use-module (gnu packages admin) | 34 | #:use-module (gnu packages admin) |
| 35 | #:use-module ((gnu packages audio) #:select (snapcast)) | ||
| 35 | #:use-module (gnu packages mpd) | 36 | #:use-module (gnu packages mpd) |
| 36 | #:use-module (guix records) | 37 | #:use-module (guix records) |
| 37 | #:use-module (ice-9 format) | 38 | #:use-module (ice-9 format) |
| @@ -123,7 +124,44 @@ | |||
| 123 | mympd-ip-acl | 124 | mympd-ip-acl |
| 124 | mympd-ip-acl? | 125 | mympd-ip-acl? |
| 125 | mympd-ip-acl-allow | 126 | mympd-ip-acl-allow |
| 126 | mympd-ip-acl-deny)) | 127 | mympd-ip-acl-deny |
| 128 | |||
| 129 | snapserver-stream | ||
| 130 | snapserver-stream? | ||
| 131 | snapserver-stream-name | ||
| 132 | snapserver-stream-type | ||
| 133 | snapserver-stream-location | ||
| 134 | snapserver-stream-codec | ||
| 135 | snapserver-stream-sampleformat | ||
| 136 | snapserver-stream-chunk-ms | ||
| 137 | snapserver-stream-parameters | ||
| 138 | |||
| 139 | snapserver-configuration | ||
| 140 | snapserver-configuration? | ||
| 141 | snapserver-configuration-package | ||
| 142 | snapserver-configuration-user | ||
| 143 | snapserver-configuration-group | ||
| 144 | snapserver-configuration-shepherd-requirement | ||
| 145 | snapserver-configuration-log-file | ||
| 146 | snapserver-configuration-log-filter | ||
| 147 | snapserver-configuration-data-directory | ||
| 148 | snapserver-configuration-mdns? | ||
| 149 | snapserver-configuration-http? | ||
| 150 | snapserver-configuration-http-port | ||
| 151 | snapserver-configuration-http-bind-address | ||
| 152 | snapserver-configuration-doc-root | ||
| 153 | snapserver-configuration-tcp-control? | ||
| 154 | snapserver-configuration-tcp-control-port | ||
| 155 | snapserver-configuration-tcp-streaming? | ||
| 156 | snapserver-configuration-tcp-streaming-port | ||
| 157 | snapserver-configuration-streams | ||
| 158 | snapserver-configuration-codec | ||
| 159 | snapserver-configuration-sampleformat | ||
| 160 | snapserver-configuration-buffer-ms | ||
| 161 | snapserver-configuration-chunk-ms | ||
| 162 | snapserver-configuration-send-to-muted? | ||
| 163 | snapserver-configuration-extra-content | ||
| 164 | snapserver-service-type)) | ||
| 127 | 165 | ||
| 128 | ;;; Commentary: | 166 | ;;; Commentary: |
| 129 | ;;; | 167 | ;;; |
| @@ -945,3 +983,324 @@ prompting a pin from the user.") | |||
| 945 | mympd-serialize-configuration))) | 983 | mympd-serialize-configuration))) |
| 946 | (description "Run myMPD, a frontend for MPD (Music Player Daemon).") | 984 | (description "Run myMPD, a frontend for MPD (Music Player Daemon).") |
| 947 | (default-value (mympd-configuration)))) | 985 | (default-value (mympd-configuration)))) |
| 986 | |||
| 987 | |||
| 988 | ;;; | ||
| 989 | ;;; Snapcast | ||
| 990 | ;;; | ||
| 991 | |||
| 992 | (define %snapserver-user | ||
| 993 | (user-account | ||
| 994 | (name "snapserver") | ||
| 995 | (group %lazy-group) | ||
| 996 | (system? #t) | ||
| 997 | (comment "Snapcast server user") | ||
| 998 | (home-directory "/var/lib/snapserver") | ||
| 999 | (shell (file-append shadow "/sbin/nologin")))) | ||
| 1000 | |||
| 1001 | (define %snapserver-group | ||
| 1002 | (user-group | ||
| 1003 | (name "snapserver") | ||
| 1004 | (system? #t))) | ||
| 1005 | |||
| 1006 | (define (snapserver-stream-type? value) | ||
| 1007 | (memq value '(pipe file process librespot airplay tcp alsa meta))) | ||
| 1008 | |||
| 1009 | (define-maybe/no-serialization file-like) | ||
| 1010 | |||
| 1011 | (define-configuration/no-serialization snapserver-stream | ||
| 1012 | (name | ||
| 1013 | (string "default") | ||
| 1014 | "Name of the stream, as shown by Snapcast clients.") | ||
| 1015 | |||
| 1016 | (type | ||
| 1017 | (snapserver-stream-type 'pipe) | ||
| 1018 | "Type of the stream source: one of @code{pipe}, @code{file}, | ||
| 1019 | @code{process}, @code{librespot}, @code{airplay}, @code{tcp}, @code{alsa} or | ||
| 1020 | @code{meta}.") | ||
| 1021 | |||
| 1022 | (location | ||
| 1023 | (string "/run/snapserver/snapfifo") | ||
| 1024 | "Location of the stream source: a file name for @code{pipe}, @code{file} | ||
| 1025 | and @code{process} streams, a host name for @code{tcp} streams, a device name | ||
| 1026 | for @code{alsa} streams, and a slash-separated list of stream names for | ||
| 1027 | @code{meta} streams.") | ||
| 1028 | |||
| 1029 | (codec | ||
| 1030 | maybe-string | ||
| 1031 | "Codec to use for this stream, overriding the @code{codec} field of | ||
| 1032 | @code{snapserver-configuration}.") | ||
| 1033 | |||
| 1034 | (sampleformat | ||
| 1035 | maybe-string | ||
| 1036 | "Sample format of this stream, given as | ||
| 1037 | @code{@var{rate}:@var{bits}:@var{channels}}, overriding the | ||
| 1038 | @code{sampleformat} field of @code{snapserver-configuration}.") | ||
| 1039 | |||
| 1040 | (chunk-ms | ||
| 1041 | maybe-integer | ||
| 1042 | "Duration, in milliseconds, of the audio chunks read from this source, | ||
| 1043 | overriding the @code{chunk-ms} field of @code{snapserver-configuration}.") | ||
| 1044 | |||
| 1045 | (parameters | ||
| 1046 | (alist '()) | ||
| 1047 | "Additional source parameters, given as an association list, for example | ||
| 1048 | @code{'((\"controlscript\" . \"meta_mpd.py\"))}.")) | ||
| 1049 | |||
| 1050 | (define (snapserver-stream->uri stream) | ||
| 1051 | (match-record stream <snapserver-stream> | ||
| 1052 | (name type location codec sampleformat chunk-ms parameters) | ||
| 1053 | (let ((query `(("name" . ,name) | ||
| 1054 | ,@(if (maybe-value-set? codec) | ||
| 1055 | `(("codec" . ,codec)) | ||
| 1056 | '()) | ||
| 1057 | ,@(if (maybe-value-set? sampleformat) | ||
| 1058 | `(("sampleformat" . ,sampleformat)) | ||
| 1059 | '()) | ||
| 1060 | ,@(if (maybe-value-set? chunk-ms) | ||
| 1061 | `(("chunk_ms" . ,chunk-ms)) | ||
| 1062 | '()) | ||
| 1063 | ,@parameters))) | ||
| 1064 | (simple-format #f "~A://~A?~A" type location | ||
| 1065 | (string-join | ||
| 1066 | (map (match-lambda | ||
| 1067 | ((key . value) | ||
| 1068 | (simple-format #f "~A=~A" key value))) | ||
| 1069 | query) | ||
| 1070 | "&"))))) | ||
| 1071 | |||
| 1072 | (define list-of-snapserver-stream? | ||
| 1073 | (list-of snapserver-stream?)) | ||
| 1074 | |||
| 1075 | (define-configuration/no-serialization snapserver-configuration | ||
| 1076 | (package | ||
| 1077 | (file-like snapcast) | ||
| 1078 | "The Snapcast package to use.") | ||
| 1079 | |||
| 1080 | (user | ||
| 1081 | (user-account %snapserver-user) | ||
| 1082 | "Owner of the @command{snapserver} process.") | ||
| 1083 | |||
| 1084 | (group | ||
| 1085 | (user-group %snapserver-group) | ||
| 1086 | "Owner group of the @command{snapserver} process.") | ||
| 1087 | |||
| 1088 | (shepherd-requirement | ||
| 1089 | (list-of-symbols '()) | ||
| 1090 | "This is a list of symbols naming Shepherd services that this service | ||
| 1091 | will depend on.") | ||
| 1092 | |||
| 1093 | (log-file | ||
| 1094 | (string "/var/log/snapserver.log") | ||
| 1095 | "File where @command{snapserver} writes its log.") | ||
| 1096 | |||
| 1097 | (log-filter | ||
| 1098 | (string "*:info") | ||
| 1099 | "Log filter, given as @code{@var{tag}:@var{level}} pairs separated by | ||
| 1100 | commas, where @var{tag} is @code{*} or a log tag and @var{level} is one of | ||
| 1101 | @code{trace}, @code{debug}, @code{info}, @code{notice}, @code{warning}, | ||
| 1102 | @code{error} or @code{fatal}.") | ||
| 1103 | |||
| 1104 | (data-directory | ||
| 1105 | (string "/var/lib/snapserver") | ||
| 1106 | "Directory where @command{snapserver} stores persistent data, such as | ||
| 1107 | client settings.") | ||
| 1108 | |||
| 1109 | (mdns? | ||
| 1110 | (boolean #t) | ||
| 1111 | "Whether to publish the Snapcast services over mDNS. This requires | ||
| 1112 | @code{avahi-service-type} (@pxref{Networking Services}).") | ||
| 1113 | |||
| 1114 | (http? | ||
| 1115 | (boolean #t) | ||
| 1116 | "Whether to enable the HTTP JSON-RPC interface, which also serves the | ||
| 1117 | Snapweb web interface.") | ||
| 1118 | |||
| 1119 | (http-port | ||
| 1120 | (integer 1780) | ||
| 1121 | "Port the HTTP server listens on.") | ||
| 1122 | |||
| 1123 | (http-bind-address | ||
| 1124 | (string "::") | ||
| 1125 | "Address the HTTP server listens on.") | ||
| 1126 | |||
| 1127 | (doc-root | ||
| 1128 | maybe-file-like | ||
| 1129 | "Directory served by the HTTP server. It defaults to the Snapweb web | ||
| 1130 | interface bundled with the @code{package} field.") | ||
| 1131 | |||
| 1132 | (tcp-control? | ||
| 1133 | (boolean #t) | ||
| 1134 | "Whether to enable the TCP JSON-RPC control interface.") | ||
| 1135 | |||
| 1136 | (tcp-control-port | ||
| 1137 | (integer 1705) | ||
| 1138 | "Port the TCP control interface listens on.") | ||
| 1139 | |||
| 1140 | (tcp-streaming? | ||
| 1141 | (boolean #t) | ||
| 1142 | "Whether to enable TCP streaming, which is what Snapcast clients connect | ||
| 1143 | to.") | ||
| 1144 | |||
| 1145 | (tcp-streaming-port | ||
| 1146 | (integer 1704) | ||
| 1147 | "Port the TCP streaming interface listens on.") | ||
| 1148 | |||
| 1149 | (streams | ||
| 1150 | (list-of-snapserver-stream (list (snapserver-stream))) | ||
| 1151 | "List of @code{snapserver-stream} records describing the audio sources | ||
| 1152 | @command{snapserver} reads from.") | ||
| 1153 | |||
| 1154 | (codec | ||
| 1155 | (string "flac") | ||
| 1156 | "Default codec used to send audio to clients, given as | ||
| 1157 | @code{@var{codec}[:@var{options}]} where @var{codec} is one of @code{flac}, | ||
| 1158 | @code{ogg}, @code{opus} or @code{pcm}.") | ||
| 1159 | |||
| 1160 | (sampleformat | ||
| 1161 | (string "48000:16:2") | ||
| 1162 | "Default sample format of the streams, given as | ||
| 1163 | @code{@var{rate}:@var{bits}:@var{channels}}.") | ||
| 1164 | |||
| 1165 | (buffer-ms | ||
| 1166 | (integer 1000) | ||
| 1167 | "End-to-end latency, in milliseconds.") | ||
| 1168 | |||
| 1169 | (chunk-ms | ||
| 1170 | (integer 20) | ||
| 1171 | "Default duration, in milliseconds, of the audio chunks read from the | ||
| 1172 | sources.") | ||
| 1173 | |||
| 1174 | (send-to-muted? | ||
| 1175 | (boolean #f) | ||
| 1176 | "Whether to send audio to muted clients.") | ||
| 1177 | |||
| 1178 | (extra-content | ||
| 1179 | maybe-string | ||
| 1180 | "Content appended verbatim to @file{snapserver.conf}, for options that are | ||
| 1181 | not covered by the fields above, such as the @code{[ssl]} and | ||
| 1182 | @code{[authorization]} sections.")) | ||
| 1183 | |||
| 1184 | (define (snapserver-config-file config) | ||
| 1185 | (define (boolean->string value) | ||
| 1186 | (if value "1" "0")) | ||
| 1187 | |||
| 1188 | (match-record config <snapserver-configuration> | ||
| 1189 | (package log-filter data-directory mdns? | ||
| 1190 | http? http-port http-bind-address doc-root | ||
| 1191 | tcp-control? tcp-control-port tcp-streaming? tcp-streaming-port | ||
| 1192 | streams codec sampleformat buffer-ms chunk-ms send-to-muted? | ||
| 1193 | extra-content) | ||
| 1194 | (apply mixed-text-file "snapserver.conf" | ||
| 1195 | `("[server]\n" | ||
| 1196 | "datadir = " ,data-directory "\n" | ||
| 1197 | "mdns_enabled = " ,(boolean->string mdns?) "\n" | ||
| 1198 | |||
| 1199 | "\n[http]\n" | ||
| 1200 | "enabled = " ,(boolean->string http?) "\n" | ||
| 1201 | "port = " ,(number->string http-port) "\n" | ||
| 1202 | "bind_to_address = " ,http-bind-address "\n" | ||
| 1203 | "doc_root = " ,(if (maybe-value-set? doc-root) | ||
| 1204 | doc-root | ||
| 1205 | (file-append package | ||
| 1206 | "/share/snapserver/snapweb")) | ||
| 1207 | "\n" | ||
| 1208 | |||
| 1209 | "\n[tcp-control]\n" | ||
| 1210 | "enabled = " ,(boolean->string tcp-control?) "\n" | ||
| 1211 | "port = " ,(number->string tcp-control-port) "\n" | ||
| 1212 | |||
| 1213 | "\n[tcp-streaming]\n" | ||
| 1214 | "enabled = " ,(boolean->string tcp-streaming?) "\n" | ||
| 1215 | "port = " ,(number->string tcp-streaming-port) "\n" | ||
| 1216 | |||
| 1217 | "\n[stream]\n" | ||
| 1218 | ;; The upstream defaults for these two point into /usr/share. | ||
| 1219 | "plugin_dir = " ,(file-append package | ||
| 1220 | "/share/snapserver/plug-ins") "\n" | ||
| 1221 | "sandbox_dir = " ,(file-append package | ||
| 1222 | "/share/snapserver/sandbox") "\n" | ||
| 1223 | ,@(map (lambda (stream) | ||
| 1224 | (string-append "source = " | ||
| 1225 | (snapserver-stream->uri stream) "\n")) | ||
| 1226 | streams) | ||
| 1227 | "codec = " ,codec "\n" | ||
| 1228 | "sampleformat = " ,sampleformat "\n" | ||
| 1229 | "buffer = " ,(number->string buffer-ms) "\n" | ||
| 1230 | "chunk_ms = " ,(number->string chunk-ms) "\n" | ||
| 1231 | "send_to_muted = " ,(boolean->string send-to-muted?) "\n" | ||
| 1232 | |||
| 1233 | "\n[logging]\n" | ||
| 1234 | ;; Log to standard error, which the Shepherd redirects to the | ||
| 1235 | ;; 'log-file' of the service. | ||
| 1236 | "sink = stderr\n" | ||
| 1237 | "filter = " ,log-filter "\n" | ||
| 1238 | ,@(if (maybe-value-set? extra-content) | ||
| 1239 | `("\n" ,extra-content "\n") | ||
| 1240 | '()))))) | ||
| 1241 | |||
| 1242 | (define (snapserver-shepherd-service config) | ||
| 1243 | (match-record config <snapserver-configuration> | ||
| 1244 | (package user group shepherd-requirement log-file data-directory | ||
| 1245 | mdns? streams) | ||
| 1246 | (let ((config-file (snapserver-config-file config)) | ||
| 1247 | (username (user-account-name user)) | ||
| 1248 | (groupname (user-group-name group)) | ||
| 1249 | ;; 'snapserver' creates the named pipes of its 'pipe' sources, but | ||
| 1250 | ;; not the directories holding them. | ||
| 1251 | (directories | ||
| 1252 | (delete-duplicates | ||
| 1253 | (cons* data-directory (dirname log-file) | ||
| 1254 | (filter-map (lambda (stream) | ||
| 1255 | (and (eq? 'pipe | ||
| 1256 | (snapserver-stream-type | ||
| 1257 | stream)) | ||
| 1258 | (dirname | ||
| 1259 | (snapserver-stream-location | ||
| 1260 | stream)))) | ||
| 1261 | streams))))) | ||
| 1262 | (shepherd-service | ||
| 1263 | (documentation "Run the Snapcast server.") | ||
| 1264 | (requirement `(user-processes loopback | ||
| 1265 | ,@(if mdns? '(avahi-daemon) '()) | ||
| 1266 | ,@shepherd-requirement)) | ||
| 1267 | (provision '(snapserver)) | ||
| 1268 | (start | ||
| 1269 | (with-imported-modules (source-module-closure | ||
| 1270 | '((gnu build activation))) | ||
| 1271 | #~(begin | ||
| 1272 | (use-modules (gnu build activation)) | ||
| 1273 | |||
| 1274 | (let ((user (getpw #$username))) | ||
| 1275 | (for-each (lambda (directory) | ||
| 1276 | (unless (file-exists? directory) | ||
| 1277 | (mkdir-p/perms directory user #o755))) | ||
| 1278 | '#$directories)) | ||
| 1279 | |||
| 1280 | (make-forkexec-constructor | ||
| 1281 | (list #$(file-append package "/bin/snapserver") | ||
| 1282 | "--config" #$config-file) | ||
| 1283 | #:user #$username | ||
| 1284 | #:group #$groupname | ||
| 1285 | #:log-file #$log-file)))) | ||
| 1286 | (stop #~(make-kill-destructor)) | ||
| 1287 | (actions (list (shepherd-configuration-action config-file))))))) | ||
| 1288 | |||
| 1289 | (define (snapserver-accounts config) | ||
| 1290 | (match-record config <snapserver-configuration> (user group) | ||
| 1291 | (list (if (eq? (user-account-group user) %lazy-group) | ||
| 1292 | (set-user-group user group) | ||
| 1293 | user) | ||
| 1294 | group))) | ||
| 1295 | |||
| 1296 | (define snapserver-service-type | ||
| 1297 | (service-type | ||
| 1298 | (name 'snapserver) | ||
| 1299 | (extensions | ||
| 1300 | (list (service-extension shepherd-root-service-type | ||
| 1301 | (compose list snapserver-shepherd-service)) | ||
| 1302 | (service-extension account-service-type | ||
| 1303 | snapserver-accounts))) | ||
| 1304 | (description "Run the Snapcast server, @command{snapserver}, which streams | ||
| 1305 | audio to Snapcast clients.") | ||
| 1306 | (default-value (snapserver-configuration)))) | ||
