commit d6c713cec5dffeadaa04b0114b33ceba87238a09
parent a88c19104988fdcec10f019fdac11b9879c547a3
Author: Luke Willis <lukejw@loquat.dev>
Date: Thu, 14 May 2026 00:27:47 -0400
bta: Add times mod and mod config support
Diffstat:
2 files changed, 257 insertions(+), 67 deletions(-)
diff --git a/system/andrew.scm b/system/andrew.scm
@@ -1,6 +1,6 @@
(define-module (system andrew)
#:use-module (system andrew ci)
- #:use-module (system andrew bta)
+ #:use-module (system andrew minecraft)
#:use-module (guix gexp)
#:use-module (guix packages)
#:use-module (guix modules)
@@ -68,8 +68,11 @@ table inet filter {
# allow ngninx
tcp dport { 80, 443 } accept
- # allow minecraft / voice chat
- th dport { 25565, 24454 } accept
+ # allow minecraft java (TCP)
+ tcp dport 25565 accept
+
+ # allow minecraft bedrock & voice chat (UDP)
+ udp dport 19132 accept
# reject everything else
reject with icmpx type port-unreachable
@@ -85,60 +88,21 @@ table inet filter {
(define %mod-list
(list
- ;; Required mods
- (bta-mod
- (name "halplibe")
- (display-name "HalpLibe")
- (url "https://github.com/Turnip-Labs/bta-halplibe/releases/download/v5.4.1/halplibe-5.4.1.jar")
- (sha256 (base32 "0vn64nh94zx6bv060bxgjkwjmn53759glvkjy5c1h8g4qmpghsd1")))
- (bta-mod
- (name "modmenu")
- (display-name "Mod Menu")
- (url "https://github.com/Turnip-Labs/ModMenu/releases/download/4.0.1/modmenu-bta-4.0.1.jar")
- (sha256 (base32 "0yhj3xsb4gljgjvc7c4xhf9g9dgkhgia6xbsv9gybnm8dkha0cm2")))
- (bta-mod
- (name "orthodoxicons")
- (display-name "Orthodox Icons")
- (url "https://files.monastech.xyz/orthodoxicons-1.1.0.jar")
- (sha256 (base32 "0y580h3bczghbp75zja4530pkm1kj4dj4r6858dl5f41dmxvgnf2")))
- (bta-mod
- (name "baskettipping")
- (display-name "Basket Tipping")
- (url "https://github.com/BlueMoonJune/BasketTipping/releases/download/Release/baskettipping-1.0.2.jar")
- (sha256 (base32 "1ayscx03lw26pi4ra0bdfyb8mpkh0706r77a3jk0wxqqnfwxaws4")))
- (bta-mod
- (name "breeding")
- (display-name "Breeding")
- (url "https://github.com/UselessSolutions/bta-breeding-backport/releases/download/v1.1.0-7.3/btabreeding-1.1.0-7.3.jar")
- (sha256 (base32 "1l3jwc4q1ygv31v19c50y1j1gvsfq286i0k9ndhpmbbdjhlab1cw")))
- (bta-mod-times
- (bta-times-configuration
- (realtime #t)))
- ;; Optional mods
- (bta-mod
- (name "btwaila")
- (display-name "Better Than WAILA")
- (description "Adds additional tooltips when looking at things. If you do a \
-lot of inventory management or technical stuff, this is probably for you.")
- (optional #t)
- (url "https://github.com/ToufouMaster/BTWaila/releases/download/1.2.5-7.3_04/btwaila-1.2.5-7.3_04.jar")
- (sha256 (base32 "12yxaisg7rixmpwyx3li0nc1jz1k55d9fybjbih1nrjbvlahmrvv")))
- (bta-mod
- (name "legacyui")
- (display-name "Legacy UI")
- (description "Modifies the UI to be similar to the legacy console edition, \
-alongside improving controller support. If you use a controller, I recommend this.")
- (optional #t)
- (url "https://github.com/UselessSolutions/BTA_Babric_LegacyUI/releases/download/v1.3.1-7.3.3/legacyui-1.3.1-7.3.3.jar")
- (sha256 (base32 "1xqdcs10y5xdjmlpqkwicsqzniin3wzzb4khfgi67qkhb70p6z0k")))
- (bta-mod
- (name "piessuildingtweaks")
- (display-name "Pie's Building Tweaks")
- (description "Adds some options to assist with building: offhand, randomize, \
-refill, etc.")
- (optional #t)
- (url "https://cdn.modrinth.com/data/bODCFQpm/versions/pIctNkZy/BuildingTweaks-1.1.0-7.3_04.jar")
- (sha256 (base32 "0nf3yi3bz7z6hm5jpf0bljbpp3ggmlry50aq0bhlhcl4n0496p4i")))))
+ (minecraft-mod
+ (url "https://cdn.modrinth.com/data/P7dR8mSH/versions/q87Wssd1/fabric-api-0.148.2+26.1.2.jar")
+ (sha256 (base32 "1ar6f4f94yxk06flpknh10y6q8k5yibm6nzjzzrd738jnvyviicr")))
+ (minecraft-mod
+ (url "https://cdn.modrinth.com/data/aZj58GfX/versions/h9nSM2ZF/easyauth-mc26.1-3.4.3-SNAPSHOT.48.jar")
+ (sha256 (base32 "01v14b7r0b95idjqimqa0q3wdyzqmyi3j03bw3n8jpbsqzni613n")))
+ (minecraft-mod
+ (url "https://cdn.modrinth.com/data/wKkoqHrH/versions/q4Pg0AcC/Geyser-Fabric-2.10.0-b1138.jar")
+ (sha256 (base32 "1j1gs3kpnp4wfa8sj9s7j3c4aqx4cn33pqcfwz3iska88k54h88i")))
+ (minecraft-mod
+ (url "https://cdn.modrinth.com/data/bWrNNfkb/versions/fD4J9lnX/Floodgate-Fabric-2.2.6-b63.jar")
+ (sha256 (base32 "0bazalawy2md8x1q51vgns4p9847k0bgcs4pnnhvbnp53wljpapl")))
+ (minecraft-mod
+ (url "https://cdn.modrinth.com/data/gvQqBUqZ/versions/R7MxYvuW/lithium-fabric-0.24.2+mc26.1.2.jar")
+ (sha256 (base32 "002fvncv6g6122ark6748lj4b3za0ffa8raz6sdp9qphlbqqjli2")))))
(define andrew-os
(operating-system
@@ -183,17 +147,16 @@ refill, etc.")
(list (service nftables-service-type
(nftables-configuration
(ruleset %nftables-ruleset)))
- (service bta-service-type
- (bta-configuration
- (properties `(("motd" . "MonasTech Private Server")
- ("difficulty" . "3")
+ (service minecraft-service-type
+ (minecraft-configuration
+ (properties `(("motd" . "HolyCross SMP - CHADS ONLY")
+ ("difficulty" . "hard")
("allow-flight" . "true")
("white-list" . "true")
- ("online-mode" . "false")))
- (ops '("1a68c56c-0bbc-413d-8fe1-10a2e4e04ad2"))
- (mods %mod-list)
- (packwiz-home "/var/www/files.monastech.xyz/bta")
- (packwiz-url "https://files.monastech.xyz/bta")))
+ ("spawn-protection" . "0")
+ ("online-mode" . "true")
+ ("enforce-secure-profile" . "false")))
+ (mods %mod-list)))
(service update-channels-locked-service-type '())
(service nginx-service-type
(nginx-configuration
@@ -215,7 +178,8 @@ refill, etc.")
(listen '("443 ssl"))
(root "/var/www/files.monastech.xyz")
(ssl-certificate "/etc/dehydrated/certs/monastech.xyz/fullchain.pem")
- (ssl-certificate-key "/etc/dehydrated/certs/monastech.xyz/privkey.pem")) (nginx-server-configuration
+ (ssl-certificate-key "/etc/dehydrated/certs/monastech.xyz/privkey.pem"))
+ (nginx-server-configuration
(server-name '("loquat.dev" "www.loquat.dev"))
(listen '("443 ssl"))
(root "/var/www/loquat.dev")
diff --git a/system/andrew/minecraft.scm b/system/andrew/minecraft.scm
@@ -0,0 +1,226 @@
+(define-module (system andrew minecraft)
+ #:use-module (gnu services)
+ #:use-module (gnu services configuration)
+ #:use-module (gnu services shepherd)
+ #:use-module (gnu packages admin)
+ #:use-module (gnu packages compression)
+ #:use-module (gnu packages gnupg)
+ #:use-module (gnu packages java)
+ #:use-module (gnu system shadow)
+ #:use-module (guix gexp)
+ #:use-module (guix store)
+ #:use-module (guix base16)
+ #:use-module (guix modules)
+ #:use-module (guix packages)
+ #:use-module (guix records)
+ #:use-module (guix download)
+ #:use-module (guix build-system copy)
+ #:use-module (mt utils)
+ #:use-module (mt packages games)
+ #:use-module ((srfi srfi-1) #:select (filter-map))
+ #:use-module (ice-9 match)
+ #:export (minecraft-mod
+ minecraft-mod?
+ minecraft-mod->origin
+ minecraft-configuration
+ minecraft-configuration?
+ minecraft-service-type))
+
+;; Mod helper records
+(define-record-type* <minecraft-mod>
+ minecraft-mod
+ make-minecraft-mod
+ minecraft-mod?
+ (url minecraft-mod-url)
+ (sha256 minecraft-mod-sha256)
+ (config minecraft-mod-config
+ (default #f)))
+
+(define (minecraft-mod->origin mod)
+ (origin
+ (method url-fetch)
+ (uri (minecraft-mod-url mod))
+ (sha256 (minecraft-mod-sha256 mod))))
+
+;; Main code
+(define %fabric-server
+ (origin
+ (method url-fetch)
+ (uri "https://meta.fabricmc.net/v2/versions/loader/26.1.2/0.19.2/1.1.1/server/jar")
+ (sha256 (base32 "1gax8i1risr0irgcmbwc0jdf78yhahplsqiyhiblrq7hkydx26z9"))))
+
+(define %minecraft-account
+ (list (user-account
+ (name "minecraft")
+ (group "minecraft")
+ (system? #t)
+ (comment "Minecraft server user")
+ (home-directory "/var/lib/minecraft")
+ (shell (file-append shadow "/sbin/nologin")))
+ (user-group
+ (name "minecraft")
+ (system? #t))))
+
+;; TODO: Add more options
+(define-record-type* <minecraft-configuration>
+ minecraft-configuration
+ make-minecraft-configuration
+ minecraft-configuration?
+ (jdk minecraft-configuration-jdk
+ (default openjdk25))
+ (memory minecraft-configuration-memory
+ (default 8))
+ (properties minecraft-configuration-properties
+ (default '()))
+ (mods minecraft-configuration-mods
+ (default '()))
+ (home minecraft-configuration-home
+ (default "/var/lib/minecraft"))
+ (log-file minecraft-configuration-log-file
+ (default "/var/log/minecraft.log")))
+
+(define %rcon-password
+ (random-string 72))
+
+(define minecraft-activation
+ (match-lambda
+ (($ <minecraft-configuration> jdk memory properties mods home log-file)
+ (let* ((mod-files (map minecraft-mod->origin mods))
+ (mod-config-files (filter-map minecraft-mod-config mods)))
+ (with-imported-modules '((guix build utils))
+ #~(begin
+ (use-modules (guix build utils)
+ (ice-9 match))
+
+ (define (remkdir dir)
+ (when (file-exists? dir)
+ (delete-file-recursively dir))
+ (mkdir dir))
+
+ (let ((user (getpwnam "minecraft"))
+ (eula-txt
+ #$(string-append home "/eula.txt"))
+ (mod-dir
+ #$(string-append home "/mods"))
+ (config-dir
+ #$(string-append home "/config"))
+ (server-properties
+ #$(string-append home "/server.properties")))
+ ;; TODO: Finish
+ (match (primitive-fork)
+ (0
+ (dynamic-wind
+ (const #t)
+ (lambda ()
+ ;; Switch to the minecraft user
+ (setgid (passwd:gid user))
+ (setuid (passwd:uid user))
+
+ ;; Initialize eula.txt
+ (unless (file-exists? eula-txt)
+ (call-with-output-file
+ eula-txt
+ (lambda (port)
+ (display "eula=true\n" port))))
+
+ ;; Initialize server.properties
+ (call-with-output-file
+ server-properties
+ (lambda (port)
+ (display "enable-rcon=true\n" port)
+ (format port "rcon.password=~a\n" #$%rcon-password)
+ (for-each
+ (lambda (pair)
+ (format port "~a=~a\n" (car pair) (cdr pair)))
+ '#$properties)))
+
+ ;; Link mods
+ (remkdir mod-dir)
+ (for-each
+ (lambda (mod)
+ (symlink mod
+ (string-append mod-dir "/"
+ (string-drop (basename mod) 33))))
+ '#$mod-files)
+
+ ;; Link configs
+ (remkdir config-dir)
+ (for-each
+ (lambda (config)
+ (symlink config
+ (string-append config-dir "/"
+ (string-drop (basename config) 33))))
+ '#$mod-config-files)
+
+ ;; Return to main thread
+ (primitive-exit 0))
+ (lambda ()
+ (primitive-exit 1))))
+ (pid (waitpid pid))))))))))
+
+
+(define minecraft-shepherd-service
+ (match-lambda
+ (($ <minecraft-configuration> jdk memory properties mods home log-file)
+ (shepherd-service
+ (documentation "Minecraft server")
+ (provision '(minecraft))
+ (requirement '(user-processes networking))
+ (start #~(make-forkexec-constructor
+ (list #$(file-append jdk "/bin/java")
+ #$(format #f "-Xmx~aG" memory)
+ "-jar" #$%fabric-server
+ "nogui")
+ #:user "minecraft"
+ #:group "minecraft"
+ #:directory #$home
+ #:log-file #$log-file))
+ (actions (list (shepherd-action
+ (name 'rcon)
+ (documentation "Run server commands via rcon.")
+ (procedure
+ #~(begin
+ (use-modules (ice-9 rdelim))
+
+ (lambda (running . args)
+ (define (collect-lines port)
+ "Collect all lines from a port in a list"
+ (let loop ((line (read-line port))
+ (acc '()))
+ (if (eof-object? line)
+ (begin
+ (close-port port)
+ acc)
+ (loop (read-line port)
+ (cons line acc)))))
+
+ (let ((output-pipe (pipe)))
+ (spawn #$(file-append mcrcon "/bin/mcrcon")
+ (cons "mcrcon" args)
+ #:output (cdr output-pipe)
+ #:error (cdr output-pipe)
+ #:environment
+ (list #$(string-append "MCRCON_PASS="
+ %rcon-password))
+ #:search-path? #f)
+ (close-port (cdr output-pipe))
+ (while #t
+ (let ((line (read-line (car output-pipe))))
+ (if (eof-object? line)
+ (break))
+ (display line)
+ (newline))))))))))
+ (stop #~(make-kill-destructor SIGINT))))))
+
+(define minecraft-service-type
+ (service-type
+ (name 'minecraft)
+ (extensions
+ (list (service-extension account-service-type
+ (const %minecraft-account))
+ (service-extension activation-service-type
+ minecraft-activation)
+ (service-extension shepherd-root-service-type
+ (compose list minecraft-shepherd-service))))
+ (default-value (minecraft-configuration))
+ (description "Run a Minecraft server.")))