diff --git a/README.md b/README.md index 31bd87b0..9729396b 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. diff --git a/msctl b/msctl index 5ec3a222..0384533f 100644 --- a/msctl +++ b/msctl @@ -194,15 +194,27 @@ 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 +; 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/versions/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 @@ -774,6 +786,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" @@ -802,6 +815,218 @@ updateVersionsJSON() { fi } +# --------------------------------------------------------------------------- +# 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" + # 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 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 ' + use JSON; + $json = decode_json ($_); + 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}}; + 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"; + } + ') + # Find the newest version that has a build in the desired channel. + for VERSION in $VERSIONS; do + 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 + $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 +} + +# --------------------------------------------------------------------------- +# 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 + mkdir -p "$VERSIONS_LOCATION" + SERVER_VERSION=$(getServerVersion "$1") + if [ $? -ne 0 ]; then + printf "%s\n" "$SERVER_VERSION" + exit 1 + fi + 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. + 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 "%s\n" "$SERVER_VERSION" + exit 1 + fi + CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") + PAPER_BUILDS_JSON="$VERSIONS_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 "%s\n" "$SERVER_VERSION" + exit 1 + fi + CHANNEL=$(getMSCSValue "$1" "mscs-paper-channel" "$DEFAULT_PAPER_CHANNEL") + PAPER_BUILDS_JSON="$VERSIONS_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"}{checksums}{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 +1240,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 +1261,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 +1272,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 +1296,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 +1323,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 +1341,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 +2052,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" @@ -1839,6 +2076,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. @@ -1848,15 +2097,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 } @@ -2549,6 +2814,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 # --------------------------------------------------------------------------- @@ -2557,6 +2824,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' $VERSIONS_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 +2871,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,13 +2903,15 @@ 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 # --------------------------------------------------------------------------- # 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. @@ -2739,6 +3016,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 @@ -2830,6 +3111,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:" @@ -3036,6 +3321,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 @@ -3388,7 +3677,14 @@ 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" + printf "Removing cached PaperMC project information.\n" + fi + rm -f "$PAPER_PROJECT_JSON" + rm -f "$VERSIONS_LOCATION"/paper_builds_*.json fi # Grab the latest version information. updateVersionsJSON @@ -3411,6 +3707,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:" diff --git a/tests/mscs-config-ops.sh b/tests/mscs-config-ops.sh new file mode 100644 index 00000000..317f98de --- /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" diff --git a/tests/mscs-papermc.sh b/tests/mscs-papermc.sh new file mode 100644 index 00000000..70c0e75c --- /dev/null +++ b/tests/mscs-papermc.sh @@ -0,0 +1,131 @@ +#!/bin/sh + +_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' +{ + "versions": { + "1.20": ["1.20.4", "1.20.2"], + "1.21": ["1.21.4", "1.21.1", "1.21-pre2", "1.21-rc1"] + } +} +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 "$testworld") +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 "$testworld") +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 +) + +# 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 "$testworld") +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" +PAPER_PROJECT_JSON="$_orig_paper_project_json" +LOCATION="$_orig_location" +VERSIONS_LOCATION="$_orig_versions_location" diff --git a/tests/mscs-server-version.sh b/tests/mscs-server-version.sh new file mode 100644 index 00000000..35be31ac --- /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" diff --git a/tests/mscs-vanilla-version.sh b/tests/mscs-vanilla-version.sh new file mode 100644 index 00000000..110dea50 --- /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" diff --git a/tests/mscs-world-lifecycle.sh b/tests/mscs-world-lifecycle.sh new file mode 100644 index 00000000..75967184 --- /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" diff --git a/update.d/20-versions-subfolder b/update.d/20-versions-subfolder new file mode 100644 index 00000000..c7465e7f --- /dev/null +++ b/update.d/20-versions-subfolder @@ -0,0 +1,15 @@ +#!/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 +if [ -s "$OLD.bak" ] && [ ! -s "$NEW.bak" ]; then + mkdir -p "$NEW_DIR" + mv "$OLD.bak" "$NEW.bak" +fi