projects/guix·blob java-team · refs

354 lines · 13.6 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2018 Arun Isaac <[email protected]>
3;;; Copyright © 2022 Ludovic Courtès <[email protected]>
4;;; Copyright © 2025 Maxim Cournoyer <[email protected]>
5;;;
6;;; This file is part of GNU Guix.
7;;;
8;;; GNU Guix is free software; you can redistribute it and/or modify it
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
13;;; GNU Guix is distributed in the hope that it will be useful, but
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20
21(define-module (gnu services games)
22 #:use-module (gnu services)
23 #:use-module (gnu services configuration)
24 #:use-module (gnu services shepherd)
25 #:use-module (gnu packages admin)
26 #:use-module (gnu packages games)
27 #:use-module (gnu packages luanti)
28 #:use-module ((gnu services base) #:select (udev-service-type))
29 #:use-module (gnu system shadow)
30 #:use-module ((gnu system file-systems) #:select (file-system-mapping))
31 #:use-module (gnu build linux-container)
32 #:use-module (guix build utils)
33 #:autoload (guix least-authority) (%default-preserved-environment-variables
34 least-authority-wrapper)
35 #:use-module (guix gexp)
36 #:use-module (guix modules)
37 #:use-module (guix packages)
38 #:use-module (guix records)
39 #:use-module (ice-9 match)
40 #:use-module (srfi srfi-1)
41 #:export (joycond-configuration
42 joycond-configuration?
43 joycond-service-type
44
45 luanti-configuration
46 luanti-configuration?
47 luanti-configuration-game-configuration
48 luanti-configuration-game
49 luanti-configuration-mods
50 luanti-configuration-log-file
51 luanti-configuration-luanti
52 luanti-configuration-port
53 luanti-configuration-verbose?
54 luanti-configuration-world
55 luanti-service-type
56
57 wesnothd-configuration
58 wesnothd-configuration?
59 wesnothd-service-type))
60
61;;;
62;;; Joycond
63;;;
64
65(define-configuration/no-serialization joycond-configuration
66 (package (package joycond) "The joycond package to use"))
67
68(define (joycond-shepherd-service config)
69 (let ((joycond (joycond-configuration-package config)))
70 (list (shepherd-service
71 (documentation "Run joycond.")
72 (provision '(joycond))
73 (requirement '(user-processes bluetooth))
74 (start #~(make-forkexec-constructor
75 (list #$(file-append joycond "/bin/joycond"))))
76 (stop #~(make-kill-destructor))))))
77
78(define joycond-service-type
79 (service-type
80 (name 'joycond)
81 (description
82 "Run @command{joycond} for pairing Nintendo joycons via Bluetooth and
83install udev rules required to use the controller as an unprivileged user.")
84 (extensions
85 (list (service-extension shepherd-root-service-type
86 joycond-shepherd-service)
87 (service-extension udev-service-type
88 (compose list joycond-configuration-package))))
89 (default-value (joycond-configuration))))
90
91
92;;;
93;;; Luanti.
94;;;
95
96(define list-of-file-likes?
97 (list-of file-like?))
98
99(define-maybe/no-serialization list-of-file-likes)
100
101(define-maybe/no-serialization file-like)
102
103(define-maybe/no-serialization string)
104
105(define (port? x)
106 (and (number? x)
107 (and (>= 0) (<= x 65535))))
108
109(define-configuration/no-serialization luanti-configuration
110 (luanti
111 (file-like luanti-server)
112 "The Luanti package to use.")
113 (game
114 (file-like luanti-mineclonia)
115 "The Luanti game package to serve.")
116 (game-configuration
117 maybe-file-like
118 "A configuration file to use for the selected Luanti game, which
119corresponds to the @file{minetest.conf} file.")
120 (mods
121 maybe-list-of-file-likes
122 "A list of Luanti mod packages to use. Note that using mods is complicated
123by the requirements of Luanti to 1) manually enable the mod and any of its
124dependent mods in the @file{world.rt} file of the world used and 2) to
125register the mod names and those of its dependents via a
126@samp{secure.trusted_mods} @code{game-configuration} directive. Consult the
127example below for more precise directions.")
128 (log-file
129 (maybe-string "/var/log/luanti.log")
130 "The log file to log to. To disable logging, set this to
131@code{%unset-value}.")
132 (verbose?
133 (boolean #f)
134 "Print more detailed information.")
135 (port
136 (port 30000)
137 "The UDP port the server should listen to.")
138 (world
139 maybe-string
140 "An existing Luanti world directory to serve. If omitted, a new world is
141created under the @file{/var/lib/luanti/.minetest/worlds/world} directory. If
142an absolute file name is provided, it is used directly. Otherwise, it is
143expected to be a directory under @file{/var/lib/luanti/.minetest/worlds/}."))
144
145(define %luanti-account
146 (list (user-group
147 (name "luanti")
148 (system? #t))
149 (user-account
150 (name "luanti")
151 (group "luanti")
152 (system? #t)
153 (comment "Luanti server user")
154 (home-directory "/var/lib/luanti"))))
155
156(define (luanti-activation config)
157 "Activation script for the Luanti server."
158 (match-record config <luanti-configuration> (world)
159 #~(begin
160 (use-modules (guix build utils)
161 (srfi srfi-34))
162
163 (define user (getpwnam "luanti"))
164 (define* (sanitize-permissions file #:optional (mode #o400))
165 (guard (c (#t #t))
166 (chown file (passwd:uid user) (passwd:gid user))
167 (chmod file mode)))
168
169 (mkdir-p/perms "/var/lib/luanti" (getpwnam "luanti") #o755)
170
171 ;; Sanitize the permissions of a provided pre-populated world
172 ;; directory.
173 (when #$(and (maybe-value-set? world)
174 (absolute-file-name? world))
175 (for-each sanitize-permissions
176 (find-files #$world #:directories? #t))))))
177
178(define (transitive-mods mods)
179 "Return the transitive list of mods in MODS, these included."
180 (append-map (lambda (m)
181 (if (package? m)
182 (cons m (map second ;drop label
183 (package-transitive-propagated-inputs m)))
184 (list m)))
185 (if (maybe-value-set? mods)
186 mods
187 '())))
188
189(define (luanti-wrapper config)
190 "Return a least-authority wrapper for 'luantiserver', based on CONFIG, a
191<luanti-configuration> object."
192 (match-record config <luanti-configuration>
193 (luanti game game-configuration log-file mods world)
194 (let ((mods (transitive-mods mods)))
195 (least-authority-wrapper
196 (file-append luanti "/bin/luantiserver")
197 #:name "luantiserver-pola-wrapper"
198 #:mappings
199 (let ((readable (filter maybe-value-set?
200 (append (list luanti game game-configuration)
201 mods)))
202 (writable (filter maybe-value-set?
203 (append (list "/var/lib/luanti" log-file)
204 (if (and (maybe-value-set? world)
205 (absolute-file-name? world))
206 (list world)
207 '())))))
208 (append (map (lambda (r)
209 (file-system-mapping
210 (source r)
211 (target source)))
212 readable)
213 (map (lambda (w)
214 (file-system-mapping
215 (source w)
216 (target source)
217 (writable? #t)))
218 writable)))
219 #:user "luanti"
220 #:group "luanti"
221 ;; XXX: The user namespace must be shared otherwise the UID is different
222 ;; in the container and Luanti fails to create its data directory.
223 #:namespaces (fold delq %namespaces '(user net))
224 #:preserved-environment-variables
225 (cons* "LUANTI_GAME_PATH" "LUANTI_MOD_PATH"
226 %default-preserved-environment-variables)))))
227
228(define (luanti-shepherd-service config)
229 "Return the <shepherd-service> object of Luanti."
230 (match-record config <luanti-configuration>
231 ( luanti game game-configuration log-file mods verbose?
232 port world)
233 ;; Some mods have dependencies on other mods; we need to ensure these gets
234 ;; added to the LUANTI_MOD_PATH as well.
235 (let ((mods (transitive-mods mods)))
236 (list (shepherd-service
237 (provision '(luanti))
238 (requirement '(user-processes))
239 (start #~(make-forkexec-constructor
240 (append (list #$(luanti-wrapper config)
241 "--port" (number->string #$port))
242 (if #$(maybe-value-set? game-configuration)
243 '("--config" #$game-configuration)
244 '())
245 (if #$verbose?
246 '("--verbose")
247 '())
248 (if #$(maybe-value-set? world)
249 (if (absolute-file-name? #$world)
250 '("--world" #$world)
251 '("--worldname" #$world))
252 '()))
253 #:environment-variables
254 (append
255 (list "HOME=/var/lib/luanti"
256 (string-append "LUANTI_GAME_PATH="
257 #$game "/share/luanti/games")
258 (string-append
259 "LUANTI_MOD_PATH="
260 (list->search-path-as-string
261 (search-path-as-list '("share/luanti/mods")
262 '#$mods)
263 ":"))))
264 #:log-file #$(and (maybe-value-set? log-file)
265 log-file)))
266 (stop #~(make-kill-destructor)))))))
267
268(define luanti-service-type
269 (service-type
270 (name 'luanti)
271 (extensions
272 (list (service-extension shepherd-root-service-type
273 luanti-shepherd-service)
274 (service-extension profile-service-type
275 (match-record-lambda <luanti-configuration>
276 (luanti game)
277 (list luanti game)))
278 (service-extension account-service-type
279 (const %luanti-account))
280 (service-extension activation-service-type
281 luanti-activation)))
282 (default-value (luanti-configuration))
283 (description
284 "Run @url{https://www.luanti.org/en/, Luanti}, the voxel game engine, as a
285server.")))
286
287
288;;;
289;;; The Battle for Wesnoth server
290;;;
291
292(define-record-type* <wesnothd-configuration>
293 wesnothd-configuration make-wesnothd-configuration wesnothd-configuration?
294 (package wesnothd-configuration-package
295 (default wesnoth-server))
296 (port wesnothd-configuration-port
297 (default 15000)))
298
299(define %wesnothd-accounts
300 (list (user-account
301 (name "wesnothd")
302 (group "wesnothd")
303 (system? #t)
304 (comment "Wesnoth daemon user")
305 (home-directory "/var/empty")
306 (shell (file-append shadow "/sbin/nologin")))
307 (user-group
308 (name "wesnothd")
309 (system? #t))))
310
311(define wesnothd-shepherd-service
312 (match-lambda
313 (($ <wesnothd-configuration> package port)
314 (let ((wesnothd (least-authority-wrapper
315 (file-append package "/bin/wesnothd")
316 #:name "wesnothd"
317 #:mappings (list (file-system-mapping
318 (source "/var/run/wesnothd")
319 (target source)
320 (writable? #t)))
321 #:namespaces (delq 'net %namespaces))))
322 (shepherd-service
323 (documentation "The Battle for Wesnoth server")
324 (provision '(wesnoth-daemon))
325 (requirement '(user-processes networking))
326 (start #~(make-forkexec-constructor
327 (list #$wesnothd "-p" #$(number->string port))
328 #:user "wesnothd" #:group "wesnothd"))
329 (stop #~(make-kill-destructor)))))))
330
331(define wesnothd-activation
332 (with-imported-modules '((guix build utils))
333 #~(begin
334 (use-modules (guix build utils))
335
336 (let* ((user (getpw "wesnothd"))
337 (directory "/var/run/wesnothd"))
338 ;; wesnothd creates a Unix-domain socket in DIRECTORY.
339 (mkdir-p directory)
340 (chown directory (passwd:uid user) (passwd:gid user))))))
341
342(define wesnothd-service-type
343 (service-type
344 (name 'wesnothd)
345 (description
346 "Run The Battle for Wesnoth server @command{wesnothd}.")
347 (extensions
348 (list (service-extension account-service-type
349 (const %wesnothd-accounts))
350 (service-extension activation-service-type
351 (const wesnothd-activation))
352 (service-extension shepherd-root-service-type
353 (compose list wesnothd-shepherd-service))))
354 (default-value (wesnothd-configuration))))