mirror of
https://codeberg.org/guix/guix.git
synced 2026-01-25 03:55:08 -06:00
services: Add luanti-service-type.
* gnu/services/games.scm (luanti-configuration): New variable. (%luanti-account): Likewise. (luanti-activation): New procedure. (luanti-shepherd-service): Likewise. (luanti-service-type): New variable. * gnu/tests/games.scm: New file. Change-Id: I65a1dcf832fa8add9c9d278d82bab91ca3eef086 Reviewed-by: Liliana Marie Prikler <liliana.prikler@gmail.com>
This commit is contained in:
parent
1c1407fe79
commit
47af617b5c
5 changed files with 411 additions and 1 deletions
|
|
@ -170,6 +170,8 @@ gnu/packages/game-development\.scm @guix/games
|
|||
gnu/packages/luanti\.scm @guix/games
|
||||
gnu/packages/esolangs\.scm @guix/games
|
||||
gnu/packages/motti\.scm @guix/games
|
||||
gnu/services/games\.scm @guix/games
|
||||
gnu/tests/games\.scm @guix/games
|
||||
guix/build/luanti-build-system\.scm @guix/games
|
||||
|
||||
etc/teams/gnome @guix/gnome
|
||||
|
|
|
|||
|
|
@ -43209,6 +43209,91 @@ the @code{joycond-configuration} configuration), so that joycond
|
|||
controllers can be detected and used by an unprivileged user.
|
||||
@end defvar
|
||||
|
||||
@subsubheading Luanti service
|
||||
@cindex luanti
|
||||
@cindex voxel-based games
|
||||
@uref{https://www.luanti.org/en/, Luanti} is a voxel game engine that
|
||||
powers many games. This service is for hosting a Luanti server. The
|
||||
various options can be configured via the @code{luanti-configuration}
|
||||
record, documented below:
|
||||
|
||||
@c %start of fragment
|
||||
|
||||
@deftp {Data Type} luanti-configuration
|
||||
Available @code{luanti-configuration} fields are:
|
||||
|
||||
@table @asis
|
||||
@item @code{luanti} (default: @code{luanti-server}) (type: file-like)
|
||||
The Luanti package to use.
|
||||
|
||||
@item @code{game} (default: @code{luanti-mineclonia}) (type: file-like)
|
||||
The Luanti game package to serve.
|
||||
|
||||
@item @code{game-configuration} (type: maybe-file-like)
|
||||
A configuration file to use for the selected Luanti game, which
|
||||
corresponds to the @file{minetest.conf} file.
|
||||
|
||||
@item @code{mods} (type: maybe-list-of-file-likes)
|
||||
A list of Luanti mod packages to use. Note that using mods is
|
||||
complicated by the requirements of Luanti to 1) manually enable the mod
|
||||
and any of its dependent mods in the @file{world.rt} file of the world
|
||||
used and 2) to register the mod names and those of its dependents via a
|
||||
@samp{secure.trusted_mods} @code{game-configuration} directive. Consult
|
||||
the example below for more precise directions.
|
||||
|
||||
@item @code{log-file} (default: @code{"/var/log/luanti.log"}) (type: maybe-string)
|
||||
The log file to log to. To disable logging, set this to
|
||||
@code{%unset-value}.
|
||||
|
||||
@item @code{verbose?} (default: @code{#f}) (type: boolean)
|
||||
Print more detailed information.
|
||||
|
||||
@item @code{port} (default: @code{30000}) (type: port)
|
||||
The UDP port the server should listen to.
|
||||
|
||||
@item @code{world} (type: maybe-string)
|
||||
An existing Luanti world directory to serve. If omitted, a new world is
|
||||
created under the @file{/var/lib/luanti/.minetest/worlds/world}
|
||||
directory. If an absolute file name is provided, it is used directly.
|
||||
Otherwise, it is expected to be a directory under
|
||||
@file{/var/lib/luanti/.minetest/worlds/}.
|
||||
|
||||
@end table
|
||||
|
||||
@end deftp
|
||||
|
||||
|
||||
@c %end of fragment
|
||||
|
||||
Here's the simplest example of a Luanti server, which in its default
|
||||
configuration serves the @code{luanti-mineclonia} game.
|
||||
|
||||
@lisp
|
||||
(service luanti-service-type)
|
||||
@end lisp
|
||||
|
||||
Here's a slightly more elaborate one, which adds the
|
||||
@code{luanti-whitelist} mod. Embedded are comments explaining extra
|
||||
needed steps when using mods. Failing to do these steps will cause the
|
||||
service to fail to start.
|
||||
|
||||
@lisp
|
||||
(service luanti-service-type
|
||||
(luanti-configuration
|
||||
(game luanti-mineclonia)
|
||||
(game-configuration
|
||||
(plain-file
|
||||
"minetest.conf"
|
||||
;; lib_chatcmdbuilder is a dependency of the whitelist mod
|
||||
"secure.trusted_mods = whitelist,lib_chatcmdbuilder\n"))
|
||||
;; The
|
||||
;; '/var/lib/luanti/.minetest/worlds/world/world.mt'
|
||||
;; file needs to be hand-edited to add:
|
||||
;; load_mod_whitelist = true
|
||||
;; load_mod_lib_chatcmdbuilder = true
|
||||
(mods (list luanti-whitelist))))
|
||||
@end lisp
|
||||
|
||||
@subsubheading The Battle for Wesnoth Service
|
||||
@cindex wesnothd
|
||||
@uref{https://wesnoth.org, The Battle for Wesnoth} is a fantasy, turn
|
||||
|
|
|
|||
|
|
@ -666,6 +666,8 @@ ecosystem."
|
|||
"gnu/packages/luanti.scm"
|
||||
"gnu/packages/esolangs.scm" ; granted, rather niche
|
||||
"gnu/packages/motti.scm"
|
||||
"gnu/services/games.scm"
|
||||
"gnu/tests/games.scm"
|
||||
"guix/build/luanti-build-system.scm")))
|
||||
|
||||
(define-team gnome
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
;;; GNU Guix --- Functional package management for GNU
|
||||
;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net>
|
||||
;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
|
||||
;;; Copyright © 2025 Maxim Cournoyer <maxim@guixotic.coop>
|
||||
;;;
|
||||
;;; This file is part of GNU Guix.
|
||||
;;;
|
||||
|
|
@ -23,20 +24,36 @@
|
|||
#:use-module (gnu services shepherd)
|
||||
#:use-module (gnu packages admin)
|
||||
#:use-module (gnu packages games)
|
||||
#:use-module (gnu packages luanti)
|
||||
#:use-module ((gnu services base) #:select (udev-service-type))
|
||||
#:use-module (gnu system shadow)
|
||||
#:use-module ((gnu system file-systems) #:select (file-system-mapping))
|
||||
#:use-module (gnu build linux-container)
|
||||
#:autoload (guix least-authority) (least-authority-wrapper)
|
||||
#:use-module (guix build utils)
|
||||
#:autoload (guix least-authority) (%default-preserved-environment-variables
|
||||
least-authority-wrapper)
|
||||
#:use-module (guix gexp)
|
||||
#:use-module (guix modules)
|
||||
#:use-module (guix packages)
|
||||
#:use-module (guix records)
|
||||
#:use-module (ice-9 match)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:export (joycond-configuration
|
||||
joycond-configuration?
|
||||
joycond-service-type
|
||||
|
||||
luanti-configuration
|
||||
luanti-configuration?
|
||||
luanti-configuration-game-configuration
|
||||
luanti-configuration-game
|
||||
luanti-configuration-mods
|
||||
luanti-configuration-log-file
|
||||
luanti-configuration-luanti
|
||||
luanti-configuration-port
|
||||
luanti-configuration-verbose?
|
||||
luanti-configuration-world
|
||||
luanti-service-type
|
||||
|
||||
wesnothd-configuration
|
||||
wesnothd-configuration?
|
||||
wesnothd-service-type))
|
||||
|
|
@ -71,6 +88,202 @@ install udev rules required to use the controller as an unprivileged user.")
|
|||
(compose list joycond-configuration-package))))
|
||||
(default-value (joycond-configuration))))
|
||||
|
||||
|
||||
;;;
|
||||
;;; Luanti.
|
||||
;;;
|
||||
|
||||
(define list-of-file-likes?
|
||||
(list-of file-like?))
|
||||
|
||||
(define-maybe/no-serialization list-of-file-likes)
|
||||
|
||||
(define-maybe/no-serialization file-like)
|
||||
|
||||
(define-maybe/no-serialization string)
|
||||
|
||||
(define (port? x)
|
||||
(and (number? x)
|
||||
(and (>= 0) (<= x 65535))))
|
||||
|
||||
(define-configuration/no-serialization luanti-configuration
|
||||
(luanti
|
||||
(file-like luanti-server)
|
||||
"The Luanti package to use.")
|
||||
(game
|
||||
(file-like luanti-mineclonia)
|
||||
"The Luanti game package to serve.")
|
||||
(game-configuration
|
||||
maybe-file-like
|
||||
"A configuration file to use for the selected Luanti game, which
|
||||
corresponds to the @file{minetest.conf} file.")
|
||||
(mods
|
||||
maybe-list-of-file-likes
|
||||
"A list of Luanti mod packages to use. Note that using mods is complicated
|
||||
by the requirements of Luanti to 1) manually enable the mod and any of its
|
||||
dependent mods in the @file{world.rt} file of the world used and 2) to
|
||||
register the mod names and those of its dependents via a
|
||||
@samp{secure.trusted_mods} @code{game-configuration} directive. Consult the
|
||||
example below for more precise directions.")
|
||||
(log-file
|
||||
(maybe-string "/var/log/luanti.log")
|
||||
"The log file to log to. To disable logging, set this to
|
||||
@code{%unset-value}.")
|
||||
(verbose?
|
||||
(boolean #f)
|
||||
"Print more detailed information.")
|
||||
(port
|
||||
(port 30000)
|
||||
"The UDP port the server should listen to.")
|
||||
(world
|
||||
maybe-string
|
||||
"An existing Luanti world directory to serve. If omitted, a new world is
|
||||
created under the @file{/var/lib/luanti/.minetest/worlds/world} directory. If
|
||||
an absolute file name is provided, it is used directly. Otherwise, it is
|
||||
expected to be a directory under @file{/var/lib/luanti/.minetest/worlds/}."))
|
||||
|
||||
(define %luanti-account
|
||||
(list (user-group
|
||||
(name "luanti")
|
||||
(system? #t))
|
||||
(user-account
|
||||
(name "luanti")
|
||||
(group "luanti")
|
||||
(system? #t)
|
||||
(comment "Luanti server user")
|
||||
(home-directory "/var/lib/luanti"))))
|
||||
|
||||
(define (luanti-activation config)
|
||||
"Activation script for the Luanti server."
|
||||
(match-record config <luanti-configuration> (world)
|
||||
#~(begin
|
||||
(use-modules (guix build utils)
|
||||
(srfi srfi-34))
|
||||
|
||||
(define user (getpwnam "luanti"))
|
||||
(define* (sanitize-permissions file #:optional (mode #o400))
|
||||
(guard (c (#t #t))
|
||||
(chown file (passwd:uid user) (passwd:gid user))
|
||||
(chmod file mode)))
|
||||
|
||||
(mkdir-p/perms "/var/lib/luanti" (getpwnam "luanti") #o755)
|
||||
|
||||
;; Sanitize the permissions of a provided pre-populated world
|
||||
;; directory.
|
||||
(when #$(and (maybe-value-set? world)
|
||||
(absolute-file-name? world))
|
||||
(for-each sanitize-permissions
|
||||
(find-files #$world #:directories? #t))))))
|
||||
|
||||
(define (transitive-mods mods)
|
||||
"Return the transitive list of mods in MODS, these included."
|
||||
(append-map (lambda (m)
|
||||
(if (package? m)
|
||||
(cons m (map second ;drop label
|
||||
(package-transitive-propagated-inputs m)))
|
||||
(list m)))
|
||||
(if (maybe-value-set? mods)
|
||||
mods
|
||||
'())))
|
||||
|
||||
(define (luanti-wrapper config)
|
||||
"Return a least-authority wrapper for 'luantiserver', based on CONFIG, a
|
||||
<luanti-configuration> object."
|
||||
(match-record config <luanti-configuration>
|
||||
(luanti game game-configuration log-file mods world)
|
||||
(let ((mods (transitive-mods mods)))
|
||||
(least-authority-wrapper
|
||||
(file-append luanti "/bin/luantiserver")
|
||||
#:name "luantiserver-pola-wrapper"
|
||||
#:mappings
|
||||
(let ((readable (filter maybe-value-set?
|
||||
(append (list luanti game game-configuration)
|
||||
mods)))
|
||||
(writable (filter maybe-value-set?
|
||||
(append (list "/var/lib/luanti" log-file)
|
||||
(if (and (maybe-value-set? world)
|
||||
(absolute-file-name? world))
|
||||
(list world)
|
||||
'())))))
|
||||
(append (map (lambda (r)
|
||||
(file-system-mapping
|
||||
(source r)
|
||||
(target source)))
|
||||
readable)
|
||||
(map (lambda (w)
|
||||
(file-system-mapping
|
||||
(source w)
|
||||
(target source)
|
||||
(writable? #t)))
|
||||
writable)))
|
||||
#:user "luanti"
|
||||
#:group "luanti"
|
||||
;; XXX: The user namespace must be shared otherwise the UID is different
|
||||
;; in the container and Luanti fails to create its data directory.
|
||||
#:namespaces (fold delq %namespaces '(user net))
|
||||
#:preserved-environment-variables
|
||||
(cons* "LUANTI_GAME_PATH" "LUANTI_MOD_PATH"
|
||||
%default-preserved-environment-variables)))))
|
||||
|
||||
(define (luanti-shepherd-service config)
|
||||
"Return the <shepherd-service> object of Luanti."
|
||||
(match-record config <luanti-configuration>
|
||||
( luanti game game-configuration log-file mods verbose?
|
||||
port world)
|
||||
;; Some mods have dependencies on other mods; we need to ensure these gets
|
||||
;; added to the LUANTI_MOD_PATH as well.
|
||||
(let ((mods (transitive-mods mods)))
|
||||
(list (shepherd-service
|
||||
(provision '(luanti))
|
||||
(requirement '(user-processes))
|
||||
(start #~(make-forkexec-constructor
|
||||
(append (list #$(luanti-wrapper config)
|
||||
"--port" (number->string #$port))
|
||||
(if #$(maybe-value-set? game-configuration)
|
||||
'("--config" #$game-configuration)
|
||||
'())
|
||||
(if #$verbose?
|
||||
'("--verbose")
|
||||
'())
|
||||
(if #$(maybe-value-set? world)
|
||||
(if (absolute-file-name? #$world)
|
||||
'("--world" #$world)
|
||||
'("--worldname" #$world))
|
||||
'()))
|
||||
#:environment-variables
|
||||
(append
|
||||
(list "HOME=/var/lib/luanti"
|
||||
(string-append "LUANTI_GAME_PATH="
|
||||
#$game "/share/luanti/games")
|
||||
(string-append
|
||||
"LUANTI_MOD_PATH="
|
||||
(list->search-path-as-string
|
||||
(search-path-as-list '("share/luanti/mods")
|
||||
'#$mods)
|
||||
":"))))
|
||||
#:log-file #$(and (maybe-value-set? log-file)
|
||||
log-file)))
|
||||
(stop #~(make-kill-destructor)))))))
|
||||
|
||||
(define luanti-service-type
|
||||
(service-type
|
||||
(name 'luanti)
|
||||
(extensions
|
||||
(list (service-extension shepherd-root-service-type
|
||||
luanti-shepherd-service)
|
||||
(service-extension profile-service-type
|
||||
(match-record-lambda <luanti-configuration>
|
||||
(luanti game)
|
||||
(list luanti game)))
|
||||
(service-extension account-service-type
|
||||
(const %luanti-account))
|
||||
(service-extension activation-service-type
|
||||
luanti-activation)))
|
||||
(default-value (luanti-configuration))
|
||||
(description
|
||||
"Run @url{https://www.luanti.org/en/, Luanti}, the voxel game engine, as a
|
||||
server.")))
|
||||
|
||||
|
||||
;;;
|
||||
;;; The Battle for Wesnoth server
|
||||
|
|
|
|||
108
gnu/tests/games.scm
Normal file
108
gnu/tests/games.scm
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
;;; GNU Guix --- Functional package management for GNU
|
||||
;;; Copyright © 2025 Maxim Cournoyer <maxim@guixotic.coop>
|
||||
;;;
|
||||
;;; This file is part of GNU Guix.
|
||||
;;;
|
||||
;;; GNU Guix is free software; you can redistribute it and/or modify it
|
||||
;;; under the terms of the GNU General Public License as published by
|
||||
;;; the Free Software Foundation; either version 3 of the License, or (at
|
||||
;;; your option) any later version.
|
||||
;;;
|
||||
;;; GNU Guix is distributed in the hope that it will be useful, but
|
||||
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;;; GNU General Public License for more details.
|
||||
;;;
|
||||
;;; You should have received a copy of the GNU General Public License
|
||||
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(define-module (gnu tests games)
|
||||
#:use-module (gnu packages luanti)
|
||||
#:use-module (gnu tests)
|
||||
#:use-module (gnu services)
|
||||
#:use-module (gnu services games)
|
||||
#:use-module (gnu system)
|
||||
#:use-module (gnu system vm)
|
||||
#:use-module (guix gexp)
|
||||
#:use-module (guix modules)
|
||||
#:export (%test-luanti))
|
||||
|
||||
(define (run-luanti-test name config)
|
||||
"Run a test of an OS running LUANTI-SERVICE."
|
||||
(define os
|
||||
(marionette-operating-system
|
||||
(simple-operating-system
|
||||
(service luanti-service-type config))
|
||||
#:imported-modules '((gnu build dbus-service)
|
||||
(gnu services herd))))
|
||||
|
||||
(define vm (virtual-machine
|
||||
(operating-system os)
|
||||
(memory-size 1024)))
|
||||
|
||||
(define test
|
||||
(with-imported-modules (source-module-closure
|
||||
'((gnu build marionette)))
|
||||
#~(begin
|
||||
(use-modules (gnu build marionette)
|
||||
(srfi srfi-64))
|
||||
|
||||
(define marionette
|
||||
(make-marionette (list #$vm)))
|
||||
|
||||
(test-runner-current (system-test-runner #$output))
|
||||
(test-begin "luanti")
|
||||
|
||||
(test-assert "luanti service can be stopped"
|
||||
(marionette-eval
|
||||
'(begin
|
||||
(use-modules (gnu services herd))
|
||||
(stop-service 'luanti))
|
||||
marionette))
|
||||
|
||||
(test-assert "luanti service can be started"
|
||||
(marionette-eval
|
||||
'(begin
|
||||
(use-modules (gnu services herd))
|
||||
(start-service 'luanti))
|
||||
marionette))
|
||||
|
||||
(test-assert "luanti server is responding on configured port"
|
||||
;; This is based on the Python script example in doc/protocol.txt.
|
||||
(marionette-eval
|
||||
`(begin
|
||||
(use-modules ((gnu build dbus-service) #:select (with-retries))
|
||||
(gnu services herd)
|
||||
(ice-9 match)
|
||||
(rnrs bytevectors)
|
||||
(rnrs bytevectors gnu))
|
||||
|
||||
(define sock (socket PF_INET SOCK_DGRAM 0))
|
||||
(define addr (make-socket-address AF_INET INADDR_LOOPBACK
|
||||
,#$(luanti-configuration-port
|
||||
config)))
|
||||
(define probe #vu8(#x4f #x45 #x74 #x03 #x00 #x00 #x00 #x01))
|
||||
(define buf (make-bytevector 1000))
|
||||
|
||||
(with-retries 25 1
|
||||
(sendto sock probe addr)
|
||||
(match (select (list sock) '() '() 2) ;limit time to block
|
||||
(((sock) _ _)
|
||||
(match (recvfrom! sock buf)
|
||||
((byte-count . _)
|
||||
(and (>= (pk 'byte-count byte-count) 14)
|
||||
(pk 'peer-id (bytevector-slice buf 12 2)))))))))
|
||||
marionette))
|
||||
|
||||
(test-end))))
|
||||
|
||||
(gexp->derivation name test))
|
||||
|
||||
(define %test-luanti
|
||||
(system-test
|
||||
(name "luanti")
|
||||
(description "Connect to a running Luanti server.")
|
||||
(value (run-luanti-test name (luanti-configuration
|
||||
(game luanti-mineclonia)
|
||||
;; To test some extra code paths.
|
||||
(mods (list luanti-whitelist)))))))
|
||||
Loading…
Add table
Reference in a new issue