diff --git a/.dockerignore b/.dockerignore index 510536d7..1db22cbb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ data examples k8s-examples -.idea \ No newline at end of file +.idea +.git \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index de0a3113..d443bb68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,8 @@ HEALTHCHECK --start-period=1m CMD mc-monitor status --host localhost --port $SER RUN addgroup --gid 1000 minecraft \ && adduser --system --shell /bin/false --uid 1000 --ingroup minecraft --home /home/minecraft minecraft \ - && mkdir -m 777 /data /mods /config /plugins \ - && chown minecraft:minecraft /data /config /mods /plugins /home/minecraft + && mkdir -m 777 /data \ + && chown minecraft:minecraft /data /home/minecraft EXPOSE 25565 25575 @@ -52,7 +52,7 @@ RUN easy-add --var os=${TARGETOS} --var arch=${TARGETARCH}${TARGETVARIANT} \ --from https://github.com/itzg/{{.app}}/releases/download/{{.version}}/{{.app}}_{{.version}}_{{.os}}_{{.arch}}.tar.gz RUN easy-add --var os=${TARGETOS} --var arch=${TARGETARCH}${TARGETVARIANT} \ - --var version=1.3.5 --var app=mc-server-runner --file {{.app}} \ + --var version=1.4.2 --var app=mc-server-runner --file {{.app}} \ --from https://github.com/itzg/{{.app}}/releases/download/{{.version}}/{{.app}}_{{.version}}_{{.os}}_{{.arch}}.tar.gz RUN easy-add --var os=${TARGETOS} --var arch=${TARGETARCH}${TARGETVARIANT} \ @@ -72,8 +72,7 @@ ENV UID=1000 GID=1000 \ JVM_XX_OPTS="-XX:+UseG1GC" MEMORY="1G" \ TYPE=VANILLA VERSION=LATEST FORGEVERSION=RECOMMENDED SPONGEBRANCH=STABLE SPONGEVERSION= FABRICVERSION=LATEST LEVEL=world \ PVP=true DIFFICULTY=easy ENABLE_RCON=true RCON_PORT=25575 RCON_PASSWORD=minecraft \ - RESOURCE_PACK= RESOURCE_PACK_SHA1= \ - LEVEL_TYPE=DEFAULT GENERATOR_SETTINGS= WORLD= MODPACK= MODS= SERVER_PORT=25565 ONLINE_MODE=TRUE CONSOLE=true SERVER_NAME="Dedicated Server" \ + LEVEL_TYPE=DEFAULT SERVER_PORT=25565 ONLINE_MODE=TRUE SERVER_NAME="Dedicated Server" \ REPLACE_ENV_VARIABLES="FALSE" ENV_VARIABLE_PREFIX="CFG_" COPY start* / diff --git a/README.md b/README.md index 25459e12..4fdf5077 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,8 @@ Keep in mind that some versions of Minecraft server can't work on the newest ver ## Healthcheck -This image contains [Dinnerbone's mcstatus](https://github.com/Dinnerbone/mcstatus) and uses -its `ping` command to continually check on the container's. That can be observed +This image contains [mc-monitor](https://github.com/itzg/mc-monitor) and uses +its `status` command to continually check on the container's. That can be observed from the `STATUS` column of `docker ps` ``` @@ -161,16 +161,6 @@ You can also query the container's health in a script friendly way: healthy ``` -Finally, since `mcstatus` is on the `PATH` you can exec into the container -and use mcstatus directly and invoke any of its other commands: - -``` -> docker exec mc mcstatus localhost status -version: v1.12 (protocol 335) -description: "{u'text': u'A Minecraft Server Powered by Docker'}" -players: 0/20 No players online -``` - ## Deployment Templates and Examples ### Helm Charts @@ -997,6 +987,12 @@ Allows users to use flight on your server while in Survival mode, if they have a -e ALLOW_FLIGHT=TRUE|FALSE +### Other server property mappings: + +Environment Variable | Server Property +---------------------|----------------- +PLAYER_IDLE_TIMEOUT | player-idle-timeout + ## Miscellaneous Options ### Running as alternate user/group ID @@ -1042,10 +1038,16 @@ To enable remote JMX, such as for profiling with VisualVM or JMC, add the enviro ### Enable Aikar's Flags -[Aikar has does some research](https://aikar.co/2018/07/02/tuning-the-jvm-g1gc-garbage-collector-flags-for-minecraft/) into finding the optimal JVM flags for GC tuning, which becomes more important as more users are connected concurrently. The set of flags documented there can be added using +[Aikar has does some research](https://mcflags.emc.gs/) into finding the optimal JVM flags for GC tuning, which becomes more important as more users are connected concurrently. The set of flags documented there can be added using -e USE_AIKAR_FLAGS=true +When `MEMORY` is greater than or equal to 12G, then the Aikar flags will be adjusted according to the article. + +Large page support can also be enabled by adding + + -e USE_LARGE_PAGES=true + ### HTTP Proxy You may configure the use of an HTTP/HTTPS proxy by passing the proxy's URL via the `PROXY` diff --git a/examples/docker-compose-curseinstance.yml b/examples/docker-compose-curseinstance.yml new file mode 100644 index 00000000..c1d62f7f --- /dev/null +++ b/examples/docker-compose-curseinstance.yml @@ -0,0 +1,20 @@ +version: "3.7" + +services: + mc: + image: itzg/minecraft-server + ports: + - 25565:25565 + volumes: + # Attach .../Curse/Minecraft/Instances for use at /instances + - ./Instances:/instances:ro + # Attach /data as usual + - ./ServerData:/data + environment: + EULA: "TRUE" + # Modpacks generally need more memory, so let's give at 2 GB + MEMORY: 2G + # Use new CURSE_INSTANCE type + TYPE: CURSE_INSTANCE + # Reference directory of or full path to minecraftinstance.json + CURSE_INSTANCE_JSON: /instances/FTB Presents SkyFactory 3 diff --git a/mcstatus b/mcstatus index 89c62d78..e7e314b8 100755 --- a/mcstatus +++ b/mcstatus @@ -1,5 +1,7 @@ #!/bin/bash +echo "WARNING: mcstatus is deprecated; calling mc-monitor instead" + ##### mcstatus shim for mc-monitor # handles translating calls to # mcstatus (host:port) (command) diff --git a/start-configuration b/start-configuration index aa014998..a79f7471 100644 --- a/start-configuration +++ b/start-configuration @@ -19,9 +19,9 @@ if [ ! -e /data/eula.txt ]; then exit 1 fi - echo "# Generated via Docker on $(date)" > eula.txt - echo "eula=$EULA" >> eula.txt - if [ $? != 0 ]; then + echo "# Generated via Docker on $(date)" > /data/eula.txt + + if ! echo "eula=$EULA" >> /data/eula.txt; then log "ERROR: unable to write eula to /data. Please make sure attached directory is writable by uid=${UID}" exit 2 fi @@ -49,56 +49,61 @@ export VERSIONS_JSON=https://launchermeta.mojang.com/mc/game/version_manifest.js case "X$VERSION" in X|XLATEST|Xlatest) - export VANILLA_VERSION=`curl -fsSL $VERSIONS_JSON | jq -r '.latest.release'` + VANILLA_VERSION=$(curl -fsSL $VERSIONS_JSON | jq -r '.latest.release') ;; XSNAPSHOT|Xsnapshot) - export VANILLA_VERSION=`curl -fsSL $VERSIONS_JSON | jq -r '.latest.snapshot'` + VANILLA_VERSION=$(curl -fsSL $VERSIONS_JSON | jq -r '.latest.snapshot') ;; X[1-9]*) - export VANILLA_VERSION=$VERSION + VANILLA_VERSION=$VERSION ;; *) - export VANILLA_VERSION=`curl -fsSL $VERSIONS_JSON | jq -r '.latest.release'` + VANILLA_VERSION=$(curl -fsSL $VERSIONS_JSON | jq -r '.latest.release') ;; esac +export VANILLA_VERSION log "Resolved version given ${VERSION} into ${VANILLA_VERSION}" -cd /data +cd /data || exit 1 export ORIGINAL_TYPE=${TYPE^^} log "Resolving type given ${TYPE}" case "${TYPE^^}" in *BUKKIT|SPIGOT) - exec /start-deployBukkitSpigot $@ + exec /start-deployBukkitSpigot "$@" ;; PAPER) - exec /start-deployPaper $@ + exec /start-deployPaper "$@" ;; FORGE) - exec /start-deployForge $@ + exec /start-deployForge "$@" ;; FABRIC) - exec /start-deployFabric $@ + exec /start-deployFabric "$@" ;; FTB|CURSEFORGE) - exec /start-deployFTB $@ + exec /start-deployFTB "$@" ;; VANILLA) - exec /start-deployVanilla $@ + exec /start-deployVanilla "$@" ;; SPONGEVANILLA) - exec /start-deploySpongeVanilla $@ + exec /start-deploySpongeVanilla "$@" ;; CUSTOM) - exec /start-deployCustom $@ + exec /start-deployCustom "$@" + ;; + + CURSE_INSTANCE) + exec /start-validateCurseInstance "$@" ;; *) diff --git a/start-deployFabric b/start-deployFabric index 194594a9..d8305568 100644 --- a/start-deployFabric +++ b/start-deployFabric @@ -29,7 +29,7 @@ elif [[ ! -e $FABRIC_INSTALLER ]]; then exit 2 fi -installMarker=".fabric-installed-${VANILLA_VERSION}-${FABRIC_VERSION:-manual}" +installMarker="/data/.fabric-installed-${VANILLA_VERSION}-${FABRIC_VERSION:-manual}" debug Checking for installMarker ${installMarker} if [[ ! -e $installMarker ]]; then diff --git a/start-deployForge b/start-deployForge index d280ac5e..739808b0 100644 --- a/start-deployForge +++ b/start-deployForge @@ -47,7 +47,7 @@ elif [[ ! -e $FORGE_INSTALLER ]]; then exit 2 fi -installMarker=".forge-installed-$shortForgeVersion" +installMarker="/data/.forge-installed-$shortForgeVersion" if [ ! -e $installMarker ]; then if [ ! -e $FORGE_INSTALLER ]; then diff --git a/start-finalSetup02Modpack b/start-finalSetup02Modpack index 751f0ce0..bdd0b40b 100644 --- a/start-finalSetup02Modpack +++ b/start-finalSetup02Modpack @@ -133,9 +133,22 @@ if [[ "${GENERIC_PACK}" ]]; then base_dir=/tmp/generic_pack_base mkdir -p ${base_dir} unzip -q -d ${base_dir} ${GENERIC_PACK} - depth=$(( ${GENERIC_PACK_STRIP_DIRS:-1} + 1 )) - log "Applying generic pack, stripping $(( depth - 1 )) level ..." - find ${base_dir} -type d -mindepth $depth -maxdepth $depth -exec cp -r {} /data/ + + if [ -f /data/manifest.txt ]; then + log "Manifest exists from older generic pack, cleaning up ..." + while read f; do + rm -rf "/data/${f}" + done < /data/manifest.txt + find /data/* -type d -exec rmdir --ignore-fail-on-non-empty {} + + rm -f /data/manifest.txt + fi + log "Writing generic pack manifest ... " + find ${base_dir} -type f -print0 | xargs -0 -I {} echo "{}" | sed "s#${base_dir}/##" > /data/manifest.txt + log "Applying generic pack ..." + IFS=' +' + set -f + for d in $(find ${base_dir} -type d); do mkdir -p "$(sed "s#${base_dir}#/data#" <<< $d)"; done + for f in $(find ${base_dir} -type f); do cp -f "$f" "$(sed "s#${base_dir}#/data#" <<< $f)"; done rm -rf ${base_dir} sha256sum ${GENERIC_PACK} > ${sum_file} fi diff --git a/start-finalSetup04ServerProperties b/start-finalSetup04ServerProperties index 91919a79..d5ef5503 100644 --- a/start-finalSetup04ServerProperties +++ b/start-finalSetup04ServerProperties @@ -76,6 +76,7 @@ function customizeServerProps { setServerProp "level-type" "${LEVEL_TYPE^^}" setServerProp "resource-pack" "$RESOURCE_PACK" setServerProp "resource-pack-sha1" "$RESOURCE_PACK_SHA1" + setServerProp "player-idle-timeout" "$PLAYER_IDLE_TIMEOUT" if [ -n "$DIFFICULTY" ]; then case $DIFFICULTY in diff --git a/start-minecraftFinalSetup b/start-minecraftFinalSetup index be80eeee..ff20ceea 100644 --- a/start-minecraftFinalSetup +++ b/start-minecraftFinalSetup @@ -4,14 +4,14 @@ if [ -n "$OPS" ]; then log "Setting/adding ops" - rm -rf ops.txt.converted - echo $OPS | awk -v RS=, '{print}' > ops.txt + rm -rf /data/ops.txt.converted + echo $OPS | awk -v RS=, '{print}' > /data/ops.txt fi if [ -n "$WHITELIST" ]; then log "Setting whitelist" - rm -rf white-list.txt.converted - echo $WHITELIST | awk -v RS=, '{print}' > white-list.txt + rm -rf /data/white-list.txt.converted + echo $WHITELIST | awk -v RS=, '{print}' > /data/white-list.txt fi if [ -n "$ICON" -a ! -e server-icon.png ]; then @@ -41,7 +41,7 @@ fi # Make sure files exist and are valid JSON (for pre-1.12 to 1.12 upgrades) log "Checking for JSON files." -JSON_FILES=$(find . -maxdepth 1 -name '*.json') +JSON_FILES=$(find /data -maxdepth 1 -name '*.json') for j in $JSON_FILES; do if [[ $(cat "$j" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') == "" ]]; then log "Fixing JSON $j" @@ -51,9 +51,9 @@ done # If any modules have been provided, copy them over -mkdir -p /data/mods if [ -d /mods ]; then log "Copying any mods over..." + mkdir -p /data/mods rsync -a --out-format="update:%f:Last Modified %M" --prune-empty-dirs --update /mods /data fi @@ -80,7 +80,7 @@ esac EXTRA_ARGS="" # Optional disable console -if [[ ${CONSOLE} = false || ${CONSOLE} = FALSE ]]; then +if versionLessThan 1.14 && [[ ${CONSOLE,,} = false ]]; then EXTRA_ARGS+="--noconsole" fi @@ -115,22 +115,39 @@ if isTrue ${ENABLE_JMX}; then log "JMX is enabled. Make sure you have port forwarding for ${JMX_PORT}" fi -if isTrue ${USE_AIKAR_FLAGS}; then - # From https://aikar.co/2018/07/02/tuning-the-jvm-g1gc-garbage-collector-flags-for-minecraft/ - JVM_OPTS="${JVM_XX_OPTS} +if isTrue "${USE_AIKAR_FLAGS}"; then + # From https://mcflags.emc.gs/ + + if (( $(normalizeMemSize "${MAX_MEMORY}") >= $(normalizeMemSize 12g) )); then + log "Using Aikar's >12GB flags" + G1NewSizePercent=40 + G1MaxNewSizePercent=50 + G1HeapRegionSize=16M + G1ReservePercent=15 + InitiatingHeapOccupancyPercent=20 + else + log "Using Aikar's flags" + G1NewSizePercent=30 + G1MaxNewSizePercent=40 + G1HeapRegionSize=8M + G1ReservePercent=20 + InitiatingHeapOccupancyPercent=15 + fi + + JVM_XX_OPTS="${JVM_XX_OPTS} -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch - -XX:G1NewSizePercent=30 - -XX:G1MaxNewSizePercent=40 - -XX:G1HeapRegionSize=8M - -XX:G1ReservePercent=20 + -XX:G1NewSizePercent=${G1NewSizePercent} + -XX:G1MaxNewSizePercent=${G1MaxNewSizePercent} + -XX:G1HeapRegionSize=${G1HeapRegionSize} + -XX:G1ReservePercent=${G1ReservePercent} -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=8 - -XX:InitiatingHeapOccupancyPercent=15 + -XX:InitiatingHeapOccupancyPercent=${InitiatingHeapOccupancyPercent} -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 @@ -140,9 +157,22 @@ if isTrue ${USE_AIKAR_FLAGS}; then " fi -mcServerRunnerArgs="--stop-duration 60s" +if isTrue "${USE_LARGE_PAGES}"; then + JVM_XX_OPTS="${JVM_XX_OPTS} + -XX:+UseLargePagesInMetaspace + " +fi -if [[ ${TYPE} == "FEED-THE-BEAST" ]]; then +mcServerRunnerArgs="--stop-duration 60s" +if [[ ${TYPE} == "CURSE_INSTANCE" ]]; then + JVM_OPTS="-Xms${INIT_MEMORY} -Xmx${MAX_MEMORY} ${JVM_OPTS}" + if isTrue ${DEBUG_EXEC}; then + set -x + fi + exec mc-server-runner ${mcServerRunnerArgs} \ + --cf-instance-file "${CURSE_INSTANCE_JSON}" \ + java $JVM_XX_OPTS $JVM_OPTS $expandedDOpts -jar _SERVERJAR_ "$@" $EXTRA_ARGS +elif [[ ${TYPE} == "FEED-THE-BEAST" ]]; then mcServerRunnerArgs="${mcServerRunnerArgs} --shell bash" if [ ! -e "${FTB_DIR}/ops.json" -a -e /data/ops.txt ]; then diff --git a/start-utils b/start-utils index 7ef42751..9641256e 100644 --- a/start-utils +++ b/start-utils @@ -48,3 +48,37 @@ function logn { function log { echo "[init] $*" } + +function normalizeMemSize { + local scale=1 + case ${1,,} in + *k) + scale=1024;; + *m) + scale=1048576;; + *g) + scale=1073741824;; + esac + + val=${1:0: -1} + echo $(( val * scale )) +} + +function versionLessThan { + local activeParts + IFS=. read -ra activeParts <<< "${VANILLA_VERSION}" + + local givenParts + IFS=. read -ra givenParts <<< "$1" + + if (( ${#activeParts[@]} < 2 )); then + return 1 + fi + + if (( activeParts[0] < givenParts[0] )) || \ + (( activeParts[0] == givenParts[0] && activeParts[1] < givenParts[1] )); then + return 0 + else + return 1 + fi +} \ No newline at end of file diff --git a/start-validateCurseInstance b/start-validateCurseInstance new file mode 100755 index 00000000..09af3ab5 --- /dev/null +++ b/start-validateCurseInstance @@ -0,0 +1,18 @@ +#!/bin/bash + +. /start-utils + +if ! [[ -v CURSE_INSTANCE_JSON ]]; then + log "ERROR: CURSE_INSTANCE_JSON needs to be set" + exit 2 +elif ! [ -f "${CURSE_INSTANCE_JSON}" ] && [ -f "${CURSE_INSTANCE_JSON}/minecraftinstance.json" ]; then + CURSE_INSTANCE_JSON="${CURSE_INSTANCE_JSON}/minecraftinstance.json" +elif ! [ -f "${CURSE_INSTANCE_JSON}" ]; then + log "ERROR: CURSE_INSTANCE_JSON file does not exist: ${CURSE_INSTANCE_JSON}" + exit 2 +fi + +log "Resolved CURSE_INSTANCE_JSON as ${CURSE_INSTANCE_JSON}" + +# Continue to Final Setup +exec /start-finalSetup01World "$@"