#!/bin/bash : "${DEBUG_EXEC:=false}" : "${SETUP_ONLY:=false}" : "${CUSTOM_JAR_EXEC:=}" # shellcheck source=start-utils . "$(dirname "$0")/start-utils" isDebugging && set -x baseDataDir=/data tmpServerIconPath=/tmp/icon.img serverIconPath=${baseDataDir}/server-icon.png mcHealthEnvPath=${baseDataDir}/.mc-health.env bootstrapPath=${baseDataDir}/bootstrap.txt 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 "$tmpServerIconPath" "$ICON"; then logError "Failed to download icon from $ICON" exit 1 fi ICON="$tmpServerIconPath" iconSrc="url" elif [ -f "$ICON" ]; then iconSrc="file" else logError "$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 "$tmpServerIconPath" "$serverIconPath" else cp -f "$ICON" "$serverIconPath" fi elif [ "${specs[0]}" = GIF ]; then log "Converting GIF image to 64x64 PNG..." convert "$ICON"[0] -resize 64x64! "$serverIconPath" else log "Converting image to 64x64 PNG..." convert "$ICON" -resize 64x64! "$serverIconPath" fi fi fi useGeneratedLogs=${GENERATE_LOG4J2_CONFIG:-${ENABLE_ROLLING_LOGS:-false}} useFallbackJvmFlag=false SERVER_DIR="$baseDataDir" if [[ ${FTB_DIR:-} ]]; then SERVER_DIR="$FTB_DIR" fi patchLog4jConfig() { file=${1?} url=${2?} if ! get -o "${SERVER_DIR}/${file}" "$url"; then logError "Failed to download corrected log4j config, fallback to JVM flag" useFallbackJvmFlag=true return 1 fi JVM_OPTS="-Dlog4j.configurationFile=${file} ${JVM_OPTS}" useGeneratedLogs=false } # Temporarily disable debugging output oldState=$(shopt -po xtrace || true) shopt -u -o xtrace # 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 eval "$oldState" if ${useFallbackJvmFlag}; then JVM_OPTS="-Dlog4j2.formatMsgNoLookups=true ${JVM_OPTS}" fi if versionLessThan 1.7; then : # No patch required here. elif versionLessThan 1.18.1; then if isTrue ${SKIP_LOG4J_PATCHER:-false}; then log "Skipping Log4jPatcher, make sure you are not affected" else JVM_OPTS="-javaagent:/image/Log4jPatcher.jar ${JVM_OPTS}" fi fi # Set up log4j2 configuration with templating support LOGFILE="${SERVER_DIR}/log4j2.xml" if ${useGeneratedLogs}; then # Set up log configuration defaults : "${LOG_LEVEL:=info}" : "${ROLLING_LOG_MAX_FILES:=1000}" # Note: Can't use ${VAR:=default} syntax for values containing } as it breaks parsing if [ -z "${ROLLING_LOG_FILE_PATTERN}" ]; then ROLLING_LOG_FILE_PATTERN='logs/%d{yyyy-MM-dd}-%i.log.gz' fi # Pattern format defaults (compatible with vanilla Minecraft) # Note: Can't use ${VAR:=default} syntax because } in the value breaks parsing if [ -z "${LOG_CONSOLE_FORMAT}" ]; then LOG_CONSOLE_FORMAT='[%d{HH:mm:ss}] [%t/%level]: %msg%n' fi if [ -z "${LOG_TERMINAL_FORMAT}" ]; then LOG_TERMINAL_FORMAT='[%d{HH:mm:ss} %level]: %msg%n' fi if [ -z "${LOG_FILE_FORMAT}" ]; then LOG_FILE_FORMAT='[%d{HH:mm:ss}] [%t/%level]: %msg%n' fi export LOG_LEVEL ROLLING_LOG_FILE_PATTERN ROLLING_LOG_MAX_FILES export LOG_CONSOLE_FORMAT LOG_TERMINAL_FORMAT LOG_FILE_FORMAT # Always regenerate if file doesn't exist if [ ! -e "$LOGFILE" ] || isTrue "${REGENERATE_LOG4J2:-true}"; then log "Generating log4j2.xml from template in ${LOGFILE}" # Generate log4j2.xml using heredoc for reliable variable substitution cat > "$LOGFILE" < EOF else log "log4j2.xml already exists and is up to date, skipping generation" fi # Apply the log4j2 configuration JVM_OPTS="-Dlog4j.configurationFile=log4j2.xml ${JVM_OPTS}" else rm -f "${LOGFILE}" 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 : "${USE_AIKAR_FLAGS:=false}" : "${USE_MEOWICE_FLAGS:=false}" : "${USE_MEOWICE_GRAALVM_FLAGS:=false}" if isTrue "${USE_MEOWICE_FLAGS}"; then java_major_version=$(mc-image-helper java-release) if [[ $java_major_version -gt 16 ]]; then log "Java version $java_major_version using MeowIce's flags for Java 17+" else log "Your Java version is $java_major_version, MeowIce's flags are for Java 17+ falling back to Aikar's" USE_MEOWICE_FLAGS=FALSE USE_AIKAR_FLAGS=TRUE fi fi if isTrue "${USE_AIKAR_FLAGS}" || isTrue "${USE_MEOWICE_FLAGS}"; then # From https://mcflags.emc.gs/ if isTrue "${USE_MEOWICE_FLAGS}"; then log "Using MeowIce's flags" G1NewSizePercent=28 G1MaxNewSizePercent=50 G1HeapRegionSize=16M G1ReservePercent=15 InitiatingHeapOccupancyPercent=20 G1MixedGCCountTarget=3 G1RSetUpdatingPauseTimePercent=0 elif [[ $MAX_MEMORY ]] && (( $(normalizeMemSize "${MAX_MEMORY}") >= $(normalizeMemSize 12g) )); then log "Using Aikar's >12GB flags" G1NewSizePercent=40 G1MaxNewSizePercent=50 G1HeapRegionSize=16M G1ReservePercent=15 InitiatingHeapOccupancyPercent=20 G1MixedGCCountTarget=4 G1RSetUpdatingPauseTimePercent=5 else log "Using Aikar's flags" G1NewSizePercent=30 G1MaxNewSizePercent=40 G1HeapRegionSize=8M G1ReservePercent=20 InitiatingHeapOccupancyPercent=15 G1MixedGCCountTarget=4 G1RSetUpdatingPauseTimePercent=5 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=${G1MixedGCCountTarget} -XX:InitiatingHeapOccupancyPercent=${InitiatingHeapOccupancyPercent} -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=${G1RSetUpdatingPauseTimePercent} -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 " if isTrue "${USE_AIKAR_FLAGS}"; then JVM_XX_OPTS="${JVM_XX_OPTS} -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true " fi fi if isTrue "${USE_MEOWICE_FLAGS}"; then JVM_XX_OPTS="${JVM_XX_OPTS} -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:G1SATBBufferEnqueueingThresholdPercent=30 -XX:G1ConcMarkStepDurationMillis=5 -XX:+UseNUMA -XX:-DontCompileHugeMethods -XX:MaxNodeLimit=240000 -XX:NodeLimitFudgeFactor=8000 -XX:ReservedCodeCacheSize=400M -XX:NonNMethodCodeHeapSize=12M -XX:ProfiledCodeHeapSize=194M -XX:NonProfiledCodeHeapSize=194M -XX:NmethodSweepActivity=1 -XX:+UseFastUnorderedTimeStamps -XX:+UseCriticalJavaThreadPriority -XX:AllocatePrefetchStyle=3 -XX:+AlwaysActAsServerClassMachine -XX:+UseTransparentHugePages -XX:LargePageSizeInBytes=2M -XX:+UseLargePages -XX:+EagerJVMCI -XX:+UseStringDeduplication -XX:+UseAES -XX:+UseAESIntrinsics -XX:+UseFMA -XX:+UseLoopPredicate -XX:+RangeCheckElimination -XX:+OptimizeStringConcat -XX:+UseCompressedOops -XX:+UseThreadPriorities -XX:+OmitStackTraceInFastThrow -XX:+RewriteBytecodes -XX:+RewriteFrequentPairs -XX:+UseFPUForSpilling -XX:+UseVectorCmov -XX:+UseXMMForArrayCopy -XX:+EliminateLocks -XX:+DoEscapeAnalysis -XX:+AlignVector -XX:+OptimizeFill -XX:+EnableVectorSupport -XX:+UseCharacterCompareIntrinsics -XX:+UseCopySignIntrinsic -XX:+UseVectorStubs " if [[ $(arch) == "x86_64" ]]; then JVM_XX_OPTS="${JVM_XX_OPTS} -XX:+UseFastStosb -XX:+UseNewLongLShift -XX:+UseXmmI2D -XX:+UseXmmI2F -XX:+UseXmmLoadAndClearUpper -XX:+UseXmmRegToRegMoveAll -XX:UseAVX=2 -XX:UseSSE=4 " else log "cpu not x86_64, disabling architecture specific flags" fi fi if isTrue "${USE_MEOWICE_GRAALVM_FLAGS}"; then if [[ $java_major_version -gt 23 ]]; then log "Java 24 or higher detected, using modified GraalVM flags" JVM_XX_OPTS="${JVM_XX_OPTS} -XX:+UseFastJNIAccessors -XX:+UseInlineCaches -XX:+SegmentedCodeCache -Djdk.nio.maxCachedBufferSize=262144 -Djdk.graal.UsePriorityInlining=true -Djdk.graal.Vectorization=true -Djdk.graal.OptDuplication=true -Djdk.graal.DetectInvertedLoopsAsCounted=true -Djdk.graal.LoopInversion=true -Djdk.graal.VectorizeHashes=true -Djdk.graal.EnterprisePartialUnroll=true -Djdk.graal.VectorizeSIMD=true -Djdk.graal.StripMineNonCountedLoops=true -Djdk.graal.SpeculativeGuardMovement=true -Djdk.graal.TuneInlinerExploration=1 -Djdk.graal.LoopRotation=true -Djdk.graal.CompilerConfiguration=enterprise --enable-native-access=ALL-UNNAMED " else log "Using MeowIce's flags for Graalvm" JVM_XX_OPTS="${JVM_XX_OPTS} -XX:+UseFastJNIAccessors -XX:+UseInlineCaches -XX:+SegmentedCodeCache -Djdk.nio.maxCachedBufferSize=262144 -Dgraal.UsePriorityInlining=true -Dgraal.Vectorization=true -Dgraal.OptDuplication=true -Dgraal.DetectInvertedLoopsAsCounted=true -Dgraal.LoopInversion=true -Dgraal.VectorizeHashes=true -Dgraal.EnterprisePartialUnroll=true -Dgraal.VectorizeSIMD=true -Dgraal.StripMineNonCountedLoops=true -Dgraal.SpeculativeGuardMovement=true -Dgraal.TuneInlinerExploration=1 -Dgraal.LoopRotation=true -Dgraal.OptWriteMotion=true -Dgraal.CompilerConfiguration=enterprise " fi 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 # Handle GTNH args if isType "GTNH"; then expandedDOpts="${expandedDOpts} -Dfml.readTimeout=180" java_major_version=$(mc-image-helper java-release) if (( java_major_version == 8 )); then debug "Setting GTNH java8 args." JVM_XX_OPTS="${JVM_XX_OPTS} -XX:+UseStringDeduplication -XX:+UseCompressedOops -XX:+UseCodeCacheFlushing " elif (( java_major_version >= 17 )); then debug "Setting GTNH java17+ args." expandedDOpts="${expandedDOpts} @java9args.txt" fi fi if [[ ${INIT_MEMORY} || ${MAX_MEMORY} ]]; then log "Setting initial memory to ${INIT_MEMORY:=${MEMORY}} and max to ${MAX_MEMORY:=${MEMORY}}" if isPercentage "$INIT_MEMORY"; then JVM_OPTS="-XX:InitialRAMPercentage=$(getPercentageValue "${INIT_MEMORY}") ${JVM_OPTS}" elif [[ ${INIT_MEMORY} ]]; then JVM_OPTS="-Xms${INIT_MEMORY} ${JVM_OPTS}" fi if isPercentage "$MAX_MEMORY"; then JVM_OPTS="-XX:MaxRAMPercentage=$(getPercentageValue "${MAX_MEMORY}") ${JVM_OPTS}" elif [[ ${MAX_MEMORY} ]]; then JVM_OPTS="-Xmx${MAX_MEMORY} ${JVM_OPTS}" fi fi function copyFilesForCurseForge() { if [ ! -e "${FTB_DIR}/server-icon.png" ] && [ -e "$serverIconPath" ]; then cp -f "$serverIconPath" "${FTB_DIR}/" fi cp -f ${baseDataDir}/eula.txt "${FTB_DIR}/" } if versionLessThan 'b1.8'; then echo " DISABLE_HEALTHCHECK=true " > "$mcHealthEnvPath" elif versionLessThan 1.3; then echo " MC_HEALTH_EXTRA_ARGS=( --use-old-server-list-ping ) " > "$mcHealthEnvPath" elif versionLessThan 1.7; then echo " MC_HEALTH_EXTRA_ARGS=( --use-server-list-ping ) " > "$mcHealthEnvPath" elif isTrue "$USES_PROXY_PROTOCOL"; then echo " MC_HEALTH_EXTRA_ARGS=( --use-proxy ) " > "$mcHealthEnvPath" else rm -f "$mcHealthEnvPath" fi mcServerRunnerArgs=( --stop-duration "${STOP_DURATION:-60}s" ) if isTrue "${CREATE_CONSOLE_IN_PIPE:-false}"; then mcServerRunnerArgs+=(--named-pipe "${CONSOLE_IN_NAMED_PIPE:-/tmp/minecraft-console-in}") fi if [[ ${STOP_SERVER_ANNOUNCE_DELAY} ]]; then mcServerRunnerArgs+=(--stop-server-announce-delay "${STOP_SERVER_ANNOUNCE_DELAY}s") fi if isTrue "${ENABLE_SSH}"; then mcServerRunnerArgs+=(--remote-console) fi if [[ ${TYPE} == "CURSEFORGE" && "${SERVER}" ]]; then copyFilesForCurseForge cd "${FTB_DIR}" || (logError "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 if isPercentage "$INIT_MEMORY" || isPercentage "$MAX_MEMORY"; then # Convert to bytes NORM_INIT_MEM=$(normalizeMemSize "$INIT_MEMORY") NORM_MAX_MEM=$(normalizeMemSize "$MAX_MEMORY") # Convert to MB ((NORM_INIT_MEM*=1048576)) ((NORM_MAX_MEM*=1048576)) cat > "${FTB_DIR}/settings-local.sh" < "${FTB_DIR}/settings-local.sh" < user_jvm_args.txt if isTrue ${SETUP_ONLY}; then echo "SETUP_ONLY: bash ${SERVER}" exit fi if isTrue "${DEBUG_EXEC}"; then set -x fi exec mc-server-runner "${mcServerRunnerArgs[@]}" --shell bash "${SERVER}" $EXTRA_ARGS else # If we have a bootstrap.txt file... feed that in to the server stdin if [ -f $bootstrapPath ]; then bootstrapArgs="--bootstrap $bootstrapPath" fi log "Starting the Minecraft server..." # Specifically want the variables to expand to args, so... # shellcheck disable=SC2206 finalArgs=( $JVM_XX_OPTS $JVM_OPTS $expandedDOpts ) if [[ $CUSTOM_JAR_EXEC ]]; then # shellcheck disable=SC2206 finalArgs+=($CUSTOM_JAR_EXEC) else finalArgs+=(-jar "$SERVER") fi # shellcheck disable=SC2206 finalArgs+=( "$@" $EXTRA_ARGS ) if isTrue ${SETUP_ONLY}; then echo "SETUP_ONLY: java ${finalArgs[*]}" exit fi if isTrue "${DEBUG_EXEC}"; then set -x fi exec mc-server-runner ${bootstrapArgs} "${mcServerRunnerArgs[@]}" java "${finalArgs[@]}" fi