mirror of
https://github.com/itzg/docker-minecraft-server
synced 2025-01-10 19:28:43 +00:00
515 lines
11 KiB
Bash
Executable file
515 lines
11 KiB
Bash
Executable file
#!/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...
|
|
logError "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 applyResultsFile() {
|
|
# grab SERVER and export it
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$1"
|
|
set +a
|
|
}
|
|
|
|
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
|
|
|
|
[[ $value =~ ^(https?|ftp):// ]]
|
|
}
|
|
|
|
function isValidFileURL() {
|
|
suffix=${1:?Missing required suffix arg}
|
|
url=${2:?Missing required url arg}
|
|
|
|
[[ "$url" =~ ^http.*://.*\.${suffix}(\?.*)?$ ]]
|
|
}
|
|
|
|
function resolveEffectiveUrl() {
|
|
url="${1:?Missing required url argument}"
|
|
if ! curl -Ls -o /dev/null -w "%{url_effective}" "$url"; then
|
|
logError "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() {
|
|
isTrue "${DEBUG:-false}"
|
|
}
|
|
|
|
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
|
|
ts=
|
|
if isDebugging || isTrue "${LOG_TIMESTAMP:-false}"; then
|
|
ts=" $(date --rfc-3339=seconds)"
|
|
fi
|
|
echo -e "[init]${ts} $*"
|
|
eval "$oldState"
|
|
}
|
|
|
|
function getSudoFromDistro(){
|
|
distro=$(getDistro)
|
|
command=
|
|
if [[ $distro == alpine ]]; then
|
|
command="su-exec"
|
|
else
|
|
command="gosu"
|
|
fi
|
|
echo $command
|
|
}
|
|
|
|
# Refer to https://unix.stackexchange.com/a/10065/102376
|
|
function isTerminal() {
|
|
if test -t 1; then
|
|
# see if it supports colors...
|
|
ncolors=$(tput colors)
|
|
test -n "$ncolors" && test "$ncolors" -ge 8
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
errorLogTag="[ERROR]"
|
|
warningLogTag="[WARN]"
|
|
|
|
if isTerminal; then
|
|
normal="$(tput sgr0)"
|
|
red="$(tput setaf 1)"
|
|
yellow="$(tput setaf 3)"
|
|
function getErrorColoredLogString() {
|
|
echo "${red}$errorLogTag $* ${normal}"
|
|
}
|
|
function getWarningColoredLogString() {
|
|
echo "${yellow}$warningLogTag $* ${normal}"
|
|
}
|
|
else
|
|
function getErrorColoredLogString() {
|
|
echo "$errorLogTag $*"
|
|
}
|
|
function getWarningColoredLogString() {
|
|
echo "$warningLogTag $*"
|
|
}
|
|
fi
|
|
|
|
function error() {
|
|
echo -e "$(getErrorColoredLogString "$*")"
|
|
}
|
|
|
|
function logError() {
|
|
if isDebugging; then
|
|
set +x
|
|
fi
|
|
log "$(getErrorColoredLogString "$*")"
|
|
if isDebugging; then
|
|
set -x
|
|
fi
|
|
}
|
|
|
|
function warning() {
|
|
if isDebugging; then
|
|
set +x
|
|
fi
|
|
echo -e "$(getWarningColoredLogString "$*")"
|
|
if isDebugging; then
|
|
set -x
|
|
fi
|
|
}
|
|
|
|
function logWarning() {
|
|
log "$(getWarningColoredLogString "$*")"
|
|
}
|
|
|
|
function isNumeric() {
|
|
[[ $1 =~ ^[0-9]+$ ]]
|
|
}
|
|
|
|
function isNumericElseSetToDefault() {
|
|
local var_name="$1"
|
|
local default_value="$2"
|
|
|
|
if ! isNumeric ${!var_name} ; then
|
|
eval "$var_name=$default_value"
|
|
export "$var_name"
|
|
logWarning "$var_name is not numeric, set to $default_value (seconds)"
|
|
fi
|
|
}
|
|
|
|
function checkIfNotZeroElseSetToDefault() {
|
|
local var_name="$1"
|
|
local default_value="$2"
|
|
if [ "${!var_name}" -eq "0" ] ; then
|
|
eval "$var_name=$default_value"
|
|
export "$var_name"
|
|
logWarning "$var_name must not be 0, set to $default_value (seconds)"
|
|
fi
|
|
}
|
|
|
|
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=1
|
|
|
|
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
|
|
fi
|
|
;;
|
|
"le")
|
|
if [[ $(echo -e "$left_version\n$right_version" | sort -V | head -n1) == "$left_version" ]]; then
|
|
result=0
|
|
fi
|
|
;;
|
|
"eq")
|
|
if [[ "$left_version" == "$right_version" ]]; then
|
|
result=0
|
|
fi
|
|
;;
|
|
"ge")
|
|
if [[ $(echo -e "$left_version\n$right_version" | sort -V | tail -n1) == "$left_version" ]]; then
|
|
result=0
|
|
fi
|
|
;;
|
|
"gt")
|
|
if [[ $(echo -e "$left_version\n$right_version" | sort -V | tail -n1) == "$left_version" && "$left_version" != "$right_version" ]]; then
|
|
result=0
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Unsupported comparison operator: $comparison"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
return $result
|
|
}
|
|
|
|
function versionLessThan() {
|
|
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
|
|
|
|
eval "$oldState"
|
|
|
|
# Verify strict mode because it might be enabled
|
|
compare_version "${VERSION}" "lt" "${1?}"
|
|
}
|
|
|
|
function writeEula() {
|
|
if ! echo "# Generated via Docker
|
|
# $(date)
|
|
eula=${EULA,,}
|
|
" >/data/eula.txt; then
|
|
logError "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}"
|
|
;;
|
|
*)
|
|
logError "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)
|
|
|
|
case "${distro}" in
|
|
debian | ubuntu | ol)
|
|
sha1sum -c "${sum_file}" --status 2>/dev/null && return 0
|
|
;;
|
|
alpine)
|
|
sha1sum -c "${sum_file}" -s 2>/dev/null && return 0
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
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
|
|
logWarning "Using REMOVE_OLD_MODS interferes with $reason -- it is now disabled"
|
|
REMOVE_OLD_MODS=false
|
|
fi
|
|
}
|