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.")))