#!/bin/bash function get_from_gh() { if [[ "${GH_TOKEN:-}" ]]; then # User has provided a Personal Access Token to mitigate rate-limiting issues if [[ -z "${oAuthScopes}" ]]; then oAuthScopes=$(curl -s -H "Authorization: token $GH_TOKEN" https://api.github.com/users/codertocat -I | grep x-oauth-scopes) fi if [[ ! "$oAuthScopes" =~ ^x-oauth-scopes:[[:space:]]*$ ]]; then # Don't use what you don't have to... log "ERROR: GH_TOKEN has permissions it doesn't need. Recreate or update this personal access token and disable ALL scopes." exit 1 else curl -fsSL -H "Authorization: token $GH_TOKEN" "${@:2}" "$1" fi else curl -fsSL "${@:2}" "$1" fi } function join_by() { local d=$1 shift echo -n "$1" shift printf "%s" "${@/#/$d}" } function get_major_version() { version=$1 echo "$version" | cut -d. -f 1-2 } function isURL() { local value=$1 if [[ ${value:0:8} == "https://" || ${value:0:7} == "http://" || ${value:0:6} == "ftp://" ]]; then return 0 else return 1 fi } function isValidFileURL() { suffix=${1:?Missing required suffix arg} url=${2:?Missing required url arg} [[ "$url" == http*://*.${suffix} || "$url" == http*://*.${suffix}\?* ]] } function resolveEffectiveUrl() { url="${1:?Missing required url argument}" if ! curl -Ls -o /dev/null -w "%{url_effective}" "$url"; then log "ERROR failed to resolve effective URL from $url" exit 2 fi } function getFilenameFromUrl() { url="${1:?Missing required url argument}" strippedOfQuery="${url%\?*}" basename "$strippedOfQuery" } function isTrue() { case "${1,,}" in true | yes | on | 1) return 0 ;; *) return 1 ;; esac } function isFalse() { case "${1,,}" in false | no | off | 0) return 0 ;; *) return 1 ;; esac } function isDebugging() { if isTrue "${DEBUG:-false}"; then return 0 else return 1 fi } function handleDebugMode() { if isDebugging; then set -x fi } function debug() { if isDebugging; then log "DEBUG: $*" fi } function logn() { echo -n "[init] $*" } function log() { local oldState # The return status when listing options is zero if all optnames are enabled, non- zero otherwise. oldState=$(shopt -po xtrace || true) shopt -u -o xtrace if isDebugging || isTrue "${LOG_TIMESTAMP:-false}"; then ts=" $(date --rfc-3339=seconds)" else ts= fi echo "[init]${ts} $*" eval "$oldState" } function logAutopause() { echo "[Autopause loop] $*" } function logAutopauseAction() { echo "[$(date -Iseconds)] [Autopause] $*" } function logAutostop() { echo "[Autostop loop] $*" } function logAutostopAction() { echo "[$(date -Iseconds)] [Autostop] $*" } function logRcon() { echo "[Rcon loop] $*" } 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 compare_version() { local left_version=$1 local comparison=$2 local right_version=$3 if [[ -z "$left_version" ]]; then echo "Left version is required" return 1 fi if [[ -z "$right_version" ]]; then echo "Right version is required" return 1 fi # Handle version channels ('a', 'b', or numeric) if [[ $left_version == a* || $left_version == b* ]]; then left_version=${left_version:1} fi if [[ $right_version == a* || $right_version == b* ]]; then right_version=${right_version:1} fi local left_version_channel=${left_version:0:1} if [[ $left_version_channel =~ [0-9] ]]; then left_version_channel='r' fi local right_version_channel=${right_version:0:1} if [[ $right_version_channel =~ [0-9] ]]; then right_version_channel='r' fi if [[ $comparison == "lt" && $left_version_channel < $right_version_channel ]]; then return 0 elif [[ $comparison == "lt" && $left_version_channel > $right_version_channel ]]; then return 1 elif [[ $comparison == "gt" && $left_version_channel > $right_version_channel ]]; then return 0 elif [[ $comparison == "gt" && $left_version_channel < $right_version_channel ]]; then return 1 elif [[ $comparison == "le" && $left_version_channel < $right_version_channel ]]; then return 0 elif [[ $comparison == "le" && $left_version_channel == $right_version_channel ]]; then return 0 elif [[ $comparison == "ge" && $left_version_channel > $right_version_channel ]]; then return 0 elif [[ $comparison == "ge" && $left_version_channel == $right_version_channel ]]; then return 0 elif [[ $comparison == "eq" && $left_version_channel == $right_version_channel ]]; then return 0 fi # Compare the versions using sort -V local result case $comparison in "lt") if [[ $(echo -e "$left_version\n$right_version" | sort -V | head -n1) == "$left_version" && "$left_version" != "$right_version" ]]; then result=0 else result=1 fi ;; "le") if [[ $(echo -e "$left_version\n$right_version" | sort -V | head -n1) == "$left_version" ]]; then result=0 else result=1 fi ;; "eq") if [[ "$left_version" == "$right_version" ]]; then result=0 else result=1 fi ;; "ge") if [[ $(echo -e "$left_version\n$right_version" | sort -V | tail -n1) == "$left_version" ]]; then result=0 else result=1 fi ;; "gt") if [[ $(echo -e "$left_version\n$right_version" | sort -V | tail -n1) == "$left_version" && "$left_version" != "$right_version" ]]; then result=0 else result=1 fi ;; *) echo "Unsupported comparison operator: $comparison" return 1 ;; esac return $result } function versionLessThan() { # Use if-else since strict mode might be enabled if compare_version "${VERSION}" "lt" "${1?}"; then return 0 else return 1 fi } requireVar() { if [ ! -v "$1" ]; then log "ERROR: $1 is required to be set" exit 1 fi if [ -z "${!1}" ]; then log "ERROR: $1 is required to be set" exit 1 fi } requireEnum() { var=${1?} shift for allowed in "$@"; do if [[ ${!var} = "$allowed" ]]; then return 0 fi done log "ERROR: $var must be set to one of $*" # exit 1 } function writeEula() { if ! echo "# Generated via Docker # $(date) 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 } function removeOldMods { if [ -d "$1" ]; then log "Removing old mods including='${REMOVE_OLD_MODS_INCLUDE}' excluding='${REMOVE_OLD_MODS_EXCLUDE}' up to depth=${REMOVE_OLD_MODS_DEPTH}" args=( --delete --type file --min-depth=1 --max-depth "${REMOVE_OLD_MODS_DEPTH}" --name "${REMOVE_OLD_MODS_INCLUDE}" --exclude-name "${REMOVE_OLD_MODS_EXCLUDE}" ) if ! isDebugging; then args+=(--quiet) fi mc-image-helper find "${args[@]}" "$1" fi } function get() { mc-image-helper get "$@" } function get_silent() { local flags=(-s) if isTrue "${DEBUG_GET:-false}"; then flags+=("--debug") fi mc-image-helper "${flags[@]}" get "$@" } function isFamily() { for f in "${@}"; do if [[ ${FAMILY^^} == "${f^^}" ]]; then return 0 fi done return 1 } function isType() { for t in "${@}"; do # shellcheck disable=SC2153 if [[ $TYPE == "$t" ]]; then return 0 fi done return 1 } function extract() { src=${1?} destDir=${2?} type=$(file -b --mime-type "${src}") case "${type}" in application/zip) unzip -o -q -d "${destDir}" "${src}" ;; application/x-tar|application/gzip|application/x-gzip|application/x-bzip2) tar -C "${destDir}" -xf "${src}" ;; application/zstd|application/x-zstd) tar -C "${destDir}" --use-compress-program=unzstd -xf "${src}" ;; *) log "ERROR: unsupported archive type: $type" return 1 ;; esac } function getDistro() { grep -E "^ID=" /etc/os-release | cut -d= -f2 | sed -e 's/"//g' } function checkSum() { local sum_file=${1?} # Get distro distro=$(getDistro) if [ "${distro}" == "debian" ] && sha1sum -c "${sum_file}" --status 2> /dev/null; then return 0 elif [ "${distro}" == "ubuntu" ] && sha1sum -c "${sum_file}" --status 2> /dev/null; then return 0 elif [ "${distro}" == "alpine" ] && sha1sum -c "${sum_file}" -s 2> /dev/null; then return 0 elif [ "${distro}" == "ol" ] && sha1sum -c "${sum_file}" --status 2> /dev/null; then return 0 else return 1 fi } function usesMods() { case "$FAMILY" in FORGE|FABRIC|HYBRID|SPONGE) return 0 esac return 1 } function usesPlugins() { case "$FAMILY" in SPIGOT|HYBRID) return 0 esac return 1 } function resolveVersion() { givenVersion="$VERSION" # shellcheck disable=SC2153 if ! VERSION=$(mc-image-helper resolve-minecraft-version "$VERSION"); then exit 2 fi log "Resolved version given ${givenVersion} into ${VERSION}" } function resolveFamily() { case "$TYPE" in PAPER|SPIGOT|BUKKIT|CANYON|PUFFERFISH|PURPUR) FAMILY=SPIGOT ;; FORGE) FAMILY=FORGE ;; FABRIC|QUILT) FAMILY=FABRIC ;; esac export FAMILY } function ensureRemoveAllModsOff() { reason=${1?} if isTrue "${REMOVE_OLD_MODS:-false}"; then log "WARNING using REMOVE_OLD_MODS interferes with $reason -- it is now disabled" REMOVE_OLD_MODS=false fi }