games.scm (13771B)
1 (define-module (mt services games) 2 #:use-module (gnu services) 3 #:use-module (gnu services configuration) 4 #:use-module (gnu services shepherd) 5 #:use-module (gnu packages admin) 6 #:use-module (gnu packages java) 7 #:use-module (gnu system shadow) 8 #:use-module (guix gexp) 9 #:use-module (guix modules) 10 #:use-module (guix packages) 11 #:use-module (guix records) 12 #:use-module (guix download) 13 #:use-module (guix build-system copy) 14 #:use-module (mt utils) 15 #:use-module (mt packages games) 16 #:use-module (ice-9 match) 17 #:export (bta-configuration 18 bta-configuration? 19 bta-service-type 20 fabric-jar 21 %default-fabric-jar 22 modrinth-mod 23 minecraft-configuration 24 minecraft-configuration? 25 minecraft-service-type)) 26 27 ;;; 28 ;;; Better Than Adventure! 29 ;;; 30 31 ;; TODO: Add justauth support 32 33 (define %bta-account 34 (list (user-account 35 (name "bta") 36 (group "bta") 37 (system? #t) 38 (comment "BTA server user") 39 (home-directory "/var/lib/bta") 40 (shell (file-append shadow "/sbin/nologin"))) 41 (user-group 42 (name "bta") 43 (system? #t)))) 44 45 ;; TODO: Review. Is copy-build-system / package really necessary? 46 (define bta-fabric-server 47 (package 48 (name "bta-fabric-server") 49 (version "7.3_04") 50 (build-system copy-build-system) 51 (source 52 (origin 53 (method url-fetch/zipbomb) 54 (uri (string-append 55 "https://github.com/Turnip-Labs/bta-fabric-instance-repo/releases/download/v" 56 version "/bta_fabric_server_" version ".zip")) 57 (sha256 58 (base32 59 "18170krqhg61zlsvh7z9c8932qwa3waxalhh7lqdjj0qyp4lc4gr")))) 60 (home-page #f) 61 (synopsis #f) 62 (description #f) 63 (license #f))) 64 65 (define-record-type* <bta-configuration> 66 bta-configuration 67 make-bta-configuration 68 bta-configuration? 69 (jdk bta-configuration-jdk 70 (default openjdk17)) 71 (memory bta-configuration-memory 72 (default 4)) 73 (properties bta-configuration-properties 74 (default '())) 75 (ops bta-configuration-ops 76 (default '())) 77 (mods bta-configuration-mods 78 (default '())) 79 (home bta-configuration-home 80 (default "/var/lib/bta")) 81 (log-file bta-configuration-log-file 82 (default "/var/log/bta.log"))) 83 84 (define bta-activation 85 (match-lambda 86 (($ <bta-configuration> jdk memory properties ops mods home log-file) 87 (with-imported-modules '((guix build utils)) 88 #~(begin 89 (use-modules (guix build utils) 90 (ice-9 match)) 91 92 (let ((user (getpwnam "bta")) 93 (server-launcher-properties 94 #$(string-append home "/fabric-server-launcher.properties")) 95 (server-properties #$(string-append home "/server.properties")) 96 (ops-txt #$(string-append home "/ops.txt")) 97 (mod-dir #$(string-append home "/mods/"))) 98 (match (primitive-fork) 99 (0 100 (dynamic-wind 101 (const #t) 102 (lambda () 103 ;; Switch to the bta user 104 (setgid (passwd:gid user)) 105 (setuid (passwd:uid user)) 106 107 ;; Write to fabric-server-launcher.properties 108 (call-with-output-file 109 server-launcher-properties 110 (lambda (port) 111 (format port "serverJar=~a\n" 112 #$(file-append bta-fabric-server 113 "/server.jar")))) 114 115 ;; Write to server.properties 116 (call-with-output-file 117 server-properties 118 (lambda (port) 119 (for-each 120 (lambda (pair) 121 (format port "~a=~a\n" (car pair) (cdr pair))) 122 '#$properties))) 123 124 ;; Initialize ops.txt 125 (unless (file-exists? ops-txt) 126 (call-with-output-file 127 ops-txt 128 (lambda (port) 129 (for-each 130 (lambda (uuid) 131 (format port "~a\n" uuid)) 132 '#$ops)))) 133 134 ;; Link all mods into the mods folder 135 (if (file-exists? mod-dir) 136 (delete-file-recursively mod-dir)) 137 (mkdir mod-dir) 138 (for-each 139 (lambda (mod) 140 (symlink mod (string-append mod-dir (basename mod)))) 141 '#$mods) 142 143 ;; Return to main thread 144 (primitive-exit 0)) 145 (lambda () 146 (primitive-exit 1)))) 147 (pid (waitpid pid))))))))) 148 149 (define bta-shepherd-service 150 (match-lambda 151 (($ <bta-configuration> jdk memory properties ops mods home log-file) 152 (shepherd-service 153 (documentation "BTA server") 154 (provision '(bta)) 155 (requirement '(user-processes networking)) 156 (start #~(make-forkexec-constructor 157 (list #$(file-append jdk "/bin/java") 158 "-Dfabric.runtimeMappingNamespace=official" 159 #$(format #f "-Xmx~aG" memory) 160 "-jar" #$(file-append bta-fabric-server 161 "/fabric-server-launch.jar") 162 "nogui") 163 #:user "bta" 164 #:group "bta" 165 #:directory #$home 166 #:log-file #$log-file)) 167 (stop #~(make-kill-destructor SIGINT)))))) 168 169 (define bta-service-type 170 (service-type 171 (name 'bta) 172 (extensions 173 (list (service-extension account-service-type 174 (const %bta-account)) 175 (service-extension activation-service-type 176 bta-activation) 177 (service-extension shepherd-root-service-type 178 (compose list bta-shepherd-service)))) 179 (default-value (bta-configuration)) 180 (description "Run a BTA server."))) 181 182 183 ;;; 184 ;;; Minecraft. 185 ;;; 186 187 ;; TODO: Modrinth API helper 188 189 (define* (fabric-jar hash 190 #:key (minecraft "26.1.2") 191 (loader "0.19.2") 192 (launcher "1.1.1")) 193 (origin 194 (method url-fetch) 195 (uri (string-append "https://meta.fabricmc.net/v2/versions/loader/" 196 minecraft "/" 197 loader "/" 198 launcher "/server/jar")) 199 (file-name (string-append "fabric-server-mc." minecraft 200 "-loader." loader 201 "-launcher." launcher 202 ".jar")) 203 (sha256 hash))) 204 205 (define %default-fabric-jar 206 (fabric-jar (base32 "1gax8i1risr0irgcmbwc0jdf78yhahplsqiyhiblrq7hkydx26z9"))) 207 208 ;; TODO: Switch to define-configuration/no-serialization 209 (define-record-type* <minecraft-configuration> 210 minecraft-configuration 211 make-minecraft-configuration 212 minecraft-configuration? 213 (jdk minecraft-configuration-package 214 (default openjdk)) 215 (jar minecraft-configuration-jar 216 (default %default-fabric-jar)) 217 (memory minecraft-configuration-memory 218 (default 8)) 219 (properties minecraft-configuration-properties 220 (default '())) 221 (mods minecraft-configuration-mods 222 (default '())) 223 (log-file minecraft-configuration-log-file 224 (default "/var/log/minecraft.log")) 225 (home minecraft-configuration-home 226 (default "/var/lib/minecraft")) 227 (port minecraft-configuration-port 228 (default 25565))) 229 230 231 (define %minecraft-account 232 (list (user-account 233 (name "minecraft") 234 (group "minecraft") 235 (system? #t) 236 (comment "Minecraft server user") 237 (home-directory "/var/lib/minecraft") 238 (shell (file-append shadow "/sbin/nologin"))) 239 (user-group 240 (name "minecraft") 241 (system? #t)))) 242 243 (define %minecraft-eula 244 (plain-file "eula.txt" "eula=true\n")) 245 246 (define %rcon-password 247 (random-string 72)) 248 249 (define minecraft-activation 250 (match-lambda 251 (($ <minecraft-configuration> jdk jar memory properties mods log-file home port) 252 (with-imported-modules '((guix build utils)) 253 #~(begin 254 (use-modules (guix build utils) 255 (ice-9 match)) 256 257 (let ((user (getpwnam "minecraft")) 258 (eula #$(string-append home "/eula.txt")) 259 (properties #$(string-append home "/server.properties")) 260 (mod-dir #$(string-append home "/mods/"))) 261 ;; TODO: Setup files as the root user 262 ;; Setup files as the git user. 263 (match (primitive-fork) 264 (0 265 (dynamic-wind 266 (const #t) 267 (lambda () 268 ;; Switch to the minecraft user. 269 (setgid (passwd:gid user)) 270 (setuid (passwd:uid user)) 271 272 ;; Placate the EULA requirement. 273 (if (not (file-exists? eula)) 274 (symlink #$%minecraft-eula eula)) 275 276 ;; Write to server.properties. 277 (call-with-output-file 278 properties 279 (lambda (port) 280 (format port "server-port=~a\n" #$port) 281 (display "enable-rcon=true\n" port) 282 (format port "rcon.password=~a\n" #$%rcon-password) 283 (for-each 284 (lambda (pair) 285 (format port "~a=~a\n" (car pair) (cdr pair))) 286 '#$properties))) 287 288 ;; TODO: Mod config 289 290 ;; Symlink mods to the mods folder. 291 (if (file-exists? mod-dir) 292 (delete-file-recursively mod-dir)) 293 (mkdir mod-dir) 294 (for-each 295 (lambda (mod) 296 (symlink mod (string-append mod-dir (basename mod)))) 297 (list #$@mods)) 298 299 ;; Return to main thread. 300 (primitive-exit 0)) 301 (lambda () 302 (primitive-exit 1)))) 303 (pid (waitpid pid))))) 304 )))) 305 306 (define minecraft-shepherd-service 307 (match-lambda 308 (($ <minecraft-configuration> jdk jar memory properties mods log-file home port) 309 (shepherd-service 310 (documentation "Minecraft server") 311 (provision '(minecraft)) 312 (requirement '(user-processes networking)) 313 (start #~(make-forkexec-constructor 314 (list #$(file-append jdk "/bin/java") 315 #$(format #f "-Xmx~aG" memory) 316 "-jar" #$jar 317 "nogui") 318 #:user "minecraft" 319 #:group "minecraft" 320 #:directory #$home 321 #:log-file #$log-file)) 322 (stop #~(make-kill-destructor)) 323 (actions (list (shepherd-action 324 (name 'rcon) 325 (documentation "Run server commands via rcon.") 326 (procedure #~(lambda (running . args) 327 328 (define (collect-lines port) 329 "Collect all lines from a port in a list" 330 (let loop ((line (read-line port)) 331 (acc '())) 332 (if (eof-object? line) 333 (begin (close-port port) 334 acc) 335 (loop (read-line port) 336 (cons line acc))))) 337 338 (let ((output-pipe (pipe))) 339 (spawn #$(file-append mcrcon "/bin/mcrcon") 340 (cons "mcrcon" args) 341 #:output (cdr output-pipe) 342 #:error (cdr output-pipe) 343 #:environment 344 (list 345 #$(string-append "MCRCON_PASS=" 346 %rcon-password)) 347 #:search-path? #f) 348 (close-port (cdr output-pipe)) 349 (while #t 350 (let ((line (read-line (car output-pipe)))) 351 (if (eof-object? line) 352 (break)) 353 (display line) 354 (newline)))) 355 ;; TODO 356 ))))))))) 357 358 (define minecraft-service-type 359 (service-type 360 (name 'minecraft) 361 (extensions 362 (list (service-extension account-service-type 363 (const %minecraft-account)) 364 (service-extension activation-service-type 365 minecraft-activation) 366 (service-extension shepherd-root-service-type 367 (compose list minecraft-shepherd-service)))) 368 (default-value (minecraft-configuration)) 369 (description "Run a Minecraft server.")))