#!/bin/bash : "${DEBUG_EXEC:=false}" : "${SETUP_ONLY:=false}" # shellcheck source=start-utils . "${SCRIPTS:-/}start-utils" isDebugging && set -x if [ -n "$ICON" ]; then if [ ! -e server-icon.png ] || isTrue "${OVERRIDE_ICON}"; then log "Using server icon from $ICON..." if isURL "$ICON"; then # Not sure what it is yet...call it "img" if ! get -o /tmp/icon.img "$ICON"; then log "ERROR: failed to download icon from $ICON" exit 1 fi ICON=/tmp/icon.img iconSrc="url" elif [ -f "$ICON" ]; then iconSrc="file" else log "ERROR: $ICON does not appear to be a URL or existing file" exit 1 fi read -r -a specs < <(identify "$ICON" | awk 'NR == 1 { print $2, $3 }') if [ "${specs[0]} ${specs[1]}" = "PNG 64x64" ]; then if [ $iconSrc = url ]; then mv -f /tmp/icon.img /data/server-icon.png else cp -f "$ICON" /data/server-icon.png fi elif [ "${specs[0]}" = GIF ]; then log "Converting GIF image to 64x64 PNG..." convert "$ICON"[0] -resize 64x64! /data/server-icon.png else log "Converting image to 64x64 PNG..." convert "$ICON" -resize 64x64! /data/server-icon.png fi fi fi canUseRollingLogs=true useFallbackJvmFlag=false patchLog4jConfig() { file=${1?} url=${2?} if ! get -o "$file" "$url"; then log "ERROR: failed to download corrected log4j config, fallback to JVM flag" useFallbackJvmFlag=true return 1 fi JVM_OPTS="-Dlog4j.configurationFile=${file} ${JVM_OPTS}" canUseRollingLogs=false } # Patch Log4j remote code execution vulnerability # See https://www.minecraft.net/en-us/article/important-message--security-vulnerability-java-edition if versionLessThan 1.7; then : # No patch required here. elif isFamily VANILLA && versionLessThan 1.12; then patchLog4jConfig log4j2_17-111.xml https://launcher.mojang.com/v1/objects/dd2b723346a8dcd48e7f4d245f6bf09e98db9696/log4j2_17-111.xml elif isFamily VANILLA && versionLessThan 1.17; then patchLog4jConfig log4j2_112-116.xml https://launcher.mojang.com/v1/objects/02937d122c86ce73319ef9975b58896fc1b491d1/log4j2_112-116.xml # See https://purpurmc.org/docs/Log4j/ elif isType PURPUR && versionLessThan 1.17; then patchLog4jConfig purpur_log4j2_1141-1165.xml https://purpurmc.org/docs/xml/purpur_log4j2_1141-1165.xml elif isType PURPUR && versionLessThan 1.18.1; then patchLog4jConfig purpur_log4j2_117.xml https://purpurmc.org/docs/xml/purpur_log4j2_117.xml elif versionLessThan 1.18.1; then useFallbackJvmFlag=true fi if ${useFallbackJvmFlag}; then JVM_OPTS="-Dlog4j2.formatMsgNoLookups=true ${JVM_OPTS}" fi if isTrue ${ENABLE_ROLLING_LOGS:-false}; then if ! ${canUseRollingLogs}; then log "ERROR: Using rolling logs is currently not possible in the selected version due to CVE-2021-44228" exit 1 fi # Set up log configuration LOGFILE="/data/log4j2.xml" if [ ! -e "$LOGFILE" ]; then log "Creating log4j2.xml in ${LOGFILE}" cp /image/log4j2.xml "$LOGFILE" else log "log4j2.xml already created, skipping" fi JVM_OPTS="-Dlog4j.configurationFile=/data/log4j2.xml ${JVM_OPTS}" fi # Optional disable console if versionLessThan 1.14 && [[ ${CONSOLE,,} = false ]]; then EXTRA_ARGS+=" --noconsole" fi # Optional disable GUI for headless servers if [[ ${GUI,,} = false ]]; then EXTRA_ARGS+=" nogui" fi expandedDOpts= if [ -n "$JVM_DD_OPTS" ]; then for dopt in $JVM_DD_OPTS do expandedDOpts="${expandedDOpts} -D${dopt/:/=}" done fi if isTrue "${ENABLE_JMX}"; then : "${JMX_PORT:=7091}" JVM_OPTS="${JVM_OPTS} -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.port=${JMX_PORT} -Dcom.sun.management.jmxremote.rmi.port=${JMX_PORT} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.host=${JMX_BINDING:-0.0.0.0} -Djava.rmi.server.hostname=${JMX_HOST:-localhost}" log "JMX is enabled. Make sure you have port forwarding for ${JMX_PORT}" fi 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:+AlwaysPreTouch -XX:G1NewSizePercent=${G1NewSizePercent} -XX:G1MaxNewSizePercent=${G1MaxNewSizePercent} -XX:G1HeapRegionSize=${G1HeapRegionSize} -XX:G1ReservePercent=${G1ReservePercent} -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=${InitiatingHeapOccupancyPercent} -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true " fi if isTrue "${USE_FLARE_FLAGS}"; then JVM_XX_OPTS="${JVM_XX_OPTS} -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints " fi if isTrue "${USE_SIMD_FLAGS}"; then JVM_XX_OPTS="${JVM_XX_OPTS} --add-modules=jdk.incubator.vector " fi if isTrue "${DEBUG_MEMORY}"; then log "Memory usage and availability (in MB)" uname -a free -m fi if [[ ${INIT_MEMORY} || ${MAX_MEMORY} ]]; then log "Setting initial memory to ${INIT_MEMORY:=${MEMORY}} and max to ${MAX_MEMORY:=${MEMORY}}" if [[ ${INIT_MEMORY} ]]; then JVM_OPTS="-Xms${INIT_MEMORY} ${JVM_OPTS}" fi if [[ ${MAX_MEMORY} ]]; then JVM_OPTS="-Xmx${MAX_MEMORY} ${JVM_OPTS}" fi fi function copyFilesForCurseForge() { # copy player modification files unconditionally since their # processing into json is additive anyway [ -f /data/ops.txt ] && cp -f /data/ops.txt "${FTB_DIR}/" [ -f /data/white-list.txt ] && cp -f /data/white-list.txt "${FTB_DIR}/" if [ ! -e "${FTB_DIR}/server-icon.png" ] && [ -e /data/server-icon.png ]; then cp -f /data/server-icon.png "${FTB_DIR}/" fi cp -f /data/eula.txt "${FTB_DIR}/" } mcServerRunnerArgs=( --stop-duration "${STOP_DURATION:-60}s" --named-pipe "${CONSOLE_IN_NAMED_PIPE:-/tmp/minecraft-console-in}" ) if [[ ${STOP_SERVER_ANNOUNCE_DELAY} ]]; then mcServerRunnerArgs+=(--stop-server-announce-delay "${STOP_SERVER_ANNOUNCE_DELAY}s") fi if [[ ${TYPE} == "CURSEFORGE" && "${SERVER}" ]]; then copyFilesForCurseForge cd "${FTB_DIR}" || (log "ERROR: can't go into ${FTB_DIR}"; exit 1) log "Starting CurseForge server in ${FTB_DIR}..." if isTrue "${DEBUG_EXEC}"; then set -x fi exec mc-server-runner ${bootstrapArgs} "${mcServerRunnerArgs[@]}" java $JVM_XX_OPTS $JVM_OPTS $expandedDOpts -jar "$(basename "${SERVER}")" "$@" $EXTRA_ARGS elif [[ ${TYPE} == "CURSEFORGE" ]]; then mcServerRunnerArgs+=(--shell bash) copyFilesForCurseForge cat > "${FTB_DIR}/settings-local.sh" < user_jvm_args.txt if isTrue ${SETUP_ONLY}; then echo "SETUP_ONLY: bash ${SERVER}" exit fi exec mc-server-runner "${mcServerRunnerArgs[@]}" --shell bash "${SERVER}" else # If we have a bootstrap.txt file... feed that in to the server stdin if [ -f /data/bootstrap.txt ]; then bootstrapArgs="--bootstrap /data/bootstrap.txt" fi log "Starting the Minecraft server..." # shellcheck disable=SC2206 finalArgs=( $JVM_XX_OPTS $JVM_OPTS $expandedDOpts -jar "$SERVER" "$@" $EXTRA_ARGS ) if isTrue ${SETUP_ONLY}; then echo "SETUP_ONLY: java ${finalArgs[*]}" exit fi if isTrue "${DEBUG_EXEC}"; then set -x fi if isTrue "${EXEC_DIRECTLY:-false}"; then exec java "${finalArgs[@]}" else exec mc-server-runner ${bootstrapArgs} "${mcServerRunnerArgs[@]}" java "${finalArgs[@]}" fi fi