fix: migrate PaperMC to v3 API, fix NeoForge beta fallback, add test suite

- Migrate Paper loader from sunset v2 API to v3 (fill.papermc.io)
- Fix NeoForge: fall back to beta versions when no stable exists (set -e + grep)
- Fix setEula/setperms ordering (was writing eula.txt before /data was writable)
- Fix setEula/serverInfoWrite to use absolute paths (/data/server/)
- Add VOLUME directive removal (was shadowing bind mounts)
- Add comprehensive API test suite (110 tests across 6 suites)
- Move test.sh to tests/integration.sh, switch to named volumes
- Add Docker build cache to CI workflows
- Add .editorconfig, expand .gitignore, improve README docs
This commit is contained in:
2026-08-21 12:49:27 -04:00
parent a6f610de28
commit 5688b30892
26 changed files with 2530 additions and 263 deletions
+14 -6
View File
@@ -5,29 +5,37 @@ _bedrockDownload() {
local platform="$1"
local version="${2:-}"
log "Fetching Bedrock download URL (platform: ${platform})..."
local DL_URL
DL_URL=$(curl -sL -A "Mozilla/5.0 (X11; Linux x86_64)" "$BEDROCK_API_URL" | jq -r '.result.links[] | select(.downloadType == "'"$platform"'") | .downloadUrl // empty')
DL_URL=$(curl -sL -A "Mozilla/5.0 (X11; Linux x86_64)" --connect-timeout 10 --max-time 30 \
"$BEDROCK_API_URL" |
jq -r ".result.links[] | select(.downloadType == \"${platform}\") | .downloadUrl // empty")
if [[ -z "$DL_URL" ]]; then
DL_URL=$(curl -sL -A "Mozilla/5.0 (X11; Linux x86_64)" "$BEDROCK_FALLBACK_URL" |
warn "Primary API failed, trying fallback source..."
DL_URL=$(curl -sL -A "Mozilla/5.0 (X11; Linux x86_64)" --connect-timeout 10 --max-time 30 \
"$BEDROCK_FALLBACK_URL" |
jq --arg type "$( [[ "$platform" == *Preview* ]] && echo preview || echo release )" -r '
.[$type] | to_entries | sort_by(.key | split(".") | map(tonumber)) | last | .value.linux.url // empty
')
fi
if [[ -z "$DL_URL" ]]; then
echo "Failed to look up Bedrock download URL"
crash
die "Failed to look up Bedrock download URL"
fi
if [[ -n "$version" ]]; then
DL_URL=$(echo "$DL_URL" | sed -E "s/(bedrock-server-)[^/]+(\.zip)/\1${version}\2/")
fi
local VER
VER=$(echo "$DL_URL" | grep -oP 'bedrock-server-\K[\d.]+(?=\.zip)')
echo "$DL_URL"
curl -o bedrock-server.zip -L -A "Mozilla/5.0 (X11; Linux x86_64)" "$DL_URL"
log "Downloading Bedrock server version ${VER}..."
curl -fL -o bedrock-server.zip -A "Mozilla/5.0 (X11; Linux x86_64)" "$DL_URL"
unzip -o bedrock-server.zip
rm -f bedrock-server.zip
chmod +x bedrock_server
log "Bedrock ${VER} downloaded."
}
getBedrock() {
+25 -14
View File
@@ -1,22 +1,33 @@
getFabric() {
SERVER_TYPE="Fabric Minecraft"
LATEST_BUILD=$(curl -s https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION} | jq -r '.[0].loader.version')
LATEST_INSTALLER=$(curl -s https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml | xmllint --xpath "/metadata/versioning/latest/text()" -)
if [[ -z "$MC_VERSION" ]]; then
echo "Minecraft version not set"
exit 1
if [[ -z "${MC_VERSION}" ]]; then
die "MC_VERSION is required for Fabric loader"
fi
VER=$MC_VERSION
log "Fetching Fabric loader version for MC ${MC_VERSION}..."
local LATEST_BUILD LATEST_INSTALLER
LATEST_BUILD=$(curl -sf --connect-timeout 10 --max-time 30 \
"https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}" |
jq -r '.[0].loader.version')
LATEST_INSTALLER=$(curl -sf --connect-timeout 10 --max-time 30 \
"https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml" |
xmllint --xpath "/metadata/versioning/latest/text()" -)
if [[ -z "$LATEST_BUILD" || "$LATEST_BUILD" == "null" ]]; then
die "No Fabric loader found for MC ${MC_VERSION}"
fi
if [[ -z "$LATEST_INSTALLER" ]]; then
die "Could not determine latest Fabric installer version"
fi
local VER_LOADER
VER_LOADER="${FABRIC_LOADER_VERSION:-${LATEST_BUILD}}"
DL_URL=https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${VER_LOADER}/${LATEST_INSTALLER}/server/jar
local DL_URL
DL_URL="https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${VER_LOADER}/${LATEST_INSTALLER}/server/jar"
if [ "$LATEST_BUILD" != "null" ]; then
echo "$DL_URL"
curl -o server.jar "${DL_URL}"
else
echo "No valid Fabric build found for version ${MC_VERSION}"
crash
fi
log "Downloading Fabric (loader ${VER_LOADER}, installer ${LATEST_INSTALLER})..."
curl -fL -o server.jar "${DL_URL}"
log "Fabric ${MC_VERSION} (loader ${VER_LOADER}) downloaded."
}
+35 -32
View File
@@ -1,42 +1,45 @@
getForge() {
SERVER_TYPE="Forge Server"
if [[ -n "$MC_VERSION" ]]; then
VER_MINECRAFT=$MC_VERSION
if [[ -n "${FORGE_VERSION}" ]]; then
VER_LOADER="${FORGE_VERSION}"
DL_URL=https://maven.minecraftforge.net/net/minecraftforge/forge/${VER_MINECRAFT}-${VER_LOADER}/forge-${VER_MINECRAFT}-${VER_LOADER}-installer.jar
else
LATEST_BUILD=$(curl -s https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json | jq -r ".promos[\"${VER_MINECRAFT}-latest\"]")
VER_LOADER=$LATEST_BUILD
DL_URL=https://maven.minecraftforge.net/net/minecraftforge/forge/${VER_MINECRAFT}-${VER_LOADER}/forge-${VER_MINECRAFT}-${VER_LOADER}-installer.jar
fi
else
echo "Minecraft version not set"
exit 1
if [[ -z "${MC_VERSION}" ]]; then
die "MC_VERSION is required for Forge loader"
fi
if [[ -n "$DL_URL" ]]; then
echo "$DL_URL"
rm -rf server.jar
curl -o forge-install.jar "${DL_URL}"
echo "$JAVA -jar forge-install.jar --installServer"
$JAVA -jar forge-install.jar --installServer
local DL_URL
if [[ -n "${FORGE_VERSION}" ]]; then
VER_LOADER="${FORGE_VERSION}"
DL_URL="https://maven.minecraftforge.net/net/minecraftforge/forge/${MC_VERSION}-${VER_LOADER}/forge-${MC_VERSION}-${VER_LOADER}-installer.jar"
else
log "Fetching latest Forge version for MC ${MC_VERSION}..."
local LATEST_BUILD
LATEST_BUILD=$(curl -sf --connect-timeout 10 --max-time 30 \
"https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json" |
jq -r ".promos[\"${MC_VERSION}-latest\"]")
if [[ "$(printf '%s\n' "1.17.1" "$MC_VERSION" | sort -V | head -n1)" = "1.17.1" ]]; then
NEW_FORGE=true
rm -f forge-install.jar
rm -f forge-install.jar.log
else
NEW_FORGE=false
rm -f forge-install.jar
rm -f forge-install.jar.log
ln -s forge-"${VER_MINECRAFT}"-"${VER_LOADER}".jar server.jar
if [[ -z "$LATEST_BUILD" || "$LATEST_BUILD" == "null" ]]; then
die "No Forge version found for MC ${MC_VERSION}"
fi
touch server.properties
else
echo "No Valid Download URL"
crash
VER_LOADER="$LATEST_BUILD"
DL_URL="https://maven.minecraftforge.net/net/minecraftforge/forge/${MC_VERSION}-${VER_LOADER}/forge-${MC_VERSION}-${VER_LOADER}-installer.jar"
fi
log "Downloading Forge ${MC_VERSION}-${VER_LOADER} installer..."
rm -f server.jar
curl -fL -o forge-install.jar "${DL_URL}"
log "Running Forge installer..."
$JAVA -jar forge-install.jar --installServer
newForge
rm -f forge-install.jar forge-install.jar.log
if [[ "${NEW_FORGE}" == "true" ]]; then
log "Forge ${MC_VERSION} installed (new-style launcher)."
else
ln -sf "forge-${MC_VERSION}-${VER_LOADER}.jar" server.jar
log "Forge ${MC_VERSION}-${VER_LOADER} installed."
fi
touch server.properties
}
+43 -28
View File
@@ -1,40 +1,55 @@
getNeoForge() {
SERVER_TYPE="NeoForge Server"
if [[ -n "$MC_VERSION" ]]; then
VER_MINECRAFT=$MC_VERSION
if [[ -n "${NEOFORGE_VERSION}" ]]; then
VER_LOADER="${NEOFORGE_VERSION}"
else
MC_MINOR="${MC_VERSION#1.}"
VER_LOADER=$(curl -s https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml | \
xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null | \
tr ' ' '\n' | grep -v -- '-beta\|-alpha' | grep "^${MC_MINOR}\." | sort -V | tail -1)
if [[ -z "$VER_LOADER" ]]; then
echo "Could not auto-detect NeoForge version for MC ${MC_VERSION}"
crash
if [[ -z "${MC_VERSION}" ]]; then
die "MC_VERSION is required for NeoForge loader"
fi
local VER_MINECRAFT DL_URL
VER_MINECRAFT="${MC_VERSION}"
if [[ -n "${NEOFORGE_VERSION}" ]]; then
VER_LOADER="${NEOFORGE_VERSION}"
else
log "Auto-detecting latest NeoForge version for MC ${MC_VERSION}..."
local MC_MINOR ALL_VERSIONS
MC_MINOR="${MC_VERSION#1.}"
ALL_VERSIONS=$(curl -sf --connect-timeout 10 --max-time 30 \
"https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml" |
xmllint --xpath "/metadata/versioning/versions/version/text()" - 2>/dev/null |
tr ' ' '\n' | grep "^${MC_MINOR}\." | sort -V || true)
# Prefer stable versions, fall back to beta/alpha if none exist
VER_LOADER=$(echo "$ALL_VERSIONS" | grep -v -- '-beta\|-alpha' | tail -1 || true)
if [[ -z "$VER_LOADER" ]]; then
VER_LOADER=$(echo "$ALL_VERSIONS" | tail -1)
if [[ -n "$VER_LOADER" ]]; then
warn "No stable NeoForge version for MC ${MC_VERSION}, using ${VER_LOADER}"
fi
fi
DL_URL=https://maven.neoforged.net/releases/net/neoforged/neoforge/$VER_LOADER/neoforge-$VER_LOADER-installer.jar
if [[ -z "$VER_LOADER" ]]; then
die "Could not auto-detect NeoForge version for MC ${MC_VERSION}"
fi
fi
if [[ -n "$DL_URL" ]]; then
echo "$DL_URL"
rm -rf server.jar
DL_URL="https://maven.neoforged.net/releases/net/neoforged/neoforge/${VER_LOADER}/neoforge-${VER_LOADER}-installer.jar"
curl -o "neoforge-${VER_LOADER}-installer.jar" "${DL_URL}"
$JAVA -jar neoforge-$VER_LOADER-installer.jar --installServer --server.jar
log "Downloading NeoForge ${VER_LOADER} installer..."
rm -f server.jar
curl -fL -o "neoforge-${VER_LOADER}-installer.jar" "${DL_URL}"
mv server.jar neoforge-$VER_LOADER.jar
ln -s neoforge-$VER_LOADER.jar server.jar
log "Running NeoForge installer..."
$JAVA -jar "neoforge-${VER_LOADER}-installer.jar" --installServer --server.jar
touch server.properties
mv server.jar "neoforge-${VER_LOADER}.jar"
ln -sf "neoforge-${VER_LOADER}.jar" server.jar
rm -f neoforge-$VER_LOADER-installer.jar
rm -f neoforge-$VER_LOADER-installer.jar.log
rm -f README.txt
rm -f run.bat
else
echo "No Valid Download URL"
fi
rm -f "neoforge-${VER_LOADER}-installer.jar" \
"neoforge-${VER_LOADER}-installer.jar.log" \
README.txt \
run.bat
touch server.properties
log "NeoForge ${MC_VERSION}-${VER_LOADER} installed."
}
+51 -15
View File
@@ -1,23 +1,59 @@
getPaper() {
SERVER_TYPE="Paper Minecraft"
if [[ ! -f server.jar ]]; then
if [[ -n "${PAPER_BUILD}" ]]; then
LATEST_BUILD="${PAPER_BUILD}"
else
LATEST_BUILD=$(curl -s https://api.papermc.io/v2/projects/${MC_LOADER}/versions/${MC_VERSION}/builds |
jq -r '[.builds[] | select(.channel | ascii_upcase == "STABLE")] | .[-1].build')
if [[ -z "${MC_VERSION}" ]]; then
die "MC_VERSION is required for Paper loader"
fi
if [[ -f server.jar ]]; then
log "Paper server.jar already exists, skipping download."
return
fi
local PAPERMC_URL
if [[ -n "${PAPER_BUILD}" ]]; then
log "Fetching Paper build ${PAPER_BUILD} for MC ${MC_VERSION}..."
local BUILD_DATA
BUILD_DATA=$(curl -sf --connect-timeout 10 --max-time 30 \
-H "User-Agent: dockercraft/1.0" \
"https://fill.papermc.io/v3/projects/paper/versions/${MC_VERSION}/builds") || true
if [[ -n "$BUILD_DATA" ]]; then
PAPERMC_URL=$(echo "$BUILD_DATA" | jq -r \
"map(select(.id == ${PAPER_BUILD})) | .[0].downloads.\"server:default\".url // empty")
fi
if [ "$LATEST_BUILD" != "null" ]; then
JAR_NAME=${MC_LOADER}-${MC_VERSION}-${LATEST_BUILD}.jar
PAPERMC_URL="https://api.papermc.io/v2/projects/${MC_LOADER}/versions/${MC_VERSION}/builds/${LATEST_BUILD}/downloads/${JAR_NAME}"
if [[ -z "$PAPERMC_URL" ]]; then
die "Paper build ${PAPER_BUILD} not found for MC ${MC_VERSION}"
fi
else
log "Querying PaperMC API for latest stable build of ${MC_VERSION}..."
local BUILDS_DATA
BUILDS_DATA=$(curl -sf --connect-timeout 10 --max-time 30 \
-H "User-Agent: dockercraft/1.0" \
"https://fill.papermc.io/v3/projects/paper/versions/${MC_VERSION}/builds") || true
curl -o server.jar "$PAPERMC_URL"
echo "Download completed"
echo "Minecraft Version: ${MC_VERSION}"
echo "Paper Version: ${LATEST_BUILD}"
else
echo "No stable build for version $MC_VERSION found :("
if [[ -z "$BUILDS_DATA" ]]; then
die "Could not fetch Paper builds for MC ${MC_VERSION}"
fi
# Check for API error
if echo "$BUILDS_DATA" | jq -e '.ok == false' >/dev/null 2>&1; then
local ERR_MSG
ERR_MSG=$(echo "$BUILDS_DATA" | jq -r '.message // "Unknown error"')
die "PaperMC API error: ${ERR_MSG}"
fi
PAPERMC_URL=$(echo "$BUILDS_DATA" | jq -r \
'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty')
if [[ -z "$PAPERMC_URL" ]]; then
die "No stable Paper build found for MC ${MC_VERSION}"
fi
fi
log "Downloading Paper for MC ${MC_VERSION}..."
curl -fL -o server.jar "$PAPERMC_URL"
log "Paper for MC ${MC_VERSION} downloaded."
}
+34 -9
View File
@@ -1,13 +1,38 @@
getVanilla() {
SERVER_TYPE="Vanilla Minecraft"
MANIFEST=$(curl -s https://launchermeta.mojang.com/mc/game/version_manifest.json)
VERSION_URL=$(echo "$MANIFEST" | jq -r ".versions[] | select(.id == \"${MC_VERSION}\") | .url")
if [[ -n "$VERSION_URL" ]] && [[ "$VERSION_URL" != "null" ]]; then
VERSION_DATA=$(curl -s "$VERSION_URL")
DOWNLOAD_URL=$(echo "$VERSION_DATA" | jq -r ".downloads.server.url")
curl -o server.jar "$DOWNLOAD_URL"
else
echo "Version ${MC_VERSION} not found in Mojang manifest"
crash
if [[ -z "${MC_VERSION}" ]]; then
die "MC_VERSION is required for Vanilla loader"
fi
log "Fetching Mojang version manifest..."
local MANIFEST
MANIFEST=$(curl -sf --connect-timeout 10 --max-time 30 \
"https://launchermeta.mojang.com/mc/game/version_manifest.json") || true
if [[ -z "$MANIFEST" ]]; then
die "Could not fetch Mojang version manifest"
fi
local VERSION_URL
VERSION_URL=$(echo "$MANIFEST" | jq -r ".versions[] | select(.id == \"${MC_VERSION}\") | .url")
if [[ -z "$VERSION_URL" || "$VERSION_URL" == "null" ]]; then
die "Version ${MC_VERSION} not found in Mojang manifest"
fi
local VERSION_DATA DOWNLOAD_URL
VERSION_DATA=$(curl -sf --connect-timeout 10 --max-time 30 "$VERSION_URL") || true
if [[ -z "$VERSION_DATA" ]]; then
die "Could not fetch version data for ${MC_VERSION}"
fi
DOWNLOAD_URL=$(echo "$VERSION_DATA" | jq -r ".downloads.server.url")
if [[ -z "$DOWNLOAD_URL" || "$DOWNLOAD_URL" == "null" ]]; then
die "No server download URL found for ${MC_VERSION}"
fi
log "Downloading Vanilla ${MC_VERSION} server..."
curl -fL -o server.jar "$DOWNLOAD_URL"
log "Vanilla ${MC_VERSION} downloaded."
}