docker-minecraft-server/scripts/start-utils

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
}