summaryrefslogtreecommitdiff
path: root/gnu/services
diff options
context:
space:
mode:
authorChristopher Baines <mail@cbaines.net>2026-08-09 10:20:02 +0100
committerChristopher Baines <mail@cbaines.net>2026-08-22 20:03:01 +0100
commit9ceab34e9bd42c878bfeab15c71687ab754e4cf4 (patch)
tree5eb50180c5a7b577f461b2c89d777213308d2c2e /gnu/services
parent239327961888f109b0a66bda099a5ac23ce9f9ef (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
Diffstat (limited to 'gnu/services')
-rw-r--r--gnu/services/audio.scm361
1 files changed, 360 insertions, 1 deletions
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}
1025and @code{process} streams, a host name for @code{tcp} streams, a device name
1026for @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,
1043overriding 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
1091will 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
1100commas, 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
1107client 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
1117Snapweb 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
1130interface 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
1143to.")
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
1172sources.")
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
1181not 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
1305audio to Snapcast clients.")
1306 (default-value (snapserver-configuration))))