From c89092cc56611f4c66759c8c3fc6e905dfb1e9d1 Mon Sep 17 00:00:00 2001 From: SuperChewie <95657093+SuperChewie@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:35:32 -0500 Subject: [PATCH 01/17] Add full support for PaperMC --- msctl | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 257 insertions(+), 18 deletions(-) diff --git a/msctl b/msctl index 5ec3a22..2bc516b 100644 --- a/msctl +++ b/msctl @@ -203,6 +203,15 @@ mscs_defaults() { ; Length in minutes to keep the version_manifest.json file before updating. # mscs-versions-duration=30 +; URL to download the PaperMC project information file. +# mscs-paper-versions-url=https://fill.papermc.io/v3/projects/paper + +; Location of the PaperMC project information file. +# mscs-paper-project-json=/opt/mscs/paper_project.json + +; Default PaperMC build channel (STABLE, BETA, or ALPHA). +# mscs-default-paper-channel=STABLE + ; Length in minutes to keep lock files before removing. # mscs-lockfile-duration=1440 @@ -802,6 +811,181 @@ updateVersionsJSON() { fi } +# --------------------------------------------------------------------------- +# Update the paper_project.json file. +# --------------------------------------------------------------------------- +updatePaperProjectJSON() { + if [ -s $PAPER_PROJECT_JSON ]; then + # Make a backup copy of the paper_project.json file. + cp -p "$PAPER_PROJECT_JSON" "$PAPER_PROJECT_JSON.bak" + # Delete the paper_project.json file if it is old. + find "$PAPER_PROJECT_JSON" -mmin +"$VERSIONS_DURATION" -delete >/dev/null 2>&1 + if [ -s $PAPER_PROJECT_JSON ]; then + printf "The cached copy of the PaperMC project information is up to date.\n" + printf "Use the force-update option to ensure a new copy is downloaded.\n" + else + printf "The PaperMC project information cache was out of date, it has been removed.\n" + fi + fi + # Download the paper_project.json file if it does not exist. + if [ ! -s $PAPER_PROJECT_JSON ]; then + printf "Downloading current PaperMC project information.\n" + $WGET --no-use-server-timestamps \ + --header="User-Agent: mscs/1.0 (https://github.com/MinecraftServerControl/mscs)" \ + -qO "$PAPER_PROJECT_JSON" "$PAPER_VERSIONS_URL" + if [ $? -ne 0 ]; then + if [ -s $PAPER_PROJECT_JSON.bak ]; then + printf "Error downloading the PaperMC project information, using a backup.\n" + cp -p "$PAPER_PROJECT_JSON.bak" "$PAPER_PROJECT_JSON" + else + printf "Error downloading the PaperMC project information, exiting.\n" + exit 1 + fi + fi + fi +} + +# --------------------------------------------------------------------------- +# Get the current PaperMC version number. +# +# @param 1 The world server. +# @return The current PaperMC version number. +# --------------------------------------------------------------------------- +getCurrentPaperVersion() { + local VERSION + # Extract the current version information. + VERSION=$(cat $PAPER_PROJECT_JSON | $PERL -0777 -sne ' + use JSON; + $json = decode_json ($_); + $version = $json->{versions}[0]; + $version =~ s/[\s#%*+?^\${}()|[\]\\]/-/g; + print $version; + ') + # Print an error and exit if the version string is empty. + if [ -z "$VERSION" ]; then + printf "Error detecting the current PaperMC version.\n" + exit 1 + fi + printf "$VERSION" +} + +# --------------------------------------------------------------------------- +# Update the paper_builds_VERSION.json file for the world. +# +# @param 1 The world server. +# --------------------------------------------------------------------------- +updatePaperBuildsJSON() { + local PAPER_BUILDS_JSON PAPER_BUILDS_URL SERVER_VERSION + SERVER_VERSION=$(getServerVersion "$1") + if [ $? -ne 0 ]; then + printf "$SERVER_VERSION\n" + exit 1 + fi + PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" + PAPER_BUILDS_URL="$PAPER_VERSIONS_URL/versions/$SERVER_VERSION/builds" + if [ -s $PAPER_BUILDS_JSON ]; then + # Make a backup copy of the paper_builds_VERSION.json file. + cp -p "$PAPER_BUILDS_JSON" "$PAPER_BUILDS_JSON.bak" + # Delete the paper_builds_VERSION.json file if it is old. + find "$PAPER_BUILDS_JSON" -mmin +"$VERSIONS_DURATION" -delete >/dev/null 2>&1 + if [ -s $PAPER_BUILDS_JSON ]; then + printf "The cached copy of the PaperMC build list is up to date.\n" + printf "Use the force-update option to ensure a new copy is downloaded.\n" + else + printf "The PaperMC build list cache was out of date, it has been removed.\n" + fi + fi + # Download the paper_builds_VERSION.json file if it does not exist. + if [ ! -s $PAPER_BUILDS_JSON ]; then + printf "Downloading current PaperMC build list.\n" + $WGET --no-use-server-timestamps \ + --header="User-Agent: mscs/1.0 (https://github.com/MinecraftServerControl/mscs)" \ + -qO "$PAPER_BUILDS_JSON" "$PAPER_BUILDS_URL" + if [ $? -ne 0 ]; then + if [ -s $PAPER_BUILDS_JSON.bak ]; then + printf "Error downloading the PaperMC build list, using a backup.\n" + cp -p "$PAPER_BUILDS_JSON.bak" "$PAPER_BUILDS_JSON" + else + printf "Error downloading the PaperMC build list, exiting.\n" + exit 1 + fi + fi + fi +} + +# --------------------------------------------------------------------------- +# Get the download URL for a PaperMC server version. +# +# @param 1 The world server. +# @return The download URL for the PaperMC server. +# --------------------------------------------------------------------------- +getPaperServerURL() { + local CHANNEL PAPER_BUILDS_JSON SERVER_VERSION + SERVER_VERSION=$(getServerVersion "$1") + if [ $? -ne 0 ]; then + printf "$SERVER_VERSION\n" + exit 1 + fi + CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") + PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" + cat $PAPER_BUILDS_JSON | $PERL -0777 -sne ' + use JSON; + my $builds = decode_json ($_); + my ($b) = grep { $_->{channel} eq "$channel" } @{$builds}; + print $b->{downloads}{"server:default"}{url}; + ' -- -channel="$CHANNEL" +} + +# --------------------------------------------------------------------------- +# Get the sha256sum for a PaperMC server version. +# +# @param 1 The world server. +# @return The sha256sum for the PaperMC server. +# --------------------------------------------------------------------------- +getPaperServerChecksum() { + local CHANNEL PAPER_BUILDS_JSON SERVER_VERSION + SERVER_VERSION=$(getServerVersion "$1") + if [ $? -ne 0 ]; then + printf "$SERVER_VERSION\n" + exit 1 + fi + CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") + PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" + cat $PAPER_BUILDS_JSON | $PERL -0777 -sne ' + use JSON; + my $builds = decode_json ($_); + my ($b) = grep { $_->{channel} eq "$channel" } @{$builds}; + print $b->{downloads}{"server:default"}{sha256}; + ' -- -channel="$CHANNEL" +} + +# --------------------------------------------------------------------------- +# Update the modded server build information for the world. +# +# @param 1 The world server. +# --------------------------------------------------------------------------- +updateModdedServerBuilds() { + case "$(getMSCSValue "$1" "mscs-server-type" "vanilla")" in + papermc) + updatePaperProjectJSON + updatePaperBuildsJSON "$1" + ;; + esac +} + +# --------------------------------------------------------------------------- +# Get the current version number for the server type of the world. +# +# @param 1 The world server. +# @return The current version number. +# --------------------------------------------------------------------------- +getServerCurrentVersion() { + case "$(getMSCSValue "$1" "mscs-server-type" "vanilla")" in + papermc) getCurrentPaperVersion "$1" ;; + *) getCurrentMinecraftVersion "$1" ;; + esac +} + # --------------------------------------------------------------------------- # Get the current Minecraft version number. # @@ -1015,7 +1199,7 @@ getClientURL() { # --------------------------------------------------------------------------- getServerVersion() { local CURRENT_VERSION - CURRENT_VERSION=$(getCurrentMinecraftVersion "$1") + CURRENT_VERSION=$(getServerCurrentVersion "$1") if [ $? -ne 0 ]; then printf "$CURRENT_VERSION\n" exit 1 @@ -1036,8 +1220,8 @@ getServerVersion() { # @return SERVER_JAR # --------------------------------------------------------------------------- getServerJar() { - local CURRENT_VERSION SERVER_VERSION SERVER_JAR - CURRENT_VERSION=$(getCurrentMinecraftVersion "$1") + local CURRENT_VERSION DEFAULT_JAR SERVER_TYPE SERVER_VERSION + CURRENT_VERSION=$(getServerCurrentVersion "$1") if [ $? -ne 0 ]; then printf "$CURRENT_VERSION\n" exit 1 @@ -1047,8 +1231,15 @@ getServerJar() { printf "$SERVER_VERSION\n" exit 1 fi + # Choose the default jar name based on the server type. + SERVER_TYPE=$(getMSCSValue "$1" "mscs-server-type" "vanilla") + case "$SERVER_TYPE" in + papermc) DEFAULT_JAR='paper.$SERVER_VERSION.jar' ;; + # forge) DEFAULT_JAR='forge-$SERVER_VERSION.jar' ;; + *) DEFAULT_JAR="$DEFAULT_SERVER_JAR" ;; + esac # Get the server jar, use the default value if not provided. - getMSCSValue "$1" "mscs-server-jar" "$DEFAULT_SERVER_JAR" | + getMSCSValue "$1" "mscs-server-jar" "$DEFAULT_JAR" | $PERL -sne ' $_ =~ s/\$CURRENT_VERSION/$current_version/g; $_ =~ s/\$SERVER_VERSION/$server_version/g; @@ -1064,7 +1255,7 @@ getServerJar() { # --------------------------------------------------------------------------- getServerLocation() { local CURRENT_VERSION SERVER_VERSION - CURRENT_VERSION=$(getCurrentMinecraftVersion "$1") + CURRENT_VERSION=$(getServerCurrentVersion "$1") if [ $? -ne 0 ]; then printf "$CURRENT_VERSION\n" exit 1 @@ -1091,7 +1282,7 @@ getServerLocation() { # --------------------------------------------------------------------------- getServerURL() { local CURRENT_VERSION SERVER_VERSION URL - CURRENT_VERSION=$(getCurrentMinecraftVersion "$1") + CURRENT_VERSION=$(getServerCurrentVersion "$1") if [ $? -ne 0 ]; then printf "$CURRENT_VERSION\n" exit 1 @@ -1109,9 +1300,13 @@ getServerURL() { print; ' -- -current_version="$CURRENT_VERSION" -server_version="$SERVER_VERSION") # If the download URL was not specified, extract it from the version - # manifest. + # manifest or server-type-specific source. if [ -z "$URL" ]; then - URL=$(getMinecraftVersionDownloadURL $SERVER_VERSION 'server') + case "$(getMSCSValue "$1" "mscs-server-type" "vanilla")" in + papermc) URL=$(getPaperServerURL "$1") ;; + # forge) URL=$(getForgeServerURL "$1") ;; + *) URL=$(getMinecraftVersionDownloadURL $SERVER_VERSION 'server') ;; + esac fi printf "$URL" } @@ -1816,7 +2011,8 @@ updateClientSoftware() { # @param 1 The world server to update. # --------------------------------------------------------------------------- updateServerSoftware() { - local SERVER_JAR SERVER_LOCATION SERVER_URL SERVER_VERSION SHA1 SHA1_FILE + local SERVER_JAR SERVER_LOCATION SERVER_TYPE SERVER_URL SERVER_VERSION + local SHA1 SHA1_FILE SHA256 SHA256_FILE SERVER_JAR=$(getServerJar "$1") if [ $? -ne 0 ]; then printf "$SERVER_JAR\n" @@ -1848,15 +2044,31 @@ updateServerSoftware() { printf "Error updating the Minecraft server software.\n" exit 1 fi - SHA1=$(getMinecraftVersionDownloadSHA1 $SERVER_VERSION 'server') - if [ -z $SHA1 ]; then - printf "Error retrieving the sha1 for the Minecraft server software.\n" - exit 1 - fi - SHA1_FILE=$(sha1sum $SERVER_LOCATION/$SERVER_JAR | cut -d ' ' -f1) - if [ $SHA1 != "$SHA1_FILE" ]; then - printf "Error verifying the sha1 for the Minecraft server software.\n" - fi + SERVER_TYPE=$(getMSCSValue "$1" "mscs-server-type" "vanilla") + case "$SERVER_TYPE" in + papermc) + SHA256=$(getPaperServerChecksum "$1") + if [ -z $SHA256 ]; then + printf "Error retrieving the sha256 for the Paper server software.\n" + exit 1 + fi + SHA256_FILE=$(sha256sum $SERVER_LOCATION/$SERVER_JAR | cut -d ' ' -f1) + if [ $SHA256 != "$SHA256_FILE" ]; then + printf "Error verifying the sha256 for the Paper server software.\n" + fi + ;; + *) + SHA1=$(getMinecraftVersionDownloadSHA1 $SERVER_VERSION 'server') + if [ -z $SHA1 ]; then + printf "Error retrieving the sha1 for the Minecraft server software.\n" + exit 1 + fi + SHA1_FILE=$(sha1sum $SERVER_LOCATION/$SERVER_JAR | cut -d ' ' -f1) + if [ $SHA1 != "$SHA1_FILE" ]; then + printf "Error verifying the sha1 for the Minecraft server software.\n" + fi + ;; + esac fi } @@ -2557,6 +2769,12 @@ LOCATION=$(getDefaultsValue 'mscs-location' $HOME'/mscs') # --------------------------------------------------------------------------- MINECRAFT_VERSIONS_URL=$(getDefaultsValue 'mscs-versions-url' 'https://launchermeta.mojang.com/mc/game/version_manifest.json') +# PaperMC Versions information +# --------------------------------------------------------------------------- +PAPER_VERSIONS_URL=$(getDefaultsValue 'mscs-paper-versions-url' 'https://fill.papermc.io/v3/projects/paper') +PAPER_PROJECT_JSON=$(getDefaultsValue 'mscs-paper-project-json' $LOCATION'/paper_project.json') +DEFAULT_PAPER_CHANNEL=$(getDefaultsValue 'mscs-default-paper-channel' 'STABLE') + # Minecraft Server Settings # --------------------------------------------------------------------------- # Settings used if not provided in the world's mscs.properties file. @@ -2598,6 +2816,8 @@ DEFAULT_RESTART_AFTER_CRASH=$(getDefaultsValue 'mscs-default-restart-after-crash # mscs-server-location - Assign the location of the server .jar file. # mscs-server-command - Assign the command to run for the server. # mscs-restart-after-crash - Restart the server after a crash (default disabled). +# mscs-server-type - Assign the server type (vanilla, papermc). +# mscs-paper-channel - Assign the PaperMC build channel (STABLE, BETA, ALPHA). # # Like above, the following variables may be used in some of the key values: # $JAVA - The Java virtual machine. @@ -2628,6 +2848,8 @@ DEFAULT_RESTART_AFTER_CRASH=$(getDefaultsValue 'mscs-default-restart-after-crash # mscs-server-location=/opt/mscs/server # mscs-server-command=$JAVA -Xms$INITIAL_MEMORY -Xmx$MAXIMUM_MEMORY $JVM_ARGS -jar $SERVER_LOCATION/$SERVER_JAR $SERVER_ARGS # mscs-restart-after-crash=false +# mscs-server-type=vanilla +# mscs-paper-channel=STABLE # World (Server Instance) Configuration # --------------------------------------------------------------------------- @@ -2707,6 +2929,8 @@ case "$COMMAND" in start) # Grab the latest version information. updateVersionsJSON + # Grab the latest PaperMC project information. + updatePaperProjectJSON # Figure out which worlds to start. WORLDS=$(getEnabledWorlds) if [ -z "$WORLDS" ]; then @@ -2807,6 +3031,8 @@ case "$COMMAND" in restart | reload | force-restart | force-reload) # Grab the latest version information. updateVersionsJSON + # Grab the latest PaperMC project information. + updatePaperProjectJSON # Figure out which worlds to restart. WORLDS="" if [ "$#" -ge 1 ]; then @@ -3014,6 +3240,8 @@ case "$COMMAND" in enable) # Grab the latest version information. updateVersionsJSON + # Grab the latest PaperMC project information. + updatePaperProjectJSON # Get list of disabled worlds. WORLDS="" if [ "$#" -ge 1 ]; then @@ -3389,6 +3617,13 @@ case "$COMMAND" in printf "Removing cached version manifest.\n" fi rm -f $VERSIONS_JSON + # Remove cached PaperMC project information. + if [ -s $PAPER_PROJECT_JSON ]; then + cp -fp "$PAPER_PROJECT_JSON" "$PAPER_PROJECT_JSON.bak" + printf "Removing cached PaperMC project information.\n" + fi + rm -f $PAPER_PROJECT_JSON + rm -f "$LOCATION"/paper_builds_*.json fi # Grab the latest version information. updateVersionsJSON @@ -3411,6 +3646,10 @@ case "$COMMAND" in printf "Unable to update worlds: no enabled worlds found.\n" exit 1 fi + # Grab the latest modded server build information. + for WORLD in $WORLDS; do + updateModdedServerBuilds "$WORLD" + done # Stop all of the world servers and backup the worlds. RUNNING= printf "Stopping Minecraft Server:" From 7ef7ef86fb2e62b28fd410d74fa9ca15b33323dd Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Sun, 5 Apr 2026 18:39:49 -0500 Subject: [PATCH 02/17] correct api calls --- msctl | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/msctl b/msctl index 2bc516b..fd46908 100644 --- a/msctl +++ b/msctl @@ -857,7 +857,13 @@ getCurrentPaperVersion() { VERSION=$(cat $PAPER_PROJECT_JSON | $PERL -0777 -sne ' use JSON; $json = decode_json ($_); - $version = $json->{versions}[0]; + my @families = sort { + my @av = split /\./, $a; my @bv = split /\./, $b; + for my $i (0..($#av > $#bv ? $#av : $#bv)) { + my $c = ($bv[$i]//0) <=> ($av[$i]//0); return $c if $c; + } 0; + } keys %{$json->{versions}}; + ($version) = grep { !/-(pre|rc)\d*$/i } @{$json->{versions}{$families[0]}}; $version =~ s/[\s#%*+?^\${}()|[\]\\]/-/g; print $version; ') @@ -955,7 +961,7 @@ getPaperServerChecksum() { use JSON; my $builds = decode_json ($_); my ($b) = grep { $_->{channel} eq "$channel" } @{$builds}; - print $b->{downloads}{"server:default"}{sha256}; + print $b->{downloads}{"server:default"}{checksums}{sha256}; ' -- -channel="$CHANNEL" } @@ -2929,8 +2935,6 @@ case "$COMMAND" in start) # Grab the latest version information. updateVersionsJSON - # Grab the latest PaperMC project information. - updatePaperProjectJSON # Figure out which worlds to start. WORLDS=$(getEnabledWorlds) if [ -z "$WORLDS" ]; then @@ -2963,6 +2967,10 @@ case "$COMMAND" in fi done fi + # Grab the latest modded server build information. + for WORLD in $WORLDS; do + updateModdedServerBuilds "$WORLD" + done # Start each world requested, if not already running. printf "Starting Minecraft Server:" for WORLD in $WORLDS; do @@ -3031,8 +3039,6 @@ case "$COMMAND" in restart | reload | force-restart | force-reload) # Grab the latest version information. updateVersionsJSON - # Grab the latest PaperMC project information. - updatePaperProjectJSON # Figure out which worlds to restart. WORLDS="" if [ "$#" -ge 1 ]; then @@ -3056,6 +3062,10 @@ case "$COMMAND" in printf "Unable to restart worlds: no enabled worlds found.\n" exit 1 fi + # Grab the latest modded server build information. + for WORLD in $WORLDS; do + updateModdedServerBuilds "$WORLD" + done # Restart each world requested, start those not already # running. printf "Restarting Minecraft Server:" @@ -3240,8 +3250,6 @@ case "$COMMAND" in enable) # Grab the latest version information. updateVersionsJSON - # Grab the latest PaperMC project information. - updatePaperProjectJSON # Get list of disabled worlds. WORLDS="" if [ "$#" -ge 1 ]; then @@ -3264,6 +3272,10 @@ case "$COMMAND" in printf "Unable to enable worlds: no disabled worlds found.\n" exit 1 fi + # Grab the latest modded server build information. + for WORLD in $WORLDS; do + updateModdedServerBuilds "$WORLD" + done printf "Enabling Minecraft world:" # Enable the world(s). for WORLD in $WORLDS; do From 656e9f09107572b4878540772b99d507d65990a5 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Sun, 5 Apr 2026 19:57:51 -0500 Subject: [PATCH 03/17] add unit tests for world lifecycle --- tests/mscs-world-lifecycle.sh | 83 +++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/mscs-world-lifecycle.sh diff --git a/tests/mscs-world-lifecycle.sh b/tests/mscs-world-lifecycle.sh new file mode 100644 index 0000000..7596718 --- /dev/null +++ b/tests/mscs-world-lifecycle.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +# Use an isolated directory so getEnabledWorlds/getDisabledWorlds only see our +# test worlds, not every directory in /tmp. +_orig_worlds_location="$WORLDS_LOCATION" +WORLDS_LOCATION=/tmp/mscs-lifecycletest +rm -rf "$WORLDS_LOCATION" +mkdir -p "$WORLDS_LOCATION" + +_lc_world="lc-testworld" +_sprops="$WORLDS_LOCATION/$_lc_world/server.properties" +_mprops="$WORLDS_LOCATION/$_lc_world/mscs.properties" + +# createWorld creates the world directory +createWorld "$_lc_world" 25565 "" +if [ ! -d "$WORLDS_LOCATION/$_lc_world" ]; then + terr "createWorld: world directory not created" +fi + +# createWorld writes correct values to server.properties +for _kv in "server-port=25565" "enable-query=true" "query.port=25565" "level-name=$_lc_world"; do + if ! grep -qs "^$_kv" "$_sprops"; then + terr "createWorld: server.properties missing '$_kv'" + fi +done + +# createWorld enables the world in mscs.properties +if ! grep -qs "^mscs-enabled=true" "$_mprops"; then + terr "createWorld: mscs.properties missing mscs-enabled=true" +fi + +# getEnabledWorlds includes the new world +_worlds=$(getEnabledWorlds) +if ! listContains "$_lc_world" "$_worlds"; then + terr "getEnabledWorlds: expected '$_lc_world' in '$_worlds'" +fi + +# isWorldEnabled returns success for an enabled world +if ! isWorldEnabled "$_lc_world"; then + terr "isWorldEnabled: expected true for '$_lc_world'" +fi + +# disableWorld sets mscs-enabled=false +disableWorld "$_lc_world" +if ! grep -qs "^mscs-enabled=false" "$_mprops"; then + terr "disableWorld: mscs.properties missing mscs-enabled=false" +fi + +# isWorldEnabled returns failure after disableWorld +if isWorldEnabled "$_lc_world"; then + terr "isWorldEnabled: expected false after disableWorld" +fi + +# isWorldDisabled returns success after disableWorld +if ! isWorldDisabled "$_lc_world"; then + terr "isWorldDisabled: expected true after disableWorld" +fi + +# getDisabledWorlds includes the world; getEnabledWorlds does not +_disabled=$(getDisabledWorlds) +_enabled=$(getEnabledWorlds) +if ! listContains "$_lc_world" "$_disabled"; then + terr "getDisabledWorlds: expected '$_lc_world' in '$_disabled'" +fi +if listContains "$_lc_world" "$_enabled"; then + terr "getEnabledWorlds: expected '$_lc_world' absent from '$_enabled'" +fi + +# enableWorld restores the world to enabled +enableWorld "$_lc_world" +if ! isWorldEnabled "$_lc_world"; then + terr "isWorldEnabled: expected true after enableWorld" +fi + +# deleteWorld removes the world directory +deleteWorld "$_lc_world" +if [ -d "$WORLDS_LOCATION/$_lc_world" ]; then + terr "deleteWorld: world directory still exists" +fi + +# clean up +rm -rf "$WORLDS_LOCATION" +WORLDS_LOCATION="$_orig_worlds_location" From a1e73d01c88ae2b522a2cfcafdd577d799e40e80 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Sun, 5 Apr 2026 19:58:02 -0500 Subject: [PATCH 04/17] add unit test for papermc functions --- tests/mscs-papermc.sh | 102 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 tests/mscs-papermc.sh diff --git a/tests/mscs-papermc.sh b/tests/mscs-papermc.sh new file mode 100644 index 0000000..aef1aee --- /dev/null +++ b/tests/mscs-papermc.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +_paper_test_location=/tmp/mscs-papertest +mkdir -p "$_paper_test_location" +_orig_paper_project_json="${PAPER_PROJECT_JSON}" +_orig_location="${LOCATION}" +PAPER_PROJECT_JSON="$_paper_test_location/paper_project.json" +LOCATION="$_paper_test_location" + +# Fixture: project JSON with mixed releases and pre-releases across two families +cat > "$PAPER_PROJECT_JSON" << 'EOF' +{ + "versions": { + "1.20": ["1.20.4", "1.20.2"], + "1.21": ["1.21.4", "1.21.1", "1.21-pre2", "1.21-rc1"] + } +} +EOF + +# getCurrentPaperVersion returns the latest non-pre-release from the latest family +got=$(getCurrentPaperVersion) +want="1.21.4" +if [ "$got" != "$want" ]; then + terr "getCurrentPaperVersion: got '$got' want '$want'" +fi + +# getCurrentPaperVersion skips pre-releases even when they appear before a release +cat > "$PAPER_PROJECT_JSON" << 'EOF' +{ + "versions": { + "1.21": ["1.21-rc2", "1.21.4", "1.21.1"] + } +} +EOF +got=$(getCurrentPaperVersion) +want="1.21.4" +if [ "$got" != "$want" ]; then + terr "getCurrentPaperVersion pre-release-first: got '$got' want '$want'" +fi + +# Fixture: builds JSON with STABLE and BETA channels +cat > "$_paper_test_location/paper_builds_1.21.4.json" << 'EOF' +[ + { + "channel": "BETA", + "downloads": { + "server:default": { + "url": "https://test.example.com/paper-1.21.4-101.jar", + "checksums": { "sha256": "betachecksum123" } + } + } + }, + { + "channel": "STABLE", + "downloads": { + "server:default": { + "url": "https://test.example.com/paper-1.21.4-100.jar", + "checksums": { "sha256": "stablechecksum456" } + } + } + } +] +EOF + +# getPaperServerURL returns the URL for the STABLE channel (default) +( + getServerVersion() { printf "1.21.4"; } + > "$propfile" + got=$(getPaperServerURL "$testworld") + want="https://test.example.com/paper-1.21.4-100.jar" + if [ "$got" != "$want" ]; then + terr "getPaperServerURL STABLE: got '$got' want '$want'" + fi +) + +# getPaperServerURL returns the URL for the BETA channel when configured +( + getServerVersion() { printf "1.21.4"; } + printf "mscs-paper-channel=BETA\n" > "$propfile" + got=$(getPaperServerURL "$testworld") + want="https://test.example.com/paper-1.21.4-101.jar" + if [ "$got" != "$want" ]; then + terr "getPaperServerURL BETA: got '$got' want '$want'" + fi +) + +# getPaperServerChecksum returns the SHA256 for the STABLE channel +( + getServerVersion() { printf "1.21.4"; } + > "$propfile" + got=$(getPaperServerChecksum "$testworld") + want="stablechecksum456" + if [ "$got" != "$want" ]; then + terr "getPaperServerChecksum STABLE: got '$got' want '$want'" + fi +) + +# clean up +> "$propfile" +rm -rf "$_paper_test_location" +PAPER_PROJECT_JSON="$_orig_paper_project_json" +LOCATION="$_orig_location" From cfe7049afbe450041153800e72eee6d6425dc094 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Sun, 5 Apr 2026 19:58:28 -0500 Subject: [PATCH 05/17] add unit test for server version --- tests/mscs-server-version.sh | 106 +++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/mscs-server-version.sh diff --git a/tests/mscs-server-version.sh b/tests/mscs-server-version.sh new file mode 100644 index 0000000..35be31a --- /dev/null +++ b/tests/mscs-server-version.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +# start with clean propfile +> "$propfile" + +# --- getServerJar --- + +# vanilla type (default): returns minecraft_server.VERSION.jar +( + getServerCurrentVersion() { printf "1.21.4"; } + getServerVersion() { printf "1.21.4"; } + > "$propfile" + got=$(getServerJar "$testworld") + want="minecraft_server.1.21.4.jar" + if [ "$got" != "$want" ]; then + terr "getServerJar vanilla: got '$got' want '$want'" + fi +) + +# papermc type: returns paper.VERSION.jar +( + getServerCurrentVersion() { printf "1.21.4"; } + getServerVersion() { printf "1.21.4"; } + printf "mscs-server-type=papermc\n" > "$propfile" + got=$(getServerJar "$testworld") + want="paper.1.21.4.jar" + if [ "$got" != "$want" ]; then + terr "getServerJar papermc: got '$got' want '$want'" + fi +) + +# explicit mscs-server-jar overrides the type-based default +( + getServerCurrentVersion() { printf "1.21.4"; } + getServerVersion() { printf "1.21.4"; } + printf "mscs-server-jar=custom.jar\n" > "$propfile" + got=$(getServerJar "$testworld") + want="custom.jar" + if [ "$got" != "$want" ]; then + terr "getServerJar custom: got '$got' want '$want'" + fi +) + +# --- getServerURL --- + +# explicit mscs-server-url is returned verbatim +( + getServerCurrentVersion() { printf "1.21.4"; } + getServerVersion() { printf "1.21.4"; } + printf "mscs-server-url=https://example.com/server.jar\n" > "$propfile" + got=$(getServerURL "$testworld") + want="https://example.com/server.jar" + if [ "$got" != "$want" ]; then + terr "getServerURL explicit: got '$got' want '$want'" + fi +) + +# papermc type with no URL delegates to getPaperServerURL +( + getServerCurrentVersion() { printf "1.21.4"; } + getServerVersion() { printf "1.21.4"; } + getPaperServerURL() { printf "https://papermc.test/paper.jar"; } + printf "mscs-server-type=papermc\n" > "$propfile" + got=$(getServerURL "$testworld") + want="https://papermc.test/paper.jar" + if [ "$got" != "$want" ]; then + terr "getServerURL papermc fallback: got '$got' want '$want'" + fi +) + +# vanilla type with no URL delegates to getMinecraftVersionDownloadURL +( + getServerCurrentVersion() { printf "1.21.4"; } + getServerVersion() { printf "1.21.4"; } + getMinecraftVersionDownloadURL() { printf "https://mojang.test/server.jar"; } + > "$propfile" + got=$(getServerURL "$testworld") + want="https://mojang.test/server.jar" + if [ "$got" != "$want" ]; then + terr "getServerURL vanilla fallback: got '$got' want '$want'" + fi +) + +# --- getServerCommand --- + +# command contains the jar name, server location, and -jar flag +( + getCurrentMinecraftVersion() { printf "1.21.4"; } + getServerVersion() { printf "1.21.4"; } + getServerJar() { printf "minecraft_server.1.21.4.jar"; } + getServerLocation() { printf "/opt/mscs/server"; } + > "$propfile" + got=$(getServerCommand "$testworld") + if ! printf "%s" "$got" | grep -qs "minecraft_server.1.21.4.jar"; then + terr "getServerCommand missing jar in: $got" + fi + if ! printf "%s" "$got" | grep -qs "/opt/mscs/server"; then + terr "getServerCommand missing location in: $got" + fi + if ! printf "%s" "$got" | grep -qs -- "-jar"; then + terr "getServerCommand missing -jar flag in: $got" + fi +) + +# clean up +> "$propfile" From 2aa81f6dbd55b1733d59470b6ebeecb6d3830904 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Sun, 5 Apr 2026 20:15:01 -0500 Subject: [PATCH 06/17] add unit test for config operations --- tests/mscs-config-ops.sh | 124 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/mscs-config-ops.sh diff --git a/tests/mscs-config-ops.sh b/tests/mscs-config-ops.sh new file mode 100644 index 0000000..317f98d --- /dev/null +++ b/tests/mscs-config-ops.sh @@ -0,0 +1,124 @@ +#!/bin/sh + +_cfg=/tmp/mscs-configtest.properties + +# clean start +rm -f "$_cfg" + +# --- getValue --- + +# key present returns value +printf "mykey=myvalue\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "myvalue" ]; then + terr "getValue key present: got '$got' want 'myvalue'" +fi + +# key absent returns default +printf "otherkey=othervalue\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "thedefault") +if [ "$got" != "thedefault" ]; then + terr "getValue key absent: got '$got' want 'thedefault'" +fi + +# file does not exist returns default +rm -f "$_cfg" +got=$(getValue "$_cfg" "mykey" "thedefault") +if [ "$got" != "thedefault" ]; then + terr "getValue no file: got '$got' want 'thedefault'" +fi + +# empty value returns default ([ -n "$KEY" ] && [ -n "$VALUE" ] requires both non-empty) +printf "mykey=\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "thedefault") +if [ "$got" != "thedefault" ]; then + terr "getValue empty value: got '$got' want 'thedefault'" +fi + +# key matching is case-insensitive +printf "MYKEY=myvalue\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "myvalue" ]; then + terr "getValue case-insensitive: got '$got' want 'myvalue'" +fi + +# hash comment lines are ignored +printf "# mykey=commented\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "default" ]; then + terr "getValue hash comment: got '$got' want 'default'" +fi + +# semicolon comment lines are ignored +printf "; mykey=commented\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "default" ]; then + terr "getValue semicolon comment: got '$got' want 'default'" +fi + +# double quotes are stripped from value +printf 'mykey="quoted-value"\n' > "$_cfg" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "quoted-value" ]; then + terr "getValue double-quote strip: got '$got' want 'quoted-value'" +fi + +# single quotes are stripped from value +printf "mykey='quoted-value'\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "quoted-value" ]; then + terr "getValue single-quote strip: got '$got' want 'quoted-value'" +fi + +# spaces around = are handled +printf "mykey = spaced-value\n" > "$_cfg" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "spaced-value" ]; then + terr "getValue spaces around =: got '$got' want 'spaced-value'" +fi + +# default with leading hyphen is returned verbatim (printf -- prevents flag misinterpretation) +rm -f "$_cfg" +got=$(getValue "$_cfg" "mykey" "-Dfoo=bar") +if [ "$got" != "-Dfoo=bar" ]; then + terr "getValue hyphen default: got '$got' want '-Dfoo=bar'" +fi + +# --- setValue --- + +# new key is appended to an empty file +: > "$_cfg" +setValue "$_cfg" "newkey" "newvalue" +if ! grep -qs "^newkey=newvalue$" "$_cfg"; then + terr "setValue append: file contents: $(cat "$_cfg")" +fi + +# existing key is updated in-place +printf "mykey=old\n" > "$_cfg" +setValue "$_cfg" "mykey" "new" +got=$(getValue "$_cfg" "mykey" "default") +if [ "$got" != "new" ]; then + terr "setValue update: got '$got' want 'new'" +fi + +# other keys are preserved when one key is updated +printf "key1=a\nkey2=b\n" > "$_cfg" +setValue "$_cfg" "key1" "x" +got=$(getValue "$_cfg" "key2" "default") +if [ "$got" != "b" ]; then + terr "setValue preserves others: key2 got '$got' want 'b'" +fi + +# file is created when it does not exist +rm -f "$_cfg" +setValue "$_cfg" "newkey" "newvalue" +if [ ! -f "$_cfg" ]; then + terr "setValue creates file: file not found at $_cfg" +fi +got=$(getValue "$_cfg" "newkey" "default") +if [ "$got" != "newvalue" ]; then + terr "setValue creates file value: got '$got' want 'newvalue'" +fi + +# clean up +rm -f "$_cfg" From 30d4e0d1de5eada1327d818f85fb7094967f799a Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Sun, 5 Apr 2026 20:16:44 -0500 Subject: [PATCH 07/17] add unit tests for vanilla lifecycle --- tests/mscs-vanilla-version.sh | 109 ++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/mscs-vanilla-version.sh diff --git a/tests/mscs-vanilla-version.sh b/tests/mscs-vanilla-version.sh new file mode 100644 index 0000000..110dea5 --- /dev/null +++ b/tests/mscs-vanilla-version.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +_vanilla_test_location=/tmp/mscs-vanillatest +_orig_versions_json="$VERSIONS_JSON" +VERSIONS_JSON="$_vanilla_test_location/version_manifest.json" +mkdir -p "$_vanilla_test_location" + +cat > "$VERSIONS_JSON" << 'EOF' +{ + "latest": {"release": "1.21.4", "snapshot": "25w14a"}, + "versions": [ + {"id": "1.21.4", "releaseTime": "2024-12-03T10:00:00+00:00", "url": "https://example.com/1.21.4.json"}, + {"id": "25w14a", "releaseTime": "2025-04-02T10:00:00+00:00", "url": "https://example.com/25w14a.json"}, + {"id": "1.21.1", "releaseTime": "2024-08-08T10:00:00+00:00", "url": "https://example.com/1.21.1.json"} + ] +} +EOF + +# --- getCurrentMinecraftVersion --- + +# returns the latest release version by default +: > "$propfile" +got=$(getCurrentMinecraftVersion "$testworld") +if [ "$got" != "1.21.4" ]; then + terr "getCurrentMinecraftVersion release: got '$got' want '1.21.4'" +fi + +# returns snapshot version when mscs-version-type=snapshot +printf "mscs-version-type=snapshot\n" > "$propfile" +got=$(getCurrentMinecraftVersion "$testworld") +if [ "$got" != "25w14a" ]; then + terr "getCurrentMinecraftVersion snapshot: got '$got' want '25w14a'" +fi + +# --- getMinecraftVersionReleaseTime --- + +got=$(getMinecraftVersionReleaseTime "1.21.4") +if [ "$got" != "2024-12-03T10:00:00+00:00" ]; then + terr "getMinecraftVersionReleaseTime 1.21.4: got '$got' want '2024-12-03T10:00:00+00:00'" +fi + +got=$(getMinecraftVersionReleaseTime "1.21.1") +if [ "$got" != "2024-08-08T10:00:00+00:00" ]; then + terr "getMinecraftVersionReleaseTime 1.21.1: got '$got' want '2024-08-08T10:00:00+00:00'" +fi + +# --- getClientVersion --- + +# returns current version by default when mscs-client-version is not set +( + getCurrentMinecraftVersion() { printf "1.21.4"; } + : > "$propfile" + got=$(getClientVersion "$testworld") + if [ "$got" != "1.21.4" ]; then + terr "getClientVersion default: got '$got' want '1.21.4'" + fi +) + +# returns pinned version when mscs-client-version is set in propfile +( + getCurrentMinecraftVersion() { printf "1.21.4"; } + printf "mscs-client-version=1.20.4\n" > "$propfile" + got=$(getClientVersion "$testworld") + if [ "$got" != "1.20.4" ]; then + terr "getClientVersion pinned: got '$got' want '1.20.4'" + fi +) + +# --- getClientJar --- + +# returns VERSION.jar by default +( + getCurrentMinecraftVersion() { printf "1.21.4"; } + getClientVersion() { printf "1.21.4"; } + : > "$propfile" + got=$(getClientJar "$testworld") + if [ "$got" != "1.21.4.jar" ]; then + terr "getClientJar default: got '$got' want '1.21.4.jar'" + fi +) + +# returns custom jar when mscs-client-jar is configured +( + getCurrentMinecraftVersion() { printf "1.21.4"; } + getClientVersion() { printf "1.21.4"; } + printf "mscs-client-jar=custom-client.jar\n" > "$propfile" + got=$(getClientJar "$testworld") + if [ "$got" != "custom-client.jar" ]; then + terr "getClientJar custom: got '$got' want 'custom-client.jar'" + fi +) + +# --- getClientLocation --- + +# returns path with version substituted (DEFAULT_CLIENT_LOCATION = $HOME/.minecraft/versions/$CLIENT_VERSION) +( + getCurrentMinecraftVersion() { printf "1.21.4"; } + getClientVersion() { printf "1.21.4"; } + : > "$propfile" + got=$(getClientLocation "$testworld") + if ! printf "%s" "$got" | grep -qs "1.21.4"; then + terr "getClientLocation default: '1.21.4' not found in '$got'" + fi +) + +# clean up +: > "$propfile" +rm -rf "$_vanilla_test_location" +VERSIONS_JSON="$_orig_versions_json" From 201a1b696274a888adc531c65f45dcc6c138cf63 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Sun, 5 Apr 2026 23:27:17 -0500 Subject: [PATCH 08/17] fix logic to determine the latest papermc version --- msctl | 64 +++++++++++++++++++++++++++++++++++-------- tests/mscs-papermc.sh | 21 ++++++++++++++ 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/msctl b/msctl index fd46908..9e1f86c 100644 --- a/msctl +++ b/msctl @@ -852,9 +852,10 @@ updatePaperProjectJSON() { # @return The current PaperMC version number. # --------------------------------------------------------------------------- getCurrentPaperVersion() { - local VERSION - # Extract the current version information. - VERSION=$(cat $PAPER_PROJECT_JSON | $PERL -0777 -sne ' + local BUILDS_JSON BUILDS_URL CHANNEL HAS_CHANNEL VERSION VERSIONS + CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") + # Get all candidate versions from newest family first, skipping pre/rc. + VERSIONS=$(cat $PAPER_PROJECT_JSON | $PERL -0777 -ne ' use JSON; $json = decode_json ($_); my @families = sort { @@ -863,16 +864,43 @@ getCurrentPaperVersion() { my $c = ($bv[$i]//0) <=> ($av[$i]//0); return $c if $c; } 0; } keys %{$json->{versions}}; - ($version) = grep { !/-(pre|rc)\d*$/i } @{$json->{versions}{$families[0]}}; - $version =~ s/[\s#%*+?^\${}()|[\]\\]/-/g; - print $version; + for my $family (@families) { + my ($v) = grep { !/-(pre|rc)\d*$/i } @{$json->{versions}{$family}}; + next unless defined $v; + $v =~ s/[\s#%*+?^\${}()|[\]\\]/-/g; + print "$v\n"; + } ') - # Print an error and exit if the version string is empty. - if [ -z "$VERSION" ]; then - printf "Error detecting the current PaperMC version.\n" - exit 1 - fi - printf "$VERSION" + # Find the newest version that has a build in the desired channel. + for VERSION in $VERSIONS; do + BUILDS_JSON="$LOCATION/paper_builds_$VERSION.json" + BUILDS_URL="$PAPER_VERSIONS_URL/versions/$VERSION/builds" + # Download builds JSON for this version if not cached. + if [ ! -s "$BUILDS_JSON" ]; then + $WGET --no-use-server-timestamps \ + --header="User-Agent: mscs/1.0 (https://github.com/MinecraftServerControl/mscs)" \ + -qO "$BUILDS_JSON" "$BUILDS_URL" 2>/dev/null || rm -f "$BUILDS_JSON" + fi + if [ -s "$BUILDS_JSON" ]; then + # Verify this version has a build in the desired channel. + HAS_CHANNEL=$(cat "$BUILDS_JSON" | $PERL -0777 -sne ' + use JSON; + my $builds = decode_json($_); + my ($b) = grep { $_->{channel} eq $channel } @{$builds}; + print "yes" if defined $b; + ' -- -channel="$CHANNEL") + if [ "$HAS_CHANNEL" = "yes" ]; then + printf "%s" "$VERSION" + return + fi + else + # Builds JSON unavailable (download failed); assume this version is valid. + printf "%s" "$VERSION" + return + fi + done + printf "Error detecting the current PaperMC version.\n" + exit 1 } # --------------------------------------------------------------------------- @@ -2041,6 +2069,18 @@ updateServerSoftware() { fi # Make sure the server software directory exists. mkdir -p "$SERVER_LOCATION" + # For PaperMC, re-download if the installed JAR does not match the latest build. + if [ "$(getMSCSValue "$1" "mscs-server-type" "vanilla")" = "papermc" ] && + [ -s "$SERVER_LOCATION/$SERVER_JAR" ]; then + SHA256=$(getPaperServerChecksum "$1") + if [ -n "$SHA256" ]; then + SHA256_FILE=$(sha256sum "$SERVER_LOCATION/$SERVER_JAR" | cut -d ' ' -f1) + if [ "$SHA256" != "$SHA256_FILE" ]; then + printf "Newer PaperMC build available, removing outdated server JAR.\n" + rm -f "$SERVER_LOCATION/$SERVER_JAR" + fi + fi + fi # Download the server jar if it is missing. if [ ! -s "$SERVER_LOCATION/$SERVER_JAR" ]; then # Download the Minecraft server software. diff --git a/tests/mscs-papermc.sh b/tests/mscs-papermc.sh index aef1aee..22817f8 100644 --- a/tests/mscs-papermc.sh +++ b/tests/mscs-papermc.sh @@ -95,6 +95,27 @@ EOF fi ) +# getCurrentPaperVersion skips a version family that has no STABLE builds +cat > "$PAPER_PROJECT_JSON" << 'EOF' +{ + "versions": { + "26.1": ["26.1.1"], + "1.21": ["1.21.11", "1.21.10"] + } +} +EOF +cat > "$_paper_test_location/paper_builds_26.1.1.json" << 'EOF' +[{"channel": "ALPHA", "downloads": {"server:default": {"url": "https://test.example.com/paper-26.1.1-1.jar", "checksums": {"sha256": "alphachecksum"}}}}] +EOF +cat > "$_paper_test_location/paper_builds_1.21.11.json" << 'EOF' +[{"channel": "STABLE", "downloads": {"server:default": {"url": "https://test.example.com/paper-1.21.11-128.jar", "checksums": {"sha256": "stablechecksum"}}}}] +EOF +got=$(getCurrentPaperVersion) +want="1.21.11" +if [ "$got" != "$want" ]; then + terr "getCurrentPaperVersion alpha-family: got '$got' want '$want'" +fi + # clean up > "$propfile" rm -rf "$_paper_test_location" From 8d7bec42faee45cb32ea583a32387bfed3fadd22 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Thu, 9 Apr 2026 22:54:56 -0500 Subject: [PATCH 09/17] shell linting --- msctl | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/msctl b/msctl index 9e1f86c..26a6e94 100644 --- a/msctl +++ b/msctl @@ -815,12 +815,12 @@ updateVersionsJSON() { # Update the paper_project.json file. # --------------------------------------------------------------------------- updatePaperProjectJSON() { - if [ -s $PAPER_PROJECT_JSON ]; then + if [ -s "$PAPER_PROJECT_JSON" ]; then # Make a backup copy of the paper_project.json file. cp -p "$PAPER_PROJECT_JSON" "$PAPER_PROJECT_JSON.bak" # Delete the paper_project.json file if it is old. find "$PAPER_PROJECT_JSON" -mmin +"$VERSIONS_DURATION" -delete >/dev/null 2>&1 - if [ -s $PAPER_PROJECT_JSON ]; then + if [ -s "$PAPER_PROJECT_JSON" ]; then printf "The cached copy of the PaperMC project information is up to date.\n" printf "Use the force-update option to ensure a new copy is downloaded.\n" else @@ -828,13 +828,13 @@ updatePaperProjectJSON() { fi fi # Download the paper_project.json file if it does not exist. - if [ ! -s $PAPER_PROJECT_JSON ]; then + if [ ! -s "$PAPER_PROJECT_JSON" ]; then printf "Downloading current PaperMC project information.\n" $WGET --no-use-server-timestamps \ --header="User-Agent: mscs/1.0 (https://github.com/MinecraftServerControl/mscs)" \ -qO "$PAPER_PROJECT_JSON" "$PAPER_VERSIONS_URL" if [ $? -ne 0 ]; then - if [ -s $PAPER_PROJECT_JSON.bak ]; then + if [ -s "$PAPER_PROJECT_JSON.bak" ]; then printf "Error downloading the PaperMC project information, using a backup.\n" cp -p "$PAPER_PROJECT_JSON.bak" "$PAPER_PROJECT_JSON" else @@ -855,7 +855,7 @@ getCurrentPaperVersion() { local BUILDS_JSON BUILDS_URL CHANNEL HAS_CHANNEL VERSION VERSIONS CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") # Get all candidate versions from newest family first, skipping pre/rc. - VERSIONS=$(cat $PAPER_PROJECT_JSON | $PERL -0777 -ne ' + VERSIONS=$(cat "$PAPER_PROJECT_JSON" | $PERL -0777 -ne ' use JSON; $json = decode_json ($_); my @families = sort { @@ -912,17 +912,17 @@ updatePaperBuildsJSON() { local PAPER_BUILDS_JSON PAPER_BUILDS_URL SERVER_VERSION SERVER_VERSION=$(getServerVersion "$1") if [ $? -ne 0 ]; then - printf "$SERVER_VERSION\n" + printf "%s\n" "$SERVER_VERSION" exit 1 fi PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" PAPER_BUILDS_URL="$PAPER_VERSIONS_URL/versions/$SERVER_VERSION/builds" - if [ -s $PAPER_BUILDS_JSON ]; then + if [ -s "$PAPER_BUILDS_JSON" ]; then # Make a backup copy of the paper_builds_VERSION.json file. cp -p "$PAPER_BUILDS_JSON" "$PAPER_BUILDS_JSON.bak" # Delete the paper_builds_VERSION.json file if it is old. find "$PAPER_BUILDS_JSON" -mmin +"$VERSIONS_DURATION" -delete >/dev/null 2>&1 - if [ -s $PAPER_BUILDS_JSON ]; then + if [ -s "$PAPER_BUILDS_JSON" ]; then printf "The cached copy of the PaperMC build list is up to date.\n" printf "Use the force-update option to ensure a new copy is downloaded.\n" else @@ -930,13 +930,13 @@ updatePaperBuildsJSON() { fi fi # Download the paper_builds_VERSION.json file if it does not exist. - if [ ! -s $PAPER_BUILDS_JSON ]; then + if [ ! -s "$PAPER_BUILDS_JSON" ]; then printf "Downloading current PaperMC build list.\n" $WGET --no-use-server-timestamps \ --header="User-Agent: mscs/1.0 (https://github.com/MinecraftServerControl/mscs)" \ -qO "$PAPER_BUILDS_JSON" "$PAPER_BUILDS_URL" if [ $? -ne 0 ]; then - if [ -s $PAPER_BUILDS_JSON.bak ]; then + if [ -s "$PAPER_BUILDS_JSON.bak" ]; then printf "Error downloading the PaperMC build list, using a backup.\n" cp -p "$PAPER_BUILDS_JSON.bak" "$PAPER_BUILDS_JSON" else @@ -957,12 +957,12 @@ getPaperServerURL() { local CHANNEL PAPER_BUILDS_JSON SERVER_VERSION SERVER_VERSION=$(getServerVersion "$1") if [ $? -ne 0 ]; then - printf "$SERVER_VERSION\n" + printf "%s\n" "$SERVER_VERSION" exit 1 fi CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" - cat $PAPER_BUILDS_JSON | $PERL -0777 -sne ' + cat "$PAPER_BUILDS_JSON" | $PERL -0777 -sne ' use JSON; my $builds = decode_json ($_); my ($b) = grep { $_->{channel} eq "$channel" } @{$builds}; @@ -980,12 +980,12 @@ getPaperServerChecksum() { local CHANNEL PAPER_BUILDS_JSON SERVER_VERSION SERVER_VERSION=$(getServerVersion "$1") if [ $? -ne 0 ]; then - printf "$SERVER_VERSION\n" + printf "%s\n" "$SERVER_VERSION" exit 1 fi CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" - cat $PAPER_BUILDS_JSON | $PERL -0777 -sne ' + cat "$PAPER_BUILDS_JSON" | $PERL -0777 -sne ' use JSON; my $builds = decode_json ($_); my ($b) = grep { $_->{channel} eq "$channel" } @{$builds}; @@ -2094,23 +2094,23 @@ updateServerSoftware() { case "$SERVER_TYPE" in papermc) SHA256=$(getPaperServerChecksum "$1") - if [ -z $SHA256 ]; then + if [ -z "$SHA256" ]; then printf "Error retrieving the sha256 for the Paper server software.\n" exit 1 fi - SHA256_FILE=$(sha256sum $SERVER_LOCATION/$SERVER_JAR | cut -d ' ' -f1) - if [ $SHA256 != "$SHA256_FILE" ]; then + SHA256_FILE=$(sha256sum "$SERVER_LOCATION/$SERVER_JAR" | cut -d ' ' -f1) + if [ "$SHA256" != "$SHA256_FILE" ]; then printf "Error verifying the sha256 for the Paper server software.\n" fi ;; *) SHA1=$(getMinecraftVersionDownloadSHA1 $SERVER_VERSION 'server') - if [ -z $SHA1 ]; then + if [ -z "$SHA1" ]; then printf "Error retrieving the sha1 for the Minecraft server software.\n" exit 1 fi - SHA1_FILE=$(sha1sum $SERVER_LOCATION/$SERVER_JAR | cut -d ' ' -f1) - if [ $SHA1 != "$SHA1_FILE" ]; then + SHA1_FILE=$(sha1sum "$SERVER_LOCATION/$SERVER_JAR" | cut -d ' ' -f1) + if [ "$SHA1" != "$SHA1_FILE" ]; then printf "Error verifying the sha1 for the Minecraft server software.\n" fi ;; @@ -3670,11 +3670,11 @@ case "$COMMAND" in fi rm -f $VERSIONS_JSON # Remove cached PaperMC project information. - if [ -s $PAPER_PROJECT_JSON ]; then + if [ -s "$PAPER_PROJECT_JSON" ]; then cp -fp "$PAPER_PROJECT_JSON" "$PAPER_PROJECT_JSON.bak" printf "Removing cached PaperMC project information.\n" fi - rm -f $PAPER_PROJECT_JSON + rm -f "$PAPER_PROJECT_JSON" rm -f "$LOCATION"/paper_builds_*.json fi # Grab the latest version information. From dc7033a88ed0c26983ae80201f6c40ab9ffcfc23 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Thu, 9 Apr 2026 22:55:26 -0500 Subject: [PATCH 10/17] build fixture for papermc unit test --- tests/mscs-papermc.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/mscs-papermc.sh b/tests/mscs-papermc.sh index 22817f8..019bb87 100644 --- a/tests/mscs-papermc.sh +++ b/tests/mscs-papermc.sh @@ -17,6 +17,11 @@ cat > "$PAPER_PROJECT_JSON" << 'EOF' } EOF +# Fixture: builds JSON for the versions getCurrentPaperVersion will check +cat > "$_paper_test_location/paper_builds_1.21.4.json" << 'EOF' +[{"channel": "STABLE", "downloads": {"server:default": {"url": "https://test.example.com/paper-1.21.4-100.jar", "checksums": {"sha256": "stablechecksum456"}}}}] +EOF + # getCurrentPaperVersion returns the latest non-pre-release from the latest family got=$(getCurrentPaperVersion) want="1.21.4" From 05c15a4481670036e021207ee365618f2fcebc3e Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Thu, 9 Apr 2026 23:00:47 -0500 Subject: [PATCH 11/17] shell linting --- msctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msctl b/msctl index 26a6e94..3a8f12b 100644 --- a/msctl +++ b/msctl @@ -2104,7 +2104,7 @@ updateServerSoftware() { fi ;; *) - SHA1=$(getMinecraftVersionDownloadSHA1 $SERVER_VERSION 'server') + SHA1=$(getMinecraftVersionDownloadSHA1 "$SERVER_VERSION" 'server') if [ -z "$SHA1" ]; then printf "Error retrieving the sha1 for the Minecraft server software.\n" exit 1 @@ -3668,7 +3668,7 @@ case "$COMMAND" in cp -fp "$VERSIONS_JSON" "$VERSIONS_JSON.bak" printf "Removing cached version manifest.\n" fi - rm -f $VERSIONS_JSON + rm -f "$VERSIONS_JSON" # Remove cached PaperMC project information. if [ -s "$PAPER_PROJECT_JSON" ]; then cp -fp "$PAPER_PROJECT_JSON" "$PAPER_PROJECT_JSON.bak" From 2fe18baacdce7951b7cc0a2c4b188db95cfb67f0 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Thu, 9 Apr 2026 23:29:54 -0500 Subject: [PATCH 12/17] use subfolder versions --- msctl | 20 +++++++++++++------- tests/mscs-papermc.sh | 3 +++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/msctl b/msctl index 3a8f12b..e9d6b61 100644 --- a/msctl +++ b/msctl @@ -783,6 +783,7 @@ setServerPropertiesValue() { # Update the version_manifest.json file. # --------------------------------------------------------------------------- updateVersionsJSON() { + mkdir -p "$VERSIONS_LOCATION" if [ -s $VERSIONS_JSON ]; then # Make a backup copy of the version_manifest.json file. cp -p "$VERSIONS_JSON" "$VERSIONS_JSON.bak" @@ -815,6 +816,7 @@ updateVersionsJSON() { # Update the paper_project.json file. # --------------------------------------------------------------------------- updatePaperProjectJSON() { + mkdir -p "$VERSIONS_LOCATION" if [ -s "$PAPER_PROJECT_JSON" ]; then # Make a backup copy of the paper_project.json file. cp -p "$PAPER_PROJECT_JSON" "$PAPER_PROJECT_JSON.bak" @@ -853,6 +855,7 @@ updatePaperProjectJSON() { # --------------------------------------------------------------------------- getCurrentPaperVersion() { local BUILDS_JSON BUILDS_URL CHANNEL HAS_CHANNEL VERSION VERSIONS + mkdir -p "$VERSIONS_LOCATION" CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") # Get all candidate versions from newest family first, skipping pre/rc. VERSIONS=$(cat "$PAPER_PROJECT_JSON" | $PERL -0777 -ne ' @@ -873,7 +876,7 @@ getCurrentPaperVersion() { ') # Find the newest version that has a build in the desired channel. for VERSION in $VERSIONS; do - BUILDS_JSON="$LOCATION/paper_builds_$VERSION.json" + BUILDS_JSON="$VERSIONS_LOCATION/paper_builds_$VERSION.json" BUILDS_URL="$PAPER_VERSIONS_URL/versions/$VERSION/builds" # Download builds JSON for this version if not cached. if [ ! -s "$BUILDS_JSON" ]; then @@ -910,12 +913,13 @@ getCurrentPaperVersion() { # --------------------------------------------------------------------------- updatePaperBuildsJSON() { local PAPER_BUILDS_JSON PAPER_BUILDS_URL SERVER_VERSION + mkdir -p "$VERSIONS_LOCATION" SERVER_VERSION=$(getServerVersion "$1") if [ $? -ne 0 ]; then printf "%s\n" "$SERVER_VERSION" exit 1 fi - PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" + PAPER_BUILDS_JSON="$VERSIONS_LOCATION/paper_builds_$SERVER_VERSION.json" PAPER_BUILDS_URL="$PAPER_VERSIONS_URL/versions/$SERVER_VERSION/builds" if [ -s "$PAPER_BUILDS_JSON" ]; then # Make a backup copy of the paper_builds_VERSION.json file. @@ -961,7 +965,7 @@ getPaperServerURL() { exit 1 fi CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") - PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" + PAPER_BUILDS_JSON="$VERSIONS_LOCATION/paper_builds_$SERVER_VERSION.json" cat "$PAPER_BUILDS_JSON" | $PERL -0777 -sne ' use JSON; my $builds = decode_json ($_); @@ -984,7 +988,7 @@ getPaperServerChecksum() { exit 1 fi CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") - PAPER_BUILDS_JSON="$LOCATION/paper_builds_$SERVER_VERSION.json" + PAPER_BUILDS_JSON="$VERSIONS_LOCATION/paper_builds_$SERVER_VERSION.json" cat "$PAPER_BUILDS_JSON" | $PERL -0777 -sne ' use JSON; my $builds = decode_json ($_); @@ -2807,6 +2811,8 @@ fi LOCATION=$(getDefaultsValue 'mscs-location' $HOME'/mscs') # Override with command-line location option. [ -n "$LOCATION_CL" ] && LOCATION="$LOCATION_CL" +# The location to store version JSON files. +VERSIONS_LOCATION=$(getDefaultsValue 'mscs-versions-location' $LOCATION'/versions') # Global Server Configuration # --------------------------------------------------------------------------- @@ -2818,7 +2824,7 @@ MINECRAFT_VERSIONS_URL=$(getDefaultsValue 'mscs-versions-url' 'https://launcherm # PaperMC Versions information # --------------------------------------------------------------------------- PAPER_VERSIONS_URL=$(getDefaultsValue 'mscs-paper-versions-url' 'https://fill.papermc.io/v3/projects/paper') -PAPER_PROJECT_JSON=$(getDefaultsValue 'mscs-paper-project-json' $LOCATION'/paper_project.json') +PAPER_PROJECT_JSON=$(getDefaultsValue 'mscs-paper-project-json' $VERSIONS_LOCATION'/paper_project.json') DEFAULT_PAPER_CHANNEL=$(getDefaultsValue 'mscs-default-paper-channel' 'STABLE') # Minecraft Server Settings @@ -2902,7 +2908,7 @@ DEFAULT_RESTART_AFTER_CRASH=$(getDefaultsValue 'mscs-default-restart-after-crash # The location to store files for each world server. WORLDS_LOCATION=$(getDefaultsValue 'mscs-worlds-location' $LOCATION'/worlds') # The location to store the version_manifest.json file. -VERSIONS_JSON=$(getDefaultsValue 'mscs-versions-json' $LOCATION'/version_manifest.json') +VERSIONS_JSON=$(getDefaultsValue 'mscs-versions-json' $VERSIONS_LOCATION'/version_manifest.json') # The duration (in minutes) to keep the version_manifest.json file before updating. VERSIONS_DURATION=$(getDefaultsValue 'mscs-versions-duration' '30') # The duration (in minutes) to keep lock files before removing. @@ -3675,7 +3681,7 @@ case "$COMMAND" in printf "Removing cached PaperMC project information.\n" fi rm -f "$PAPER_PROJECT_JSON" - rm -f "$LOCATION"/paper_builds_*.json + rm -f "$VERSIONS_LOCATION"/paper_builds_*.json fi # Grab the latest version information. updateVersionsJSON diff --git a/tests/mscs-papermc.sh b/tests/mscs-papermc.sh index 019bb87..07be39f 100644 --- a/tests/mscs-papermc.sh +++ b/tests/mscs-papermc.sh @@ -4,8 +4,10 @@ _paper_test_location=/tmp/mscs-papertest mkdir -p "$_paper_test_location" _orig_paper_project_json="${PAPER_PROJECT_JSON}" _orig_location="${LOCATION}" +_orig_versions_location="${VERSIONS_LOCATION}" PAPER_PROJECT_JSON="$_paper_test_location/paper_project.json" LOCATION="$_paper_test_location" +VERSIONS_LOCATION="$_paper_test_location" # Fixture: project JSON with mixed releases and pre-releases across two families cat > "$PAPER_PROJECT_JSON" << 'EOF' @@ -126,3 +128,4 @@ fi rm -rf "$_paper_test_location" PAPER_PROJECT_JSON="$_orig_paper_project_json" LOCATION="$_orig_location" +VERSIONS_LOCATION="$_orig_versions_location" From 0344705da791f8e8d91c799d9eccba29491e17c8 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Thu, 9 Apr 2026 23:57:01 -0500 Subject: [PATCH 13/17] update docs for versions folder --- msctl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/msctl b/msctl index e9d6b61..0384533 100644 --- a/msctl +++ b/msctl @@ -194,11 +194,14 @@ mscs_defaults() { ; Location of world files. # mscs-worlds-location=/opt/mscs/worlds +; Location of version JSON cache files. +# mscs-versions-location=/opt/mscs/versions + ; URL to download the version_manifest.json file. # mscs-versions-url=https://launchermeta.mojang.com/mc/game/version_manifest.json ; Location of the version_manifest.json file. -# mscs-versions-json=/opt/mscs/version_manifest.json +# mscs-versions-json=/opt/mscs/versions/version_manifest.json ; Length in minutes to keep the version_manifest.json file before updating. # mscs-versions-duration=30 @@ -207,7 +210,7 @@ mscs_defaults() { # mscs-paper-versions-url=https://fill.papermc.io/v3/projects/paper ; Location of the PaperMC project information file. -# mscs-paper-project-json=/opt/mscs/paper_project.json +# mscs-paper-project-json=/opt/mscs/versions/paper_project.json ; Default PaperMC build channel (STABLE, BETA, or ALPHA). # mscs-default-paper-channel=STABLE From 8a1e79eb7b66a0223fff6ab9fcc88d5e1188d87e Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Thu, 9 Apr 2026 23:57:26 -0500 Subject: [PATCH 14/17] add migration for new versions directory and existing manifest --- update.d/20-versions-subfolder | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 update.d/20-versions-subfolder diff --git a/update.d/20-versions-subfolder b/update.d/20-versions-subfolder new file mode 100644 index 0000000..1b07b00 --- /dev/null +++ b/update.d/20-versions-subfolder @@ -0,0 +1,11 @@ +#!/bin/sh +LOCATION=$(grep -m1 '^mscs-location=' /opt/mscs/mscs.defaults 2>/dev/null | cut -d= -f2) +LOCATION=${LOCATION:-/opt/mscs} +OLD="$LOCATION/version_manifest.json" +NEW_DIR="$LOCATION/versions" +NEW="$NEW_DIR/version_manifest.json" +if [ -s "$OLD" ] && [ ! -s "$NEW" ]; then + printf "Migrating version_manifest.json to versions subfolder.\n" + mkdir -p "$NEW_DIR" + mv "$OLD" "$NEW" +fi From 1efcf00e2a468fb96489f798b567d5920af9c183 Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Fri, 10 Apr 2026 00:04:09 -0500 Subject: [PATCH 15/17] move the .json.bak file too --- update.d/20-versions-subfolder | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/update.d/20-versions-subfolder b/update.d/20-versions-subfolder index 1b07b00..c7465e7 100644 --- a/update.d/20-versions-subfolder +++ b/update.d/20-versions-subfolder @@ -9,3 +9,7 @@ if [ -s "$OLD" ] && [ ! -s "$NEW" ]; then mkdir -p "$NEW_DIR" mv "$OLD" "$NEW" fi +if [ -s "$OLD.bak" ] && [ ! -s "$NEW.bak" ]; then + mkdir -p "$NEW_DIR" + mv "$OLD.bak" "$NEW.bak" +fi From 54c66f2da3ae04190ae0433dd6a23e5cd7f6a2cf Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Fri, 10 Apr 2026 00:19:58 -0500 Subject: [PATCH 16/17] add papermc to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31bd87b..9729396 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Features include: * Run multiple Minecraft worlds. * Start, stop, and restart single or multiple worlds. * Create, delete, disable, and enable worlds. -* Includes support for additional server types: [Forge](http://www.minecraftforge.net/), +* Includes support for additional server types: [PaperMC](http://papermc.io), [Forge](http://www.minecraftforge.net/), [BungeeCord](http://www.spigotmc.org/wiki/bungeecord/), [SpigotMC](http://www.spigotmc.org/wiki/spigot/), etc. * Users automatically notified of important server events. From a65b0cda381b3d167f6246b7819be19b8847bf9a Mon Sep 17 00:00:00 2001 From: SuperChewie Date: Tue, 14 Apr 2026 21:27:44 -0500 Subject: [PATCH 17/17] :bug: get version of specific world --- tests/mscs-papermc.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/mscs-papermc.sh b/tests/mscs-papermc.sh index 07be39f..70c0e75 100644 --- a/tests/mscs-papermc.sh +++ b/tests/mscs-papermc.sh @@ -25,7 +25,7 @@ cat > "$_paper_test_location/paper_builds_1.21.4.json" << 'EOF' EOF # getCurrentPaperVersion returns the latest non-pre-release from the latest family -got=$(getCurrentPaperVersion) +got=$(getCurrentPaperVersion "$testworld") want="1.21.4" if [ "$got" != "$want" ]; then terr "getCurrentPaperVersion: got '$got' want '$want'" @@ -39,7 +39,7 @@ cat > "$PAPER_PROJECT_JSON" << 'EOF' } } EOF -got=$(getCurrentPaperVersion) +got=$(getCurrentPaperVersion "$testworld") want="1.21.4" if [ "$got" != "$want" ]; then terr "getCurrentPaperVersion pre-release-first: got '$got' want '$want'" @@ -117,7 +117,7 @@ EOF cat > "$_paper_test_location/paper_builds_1.21.11.json" << 'EOF' [{"channel": "STABLE", "downloads": {"server:default": {"url": "https://test.example.com/paper-1.21.11-128.jar", "checksums": {"sha256": "stablechecksum"}}}}] EOF -got=$(getCurrentPaperVersion) +got=$(getCurrentPaperVersion "$testworld") want="1.21.11" if [ "$got" != "$want" ]; then terr "getCurrentPaperVersion alpha-family: got '$got' want '$want'"