| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 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 | |
| 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
|
| 83 | install 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 | |
| 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
|
| 119 | corresponds 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
|
| 123 | by the requirements of Luanti to 1) manually enable the mod and any of its
|
| 124 | dependent mods in the @file{world.rt} file of the world used and 2) to
|
| 125 | register the mod names and those of its dependents via a
|
| 126 | @samp{secure.trusted_mods} @code{game-configuration} directive. Consult the
|
| 127 | example 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
|
| 141 | created under the @file{/var/lib/luanti/.minetest/worlds/world} directory. If
|
| 142 | an absolute file name is provided, it is used directly. Otherwise, it is
|
| 143 | expected 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 | |
| 172 | |
| 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 |
| 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 | |
| 222 | |
| 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 | |
| 234 | |
| 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
|
| 285 | server.")))
|
| 286 |
|
| 287 |
|
| 288 | |
| 289 | |
| 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 | |
| 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))))
|