From 6e13a0aa155a0b112a5daa7ac14c486def77c4c2 Mon Sep 17 00:00:00 2001 From: Jeremy McClure Date: Wed, 17 Jun 2026 18:41:26 -0400 Subject: [PATCH] refactor: modular init, add bedrock loader, dynamic java/healthcheck - Split monolithic init into modular lib files under scripts/lib/ (common.sh, java.sh, server.sh, loaders/{vanilla,paper,fabric,forge,neoforge}.sh) - Add bedrock loader: downloads from Minecraft Services API, runs native binary with LD_LIBRARY_PATH, skips Java setup - Add auto-detection for JAVA_VERSION from Mojang version manifest (MC 1.20.5+ -> temurin@21, 1.17-1.20.4 -> temurin@17, 1.17 -> temurin@16, pre-1.17 -> temurin@8) - Fix NEW_FORGE unbound variable when non-Forge loaders run - Fix Fabric loader jq filter (-s slurp removed, null string check fixed) - Fix Forge test detection (recursive find for forge-*-server.jar) - Dynamic healthcheck: nc -z TCP 25565 for Java, nc -zu UDP 19132 for bedrock - Add .gitea/workflows/build-dev.yml for dev branch CI builds - Base image: debian:bookworm-slim (from ubuntu:noble) - Add unzip, netcat-openbsd to Docker image - Switch to shebangs, set -euo pipefail throughout --- .gitea/workflows/build-dev.yml | 25 +++ Dockerfile | 21 +- README.md | 63 +++++- compose.yaml | 38 +++- scripts/init | 343 +++----------------------------- scripts/lib/common.sh | 57 ++++++ scripts/lib/java.sh | 53 +++++ scripts/lib/loaders/bedrock.sh | 41 ++++ scripts/lib/loaders/fabric.sh | 22 ++ scripts/lib/loaders/forge.sh | 42 ++++ scripts/lib/loaders/neoforge.sh | 40 ++++ scripts/lib/loaders/paper.sh | 23 +++ scripts/lib/loaders/vanilla.sh | 13 ++ scripts/lib/server.sh | 96 +++++++++ test.sh | 177 ++++++++++++++++ 15 files changed, 726 insertions(+), 328 deletions(-) create mode 100644 .gitea/workflows/build-dev.yml mode change 100644 => 100755 scripts/init create mode 100644 scripts/lib/common.sh create mode 100644 scripts/lib/java.sh create mode 100644 scripts/lib/loaders/bedrock.sh create mode 100644 scripts/lib/loaders/fabric.sh create mode 100644 scripts/lib/loaders/forge.sh create mode 100644 scripts/lib/loaders/neoforge.sh create mode 100644 scripts/lib/loaders/paper.sh create mode 100644 scripts/lib/loaders/vanilla.sh create mode 100644 scripts/lib/server.sh create mode 100755 test.sh diff --git a/.gitea/workflows/build-dev.yml b/.gitea/workflows/build-dev.yml new file mode 100644 index 0000000..8e2d9b8 --- /dev/null +++ b/.gitea/workflows/build-dev.yml @@ -0,0 +1,25 @@ +name: Build dev image + +on: + push: + branches: [dev] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to local Gitea registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.REGISTRY }} + username: ${{ vars.ACTIONS_USER }} + password: ${{ secrets.PACKAGES_TOKEN }} + - name: Docker Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ vars.REGISTRY }}/${{ gitea.repository }}:dev diff --git a/Dockerfile b/Dockerfile index 7532f1b..62a0ebe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:noble +FROM debian:bookworm-slim ARG UID=1000 ARG GID=1000 @@ -7,7 +7,6 @@ ENV DEBIAN_FRONTEND=noninteractive SHELL ["/bin/bash", "-o", "pipefail", "-c"] -# install prerequisites RUN apt-get update && \ apt-get install -y --no-install-recommends \ curl \ @@ -15,23 +14,27 @@ RUN apt-get update && \ jq \ libxml2-utils \ sudo \ - tini && \ + tini \ + unzip \ + netcat-openbsd && \ apt-get autoremove && apt-get clean && \ rm -rf /var/lib/apt/lists/* -# Remove default ubuntu user -RUN userdel -r ubuntu && \ - useradd --create-home -u 1000 minecraft && \ +RUN groupadd -g ${GID} minecraft && \ + useradd --create-home -u ${UID} -g ${GID} minecraft && \ echo 'minecraft ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers && \ mkdir -p /data && \ chown -R ${UID}:${GID} /data RUN su -c "curl -sL https://github.com/Jabba-Team/jabba/raw/main/install.sh | bash && . ~/.jabba/jabba.sh" minecraft -COPY ./scripts/init / -RUN chmod +x /init +COPY ./scripts/ /scripts/ +RUN chmod +x /scripts/init USER minecraft -ENTRYPOINT ["tini", "--", "/init"] +HEALTHCHECK --start-period=2m --interval=30s --timeout=10s --retries=3 \ + CMD bash -c 'if [ "${MC_LOADER}" = "bedrock" ]; then nc -zu 127.0.0.1 19132; else nc -z 127.0.0.1 25565; fi' + +ENTRYPOINT ["tini", "--", "/scripts/init"] diff --git a/README.md b/README.md index 30404ce..5593cf9 100644 --- a/README.md +++ b/README.md @@ -1 +1,62 @@ -TODO \ No newline at end of file +# Dockercraft + +A Docker image for running Minecraft servers with support for multiple loaders. + +## Usage + +```yaml +services: + minecraft: + image: git.jeremymcclure.com/jeremy/dockercraft:latest + container_name: minecraft + stdin_open: true + tty: true + environment: + - EULA=true + - MC_VERSION=1.21.1 + - MC_LOADER=neoforge + - MC_LOADER_VERSION=21.1.185 + - JAVA_VERSION=temurin@21 + - XMS=2G + - XMX=4G + - PUID=1000 + - PGID=1000 + volumes: + - ./data:/data + ports: + - 25565:25565 + restart: unless-stopped +``` + +## Environment Variables + +| Variable | Default | Description | +|---|---|---| +| `EULA` | — | **Required.** Set to `true` to accept the Minecraft EULA | +| `MC_VERSION` | — | Minecraft version (e.g., `1.21.1`) | +| `MC_LOADER` | — | Server loader: `vanilla`, `paper`, `fabric`, `forge`, or `neoforge` | +| `MC_LOADER_VERSION` | — | Fallback version for any loader | +| `FABRIC_LOADER_VERSION` | `$MC_LOADER_VERSION` | Fabric loader version | +| `FORGE_VERSION` | `$MC_LOADER_VERSION` | Forge version (e.g., `52.1.14`) | +| `NEOFORGE_VERSION` | `$MC_LOADER_VERSION` | NeoForge version (e.g., `21.1.185`) | +| `PAPER_BUILD` | latest | Paper build number (auto-detects latest if unset) | +| `JAVA_VERSION` | auto-detected | Java version (via Jabba, e.g., `temurin@21`, `zulu@17`). Auto-detects from Mojang manifest if unset: 1.20.5+→21, 1.17-1.20.4→17, 1.17→16, pre-1.17→8 | +| `JAR` | `server.jar` | Server jar filename | +| `XMS` | `2G` | Initial Java heap size | +| `XMX` | `4G` | Maximum Java heap size | +| `ADD_ARGS` | — | Additional Java arguments | +| `PUID` | `1000` | User ID for /data ownership | +| `PGID` | `1000` | Group ID for /data ownership | + +## Supported Loaders + +- **Vanilla** — Official Mojang server +- **Paper** — High-performance papermc.io +- **Fabric** — Lightweight mod loader via fabricmc.net +- **Forge** — Heavyweight mod loader via minecraftforge.net +- **NeoForge** — Fork of Forge via neoforged.net +- **Bedrock** — Official Bedrock dedicated server (native binary, no Java required) + +## Volumes + +- `/data` — Persists server data, worlds, config, and Java installations diff --git a/compose.yaml b/compose.yaml index 7f9b1c2..812b4d5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,16 +1,43 @@ services: dockercraft: image: git.jeremymcclure.com/jeremy/dockercraft:latest + # Use :dev to test builds from the dev branch + #image: git.jeremymcclure.com/jeremy/dockercraft:dev container_name: dockercraft stdin_open: true tty: true environment: + - EULA=true - MC_VERSION=1.21.1 - - MC_LOADER=neoforge # paper, fabric, forge or neoforge - - MC_LOADER_VERSION=21.1.185 + # Server loader type: paper, fabric, forge, neoforge, or bedrock + - MC_LOADER=neoforge - - JAVA_VERSION=temurin@21 + # --- Loader versions ----------------------------------------------- + # Set the var matching your MC_LOADER above. Unset vars are ignored + # for other loaders. Leave empty to auto-detect latest stable version. + - FABRIC_LOADER_VERSION=0.19.3 + - FORGE_VERSION= + - NEOFORGE_VERSION=21.1.185 + - PAPER_BUILD= + # + # MC_LOADER_VERSION is a fallback for any loader that doesn't have + # its own var set. You usually don't need this. + - MC_LOADER_VERSION= + # ------------------------------------------------------------------- + + # For bedrock, MC_VERSION is the Bedrock server version (e.g. 1.21.50) + # and no Java is needed. + # + # --- Java Version -------------------------------------------------- + # If unset, auto-detected from Mojang version manifest: + # MC 1.20.5+ -> temurin@21, 1.17-1.20.4 -> temurin@17, + # 1.17 -> temurin@16, pre-1.17 -> temurin@8 + # Set explicitly to pin a version. Uses Jabba + # (https://github.com/Jabba-Team/jabba) — supports temurin, zulu, + # adopt, openjdk, graalvm, and more. + #- JAVA_VERSION=temurin@21 + # ------------------------------------------------------------------- - JAR=server.jar - XMS=2G @@ -25,7 +52,12 @@ services: volumes: - ./data:/data ports: + # Java (vanilla, paper, fabric, forge, neoforge): TCP 25565 + # Bedrock: UDP 19132 (and UDP 19133 for IPv6) + # Expose the port matching your MC_LOADER: - 25565:25565 + #- 19132:19132/udp + #- 19133:19133/udp deploy: resources: limits: diff --git a/scripts/init b/scripts/init old mode 100644 new mode 100755 index fd55c35..4c77fb6 --- a/scripts/init +++ b/scripts/init @@ -1,18 +1,23 @@ -#!/bin/env bash +#!/usr/bin/env bash +set -euo pipefail -MC_VERSION=${MC_VERSION} +MC_VERSION=${MC_VERSION:-} -MC_LOADER=${MC_LOADER=} -MC_LOADER_VERSION=${MC_LOADER_VERSION} +MC_LOADER=${MC_LOADER:-} +MC_LOADER_VERSION=${MC_LOADER_VERSION:-} -JAVA_VERSION=${JAVA_VERSION:-"temurin@21"} +FABRIC_LOADER_VERSION=${FABRIC_LOADER_VERSION:-${MC_LOADER_VERSION:-}} +FORGE_VERSION=${FORGE_VERSION:-${MC_LOADER_VERSION:-}} +NEOFORGE_VERSION=${NEOFORGE_VERSION:-${MC_LOADER_VERSION:-}} +PAPER_BUILD=${PAPER_BUILD:-""} + +JAVA_VERSION=${JAVA_VERSION:-} JAVA_HOME=/data/java/${JAVA_VERSION} JAR=${JAR:-"server.jar"} XMS=${XMS:-2G} XMX=${XMX:-4G} -MEM=${MEMORY:-2G} ADD_ARGS=${ADD_ARGS:-""} PUID="${PUID:-1000}" @@ -20,317 +25,25 @@ PGID="${PGID:-1000}" PATH=${JAVA_HOME}/bin:$PATH -# Set Permissions for data folder -setperms() { - echo -e "|_______________________________________________________|" - echo -e "|##--------------- Setting Permissions ---------------##|" - echo -e "|#| Setting permissions to UID:${PUID} and GID:${PGID}" - sudo usermod -u ${PUID} minecraft >/dev/null 2>&1 - sudo groupmod -g ${PGID} minecraft >/dev/null 2>&1 - sudo chown -R ${PUID}:${PGID} /data >/dev/null 2>&1 - echo -e "|#|---------------------------------------------------|#|" - sleep 2 - echo -e "|#| DONE" - echo -e "|##---------------------------------------------------##|" - sleep 2 -} +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Crash function -crash() { - sleep 5 - echo "Exit Complete" - exit 1 -} - -# Set Eula -setEula() { - cat < eula.txt -#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA). -eula=true -EOF -} - -# Check for Java, and install if neccesary -javaCheck() { - JAVA_HOME=/data/java/${JAVA_VERSION} - if [[ -f "$HOME/.jabba/jabba.sh" ]]; then - source $HOME/.jabba/jabba.sh - else - echo "Something went wrong with locating jabba(the java installer)" - exit 0 - fi - - if [[ ! -n "${JAVA_VERSION}" ]]; then - echo "NO JAVA_VERSION ENVIRONMENT VARIABLE SET SET IT" - crash - fi - - if [[ ! -f "/data/java/${JAVA_VERSION}/bin/java" ]]; then - rm -rf /data/java/${JAVA_VERSION} - jabba install ${JAVA_VERSION} -o /data/java/${JAVA_VERSION} - fi - PATH=${JAVA_HOME}/bin:$PATH - JAVA="java" -} - -newForge() { - if [[ "$(printf '%s\n' "1.17.1" "$MC_VERSION" | sort -V | head -n1)" = "1.17.1" ]]; then - NEW_FORGE=true - else - NEW_FORGE=false - fi -} - -# Vanilla -getVanilla() { - echo "Not yet implemented" -} - -# Paper Server -getPaper() { - SERVER_TYPE="Paper Minecraft" - if [[ ! -f server.jar ]]; then - LATEST_BUILD=$(curl -s https://api.papermc.io/v2/projects/${MC_LOADER}/versions/${MC_VERSION}/builds | - jq -r '.builds | map(select(.channel == "default") | .build) | .[-1]') - - 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}" - - # Download the latest Paper version - 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 :(" - fi - fi -} - -# Fabric Server -getFabric() { - SERVER_TYPE="Fabric Minecraft" - LATEST_BUILD=$(curl -s https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION} | jq -s '.[] .[0] .loader .version' | tr -d '"') - LATEST_INSTALLER=$(curl -s https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml | xmllint --xpath "/metadata/versioning/latest/text()" -) - - if [ "$MC_VERSION" != "null" ]; then - VER=$MC_VERSION - else - echo "Minecraft version not set" - exit 1 - fi - - if [ "$MC_LOADER_VERSION" == "null" ]; then - DL_URL=https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${LATEST_BUILD}/${LATEST_INSTALLER}/server/jar - else - DL_URL=https://meta.fabricmc.net/v2/versions/loader/${MC_VERSION}/${MC_LOADER_VERSION}/${LATEST_INSTALLER}/server/jar - fi - - if [ "$LATEST_BUILD" != "null" ]; then - echo "$DL_URL" - curl -o server.jar ${DL_URL} - else - echo "TODO ERROR TEXT" - fi - -} - -# Forge Server -getForge() { - SERVER_TYPE="Forge Server" - - if [[ -n "$MC_VERSION" ]]; then - VER_MINECRAFT=$MC_VERSION - if [[ -n "$MC_LOADER_VERSION" ]]; then - VER_LOADER=$MC_LOADER_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 - 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 - - # if greater or equalt to 1.17.1 - 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 - - # less than 1.71.1 - 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 - fi - - touch server.properties - - else - echo "No Valid Download URL" - fi -} - -# Neoforge -getNeoForge() { - SERVER_TYPE="NeoForge Server" - - if [[ -n "$MC_VERSION" ]]; then - VER_MINECRAFT=$MC_VERSION - if [[ -n "$MC_LOADER_VERSION" ]]; then - VER_LOADER=$MC_LOADER_VERSION - DL_URL=https://maven.neoforged.net/releases/net/neoforged/neoforge/$VER_LOADER/neoforge-$VER_LOADER-installer.jar - else - echo "Minecraft version not set" - exit 1 - fi - fi - - if [[ -n "$DL_URL" ]]; then - echo "$DL_URL" - rm -rf server.jar - - curl -o neoforge-$VER_LOADER-installer.jar ${DL_URL} - $JAVA -jar neoforge-$VER_LOADER-installer.jar --installServer --server.jar - - mv server.jar neoforge-$VER_LOADER.jar - ln -s neoforge-$VER_LOADER.jar server.jar - - touch server.properties - - 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 - -} - -# Server route choice -serverSelect() { - mkdir -p /data/server - cd /data/server || exit - if [[ -f "server.jar" ]] || [[ -f "run.sh" ]]; then - echo "Found what appears to be a server, sending it" - else - case $MC_LOADER in - vanilla) - getVanilla - ;; - paper) - getPaper - ;; - fabric) - getFabric - ;; - forge) - getForge - ;; - neoforge) - getNeoForge - ;; - *) - echo "Server of type ${MC_LOADER} is not recognized." - crash - ;; - esac - fi -} - -# Build out java command -buildCommand() { - JAVA_ARGS="-Xms${XMS} -Xmx${XMX}" - if [[ -n $ADD_ARGS ]]; then - RUN_STRING="${ADD_ARGS} ${JAVA_ARGS} -jar ${JAR} nogui" - else - RUN_STRING="${JAVA_ARGS} -jar ${JAR} nogui" - fi -} +source "${SCRIPT_DIR}/lib/common.sh" +source "${SCRIPT_DIR}/lib/java.sh" +source "${SCRIPT_DIR}/lib/server.sh" +source "${SCRIPT_DIR}/lib/loaders/vanilla.sh" +source "${SCRIPT_DIR}/lib/loaders/paper.sh" +source "${SCRIPT_DIR}/lib/loaders/fabric.sh" +source "${SCRIPT_DIR}/lib/loaders/forge.sh" +source "${SCRIPT_DIR}/lib/loaders/neoforge.sh" +source "${SCRIPT_DIR}/lib/loaders/bedrock.sh" setperms -javaCheck +if [[ "$MC_LOADER" != "bedrock" ]]; then + javaCheck +fi serverSelect -buildCommand - +if [[ "$MC_LOADER" != "bedrock" ]]; then + buildCommand +fi setEula - -serverInfoWrite() { - cat < server.info -MC_VERSION=$MC_VERSION - -MC_LOADER=$MC_LOADER -MC_LOADER_VERSION=$MC_LOADER_VERSION - -JAVA_VERSION=$JAVA_VERSION -JAR=$JAR - -XMS=$XMS -XMX=$XMX -ADD_ARGS=${ADD_ARGS:-"NONE"} - -PUID=$PUID -PGID=$PGID -EOF -} - -## ----------------- SEND IT ----------------- ## -while true; do - cd /data/server || exit - if [[ ! -f "server.properties" ]]; then touch "server.properties"; fi - - if [[ -f $JAR || -f "run.sh" ]]; then - echo -e "|_______________________________________________________|" - echo -e "|##----------------- Server Starting -----------------##|" - echo -e "|#| Minecraft Version: $MC_VERSION" - echo -e "|#| " - echo -e "|#| Loader Type: ${SERVER_TYPE:-$MC_LOADER}" - echo -e "|#| Loader Version: $MC_LOADER_VERSION" - echo -e "|#| " - echo -e "|#| Java Version: $JAVA_VERSION" - echo -e "|#| Jar: $JAR" - echo -e "|#| Memory Allocated: " - echo -e "|#| Xms: $XMS" - echo -e "|#| Xmx: $XMX" - echo -e "|#| Additional Args: ${ADD_ARGS:-"NONE"}" - echo -e "|#| " - echo -e "|#| Running as:" - echo -e "|#| User: $PUID" - echo -e "|#| Group: $PGID" - echo -e "|#| " - if [[ ! $NEW_FORGE ]]; then - echo -e "|#| Run Command:" - echo -e "|#| java $RUN_STRING" - fi - echo -e "|##---------------------------------------------------##|" - echo -e "|-------------------------------------------------------|" - echo -e " " - serverInfoWrite - newForge - if [[ "$MC_LOADER" == "neoforge" ]]; then - exec java @user_jvm_args.txt @libraries/net/neoforged/neoforge/"$MC_LOADER_VERSION"/unix_args.txt "$@" - elif [[ "$MC_LOADER" == "forge" ]] && [[ $NEW_FORGE == "true" ]]; then - exec java @user_jvm_args.txt @libraries/net/minecraftforge/forge/"$MC_VERSION"-"$MC_LOADER_VERSION"/unix_args.txt "$@" - else - echo "java $RUN_STRING" - exec java $RUN_STRING - fi - else - echo "Server Not Found." - crash - fi -done +serverStart diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh new file mode 100644 index 0000000..930054f --- /dev/null +++ b/scripts/lib/common.sh @@ -0,0 +1,57 @@ +crash() { + sleep 5 + echo "Exit Complete" + exit 1 +} + +setperms() { + echo -e "|_______________________________________________________|" + echo -e "|##--------------- Setting Permissions ---------------##|" + echo -e "|#| Setting permissions to UID:${PUID} and GID:${PGID}" + sudo usermod -u ${PUID} minecraft + sudo groupmod -g ${PGID} minecraft + sudo chown -R ${PUID}:${PGID} /data + echo -e "|#|---------------------------------------------------|#|" + sleep 2 + echo -e "|#| DONE" + echo -e "|##---------------------------------------------------##|" + sleep 2 +} + +setEula() { + if [[ "${EULA:-false}" != "true" ]]; then + echo "EULA not accepted. Set EULA=true to accept the Minecraft EULA (https://aka.ms/MinecraftEULA)" + crash + fi + cat < eula.txt +#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA). +eula=true +EOF +} + +newForge() { + if [[ "$(printf '%s\n' "1.17.1" "$MC_VERSION" | sort -V | head -n1)" = "1.17.1" ]]; then + NEW_FORGE=true + else + NEW_FORGE=false + fi +} + +serverInfoWrite() { + cat < server.info +MC_VERSION=$MC_VERSION + +MC_LOADER=$MC_LOADER +MC_LOADER_VERSION=$MC_LOADER_VERSION + +JAVA_VERSION=${JAVA_VERSION:-"N/A"} +JAR=${JAR:-"N/A"} + +XMS=${XMS:-"N/A"} +XMX=${XMX:-"N/A"} +ADD_ARGS=${ADD_ARGS:-"NONE"} + +PUID=$PUID +PGID=$PGID +EOF +} diff --git a/scripts/lib/java.sh b/scripts/lib/java.sh new file mode 100644 index 0000000..eb775e3 --- /dev/null +++ b/scripts/lib/java.sh @@ -0,0 +1,53 @@ +javaVersionDetect() { + if [[ -n "${JAVA_VERSION:-}" ]]; then + return + fi + + local MANIFEST + MANIFEST=$(curl -sf "https://launchermeta.mojang.com/mc/game/version_manifest.json") + if [[ -z "$MANIFEST" ]]; then + JAVA_VERSION="temurin@21" + return + fi + + local VERSION_URL + VERSION_URL=$(echo "$MANIFEST" | jq -r ".versions[] | select(.id == \"${MC_VERSION}\") | .url") + if [[ -z "$VERSION_URL" || "$VERSION_URL" == "null" ]]; then + JAVA_VERSION="temurin@21" + return + fi + + local VERSION_DATA + VERSION_DATA=$(curl -sf "$VERSION_URL") + if [[ -z "$VERSION_DATA" ]]; then + JAVA_VERSION="temurin@21" + return + fi + + local JAVA_MAJOR + JAVA_MAJOR=$(echo "$VERSION_DATA" | jq -r ".javaVersion.majorVersion // empty") + if [[ -z "$JAVA_MAJOR" ]]; then + JAVA_VERSION="temurin@8" + else + JAVA_VERSION="temurin@${JAVA_MAJOR}" + fi +} + +javaCheck() { + javaVersionDetect + + JAVA_HOME="/data/java/${JAVA_VERSION}" + if [[ -f "$HOME/.jabba/jabba.sh" ]]; then + source $HOME/.jabba/jabba.sh + else + echo "Something went wrong with locating jabba(the java installer)" + exit 1 + fi + + if [[ ! -f "/data/java/${JAVA_VERSION}/bin/java" ]]; then + rm -rf /data/java/${JAVA_VERSION} + jabba install ${JAVA_VERSION} -o /data/java/${JAVA_VERSION} + fi + PATH=${JAVA_HOME}/bin:$PATH + JAVA="java" +} diff --git a/scripts/lib/loaders/bedrock.sh b/scripts/lib/loaders/bedrock.sh new file mode 100644 index 0000000..79b9f40 --- /dev/null +++ b/scripts/lib/loaders/bedrock.sh @@ -0,0 +1,41 @@ +BEDROCK_API_URL="https://net.web.minecraft-services.net/api/v1.0/download/links" +BEDROCK_FALLBACK_URL="https://raw.githubusercontent.com/kittizz/bedrock-server-downloads/refs/heads/main/bedrock-server-downloads.json" + +_bedrockDownload() { + local platform="$1" + local version="${2:-}" + + 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') + if [[ -z "$DL_URL" ]]; then + DL_URL=$(curl -sL -A "Mozilla/5.0 (X11; Linux x86_64)" "$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 + fi + + if [[ -n "$version" ]]; then + DL_URL=$(echo "$DL_URL" | sed -E "s/(bedrock-server-)[^/]+(\.zip)/\1${version}\2/") + fi + + 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" + unzip -o bedrock-server.zip + rm -f bedrock-server.zip + chmod +x bedrock_server +} + +getBedrock() { + SERVER_TYPE="Bedrock Server" + _bedrockDownload "serverBedrockLinux" "${MC_VERSION:-}" +} + +getBedrockPreview() { + SERVER_TYPE="Bedrock Server (Preview)" + _bedrockDownload "serverBedrockPreviewLinux" "${MC_VERSION:-}" +} diff --git a/scripts/lib/loaders/fabric.sh b/scripts/lib/loaders/fabric.sh new file mode 100644 index 0000000..203c74b --- /dev/null +++ b/scripts/lib/loaders/fabric.sh @@ -0,0 +1,22 @@ +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 + fi + VER=$MC_VERSION + + VER_LOADER="${FABRIC_LOADER_VERSION:-${LATEST_BUILD}}" + 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 +} diff --git a/scripts/lib/loaders/forge.sh b/scripts/lib/loaders/forge.sh new file mode 100644 index 0000000..34fc4bf --- /dev/null +++ b/scripts/lib/loaders/forge.sh @@ -0,0 +1,42 @@ +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 + 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 + + 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 + fi + + touch server.properties + else + echo "No Valid Download URL" + crash + fi +} diff --git a/scripts/lib/loaders/neoforge.sh b/scripts/lib/loaders/neoforge.sh new file mode 100644 index 0000000..de70f61 --- /dev/null +++ b/scripts/lib/loaders/neoforge.sh @@ -0,0 +1,40 @@ +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 + fi + fi + DL_URL=https://maven.neoforged.net/releases/net/neoforged/neoforge/$VER_LOADER/neoforge-$VER_LOADER-installer.jar + fi + + if [[ -n "$DL_URL" ]]; then + echo "$DL_URL" + rm -rf server.jar + + curl -o "neoforge-${VER_LOADER}-installer.jar" "${DL_URL}" + $JAVA -jar neoforge-$VER_LOADER-installer.jar --installServer --server.jar + + mv server.jar neoforge-$VER_LOADER.jar + ln -s neoforge-$VER_LOADER.jar server.jar + + touch server.properties + + 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 +} diff --git a/scripts/lib/loaders/paper.sh b/scripts/lib/loaders/paper.sh new file mode 100644 index 0000000..a553f58 --- /dev/null +++ b/scripts/lib/loaders/paper.sh @@ -0,0 +1,23 @@ +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') + 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}" + + 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 :(" + fi + fi +} diff --git a/scripts/lib/loaders/vanilla.sh b/scripts/lib/loaders/vanilla.sh new file mode 100644 index 0000000..5e01c71 --- /dev/null +++ b/scripts/lib/loaders/vanilla.sh @@ -0,0 +1,13 @@ +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 + fi +} diff --git a/scripts/lib/server.sh b/scripts/lib/server.sh new file mode 100644 index 0000000..7435123 --- /dev/null +++ b/scripts/lib/server.sh @@ -0,0 +1,96 @@ +serverSelect() { + mkdir -p /data/server + cd /data/server || exit + if [[ -f "server.jar" ]] || [[ -f "run.sh" ]] || [[ -f "bedrock_server" ]]; then + echo "Found what appears to be a server, sending it" + else + case $MC_LOADER in + vanilla) + getVanilla + ;; + paper) + getPaper + ;; + fabric) + getFabric + ;; + forge) + getForge + ;; + neoforge) + getNeoForge + ;; + bedrock) + getBedrock + ;; + *) + echo "Server of type ${MC_LOADER} is not recognized." + crash + ;; + esac + fi +} + +buildCommand() { + JAVA_ARGS="-Xms${XMS} -Xmx${XMX}" + if [[ -n $ADD_ARGS ]]; then + RUN_STRING="${ADD_ARGS} ${JAVA_ARGS} -jar ${JAR} nogui" + else + RUN_STRING="${JAVA_ARGS} -jar ${JAR} nogui" + fi +} + +serverStart() { + while true; do + cd /data/server || exit + if [[ ! -f "server.properties" ]]; then touch "server.properties"; fi + + if [[ -f $JAR || -f "run.sh" || -f "bedrock_server" ]]; then + echo -e "|_______________________________________________________|" + echo -e "|##----------------- Server Starting -----------------##|" + echo -e "|#| Minecraft Version: $MC_VERSION" + echo -e "|#| " + echo -e "|#| Loader Type: ${SERVER_TYPE:-$MC_LOADER}" + echo -e "|#| Loader Version: $MC_LOADER_VERSION" + echo -e "|#| " + if [[ "$MC_LOADER" != "bedrock" ]]; then + echo -e "|#| Java Version: $JAVA_VERSION" + echo -e "|#| Jar: $JAR" + echo -e "|#| Memory Allocated: " + echo -e "|#| Xms: $XMS" + echo -e "|#| Xmx: $XMX" + echo -e "|#| Additional Args: ${ADD_ARGS:-"NONE"}" + fi + echo -e "|#| " + echo -e "|#| Running as:" + echo -e "|#| User: $PUID" + echo -e "|#| Group: $PGID" + echo -e "|#| " + if [[ "$MC_LOADER" == "bedrock" ]]; then + echo -e "|#| Run Command:" + echo -e "|#| LD_LIBRARY_PATH=. ./bedrock_server" + elif [[ ${NEW_FORGE:-false} != "true" ]]; then + echo -e "|#| Run Command:" + echo -e "|#| java $RUN_STRING" + fi + echo -e "|##---------------------------------------------------##|" + echo -e "|-------------------------------------------------------|" + echo -e " " + serverInfoWrite + newForge + if [[ "$MC_LOADER" == "bedrock" ]]; then + exec env LD_LIBRARY_PATH=. ./bedrock_server + elif [[ "$MC_LOADER" == "neoforge" ]]; then + exec java @user_jvm_args.txt @libraries/net/neoforged/neoforge/"${VER_LOADER}"/unix_args.txt "$@" + elif [[ "$MC_LOADER" == "forge" ]] && [[ ${NEW_FORGE:-false} == "true" ]]; then + exec java @user_jvm_args.txt @libraries/net/minecraftforge/forge/"$MC_VERSION"-"${VER_LOADER}"/unix_args.txt "$@" + else + echo "java $RUN_STRING" + exec java $RUN_STRING + fi + else + echo "Server Not Found." + crash + fi + done +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..bcc0209 --- /dev/null +++ b/test.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat < Minecraft version (default: auto-detect latest) + -l, --loader Loader to test (default: all) + -v, --loader-version Loader version (default: auto-detect latest) + -h, --help Show this help + +Examples: + $(basename "$0") Test all loaders (latest MC) + $(basename "$0") -m 1.21.1 Test all loaders on MC 1.21.1 + $(basename "$0") -m 1.21.1 -l fabric Test Fabric on MC 1.21.1 + $(basename "$0") -m 1.21.1 -l fabric -v 0.19.3 Test Fabric 0.19.3 on MC 1.21.1 + +Loaders: vanilla paper fabric forge neoforge bedrock +EOF + exit 0 +} + +detect_mc_version() { + curl -s https://launchermeta.mojang.com/mc/game/version_manifest.json | \ + jq -r '.latest.release' +} + +IMAGE="dockercraft-test:latest" +MC_VERSION="" +LOADER="" +LOADER_VERSION="" +PASS=0 +FAIL=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage ;; + -m|--mc-version) MC_VERSION="$2"; shift 2 ;; + -l|--loader) LOADER="$2"; shift 2 ;; + -v|--loader-version) LOADER_VERSION="$2"; shift 2 ;; + *) echo "Unknown option: $1"; usage ;; + esac +done + +if [[ -z "$MC_VERSION" ]]; then + echo "Detecting latest Minecraft version..." + MC_VERSION=$(detect_mc_version) + echo " → ${MC_VERSION}" +fi + +if [[ -z "$LOADER" ]]; then + tests=("vanilla" "paper" "fabric" "forge" "neoforge" "bedrock") +else + tests=("$LOADER") +fi + +cleanup() { + local name="$1" + docker kill "dc-test-${name}" 2>/dev/null || true + docker rm "dc-test-${name}" 2>/dev/null || true +} + +run_test() { + local name="$1" + shift + + echo "=== Testing: ${name} ===" + cleanup "$name" + + local data_dir + data_dir=$(mktemp -d) + + if CONTAINER_ID=$(docker run -d --name "dc-test-${name}" \ + -e EULA=true \ + -e MC_VERSION="${MC_VERSION}" \ + "$@" \ + -v "${data_dir}:/data" \ + "${IMAGE}" 2>&1); then + + local waited=0 + local jar_found=false + while [[ $waited -lt 180 ]]; do + if [[ -f "${data_dir}/server/server.jar" ]] || \ + (find "${data_dir}/server" -name 'forge-*-server.jar' 2>/dev/null | grep -q .) || \ + [[ -f "${data_dir}/server/bedrock_server" ]]; then + jar_found=true + break + fi + sleep 3 + waited=$((waited + 3)) + echo -n "." + done + echo "" + + if $jar_found; then + if [[ -f "${data_dir}/server/bedrock_server" ]]; then + echo " PASS (bedrock_server found in ~${waited}s)" + else + echo " PASS (server jar found in ~${waited}s)" + fi + PASS=$((PASS + 1)) + else + echo "" + echo " FAIL: server.jar not found after 180s" + echo " --- container logs ---" + docker logs "dc-test-${name}" 2>&1 || true + echo " ---------------------" + FAIL=$((FAIL + 1)) + fi + else + echo "" + echo " FAIL: container failed to start" + echo " ${CONTAINER_ID}" + FAIL=$((FAIL + 1)) + fi + + cleanup "$name" + rm -rf "$data_dir" +} + +echo "Building image..." +docker build -t "${IMAGE}" . + +echo "Pre-warming Java cache..." +WARM_ID=$(docker run -d --name "dc-warm" \ + -e MC_VERSION="${MC_VERSION}" \ + -e MC_LOADER=vanilla \ + -e EULA=true \ + -v "$(mktemp -d):/data" \ + "${IMAGE}" 2>&1) && sleep 5 +docker kill "dc-warm" 2>/dev/null || true +docker rm "dc-warm" 2>/dev/null || true +echo " done" + +for t in "${tests[@]}"; do + case $t in + vanilla) + run_test "vanilla" -e MC_LOADER=vanilla + ;; + paper) + run_test "paper" -e MC_LOADER=paper + ;; + fabric) + run_test "fabric" \ + -e MC_LOADER=fabric \ + -e FABRIC_LOADER_VERSION="${LOADER_VERSION}" + ;; + forge) + run_test "forge" \ + -e MC_LOADER=forge \ + -e FORGE_VERSION="${LOADER_VERSION}" + ;; + neoforge) + run_test "neoforge" \ + -e MC_LOADER=neoforge \ + -e NEOFORGE_VERSION="${LOADER_VERSION}" + ;; + bedrock) + run_test "bedrock" \ + -e MC_LOADER=bedrock \ + -e MC_VERSION= + ;; + *) + echo "Unknown loader: ${t}" + echo "Valid: vanilla paper fabric forge neoforge bedrock" + exit 1 + ;; + esac +done + +echo "" +echo "Results: ${PASS} passed, ${FAIL} failed" +[[ $FAIL -eq 0 ]]