🎉 initial codebase

This commit is contained in:
DrMaxNix 2024-09-12 20:16:59 +02:00
parent 1854866dc8
commit 1eb1bac60f
3 changed files with 381 additions and 0 deletions

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM alpine:latest
RUN apk --update add bash curl git jq openjdk21-jdk && rm -rf /var/cache/apk/*
COPY ./entrypoint.sh /entrypoint.sh
COPY ./cmd /usr/bin/cmd
ENTRYPOINT ["/entrypoint.sh"]

10
cmd Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
## CONSTANTS ##
readonly SERVER_FD_DIR="/opt/spigot-fd"
## APPEND TO STDIN FD ##
echo "$@" >> "${SERVER_FD_DIR}/stdin"

364
entrypoint.sh Executable file
View File

@ -0,0 +1,364 @@
#!/usr/bin/env bash
set -euo pipefail
## CONSTANTS ##
readonly DATA_DIR="/opt/spigot-data"
readonly SERVER_JAR_PATH="${DATA_DIR}/server.jar"
readonly BUILD_META_DIR="${DATA_DIR}/.build-meta"
readonly BUILD_DIR="/opt/spigotbuild"
readonly SERVER_FD_DIR="/opt/spigot-fd"
## PUBLIC VARIABLES ##
declare spigot_pid
declare proc_pid
## HANDLE EXIT SIGNALS ##
trap trap_exit EXIT SIGINT SIGTERM
trap_exit(){
# try stopping server first
if [[ "${spigot_pid:+x}" && "$spigot_pid" -gt 0 ]]; then
cmd "stop"
local w=0
while proc_running "$spigot_pid"; do
w=$(( w + 1 ))
snore 1
if [[ "$w" -ge 30 ]]; then
echo "[WARN ] Spigot did not react to stop command after 30s; using normal process shutdown actions" 1>&2
proc_pid="$spigot_pid"
break
fi
done
fi
# normal process
if [[ "${proc_pid:+x}" && "$proc_pid" -gt 0 ]]; then
kill -- $proc_pid 2> /dev/null || true
local q=0
while proc_running "$proc_pid"; do
q=$(( q + 1 ))
snore 1
if [[ "$q" -ge 15 ]]; then
echo "[WARN ] Sending kill to running process $proc_pid" 1>&2
kill -s KILL -- $proc_pid 2> /dev/null || true
break
fi
done
fi
exit 0
}
#
# LIB: Efficient sleep (does not create a new process).
#
snore(){
local IFS
[[ -n "${_snore_fd:-}" ]] || exec {_snore_fd}<> <(:)
read ${1:+-t "$1"} -u $_snore_fd || true
}
#
# HELPER: Check whether given pid is running.
#
# @param $1 Process ID.
#
# @exit 0: Process is running
# 1: Process is not running.
#
proc_running(){
# try reading state
local state_path="/proc/$1/stat"
[[ ! -f "$state_path" ]] && return 1
local state
state=$(cat "$state_path" 2> /dev/null | cut -d ' ' -f3) || return 1
# parse state
case "$state" in
R|S|D|Z|W|W|P|I) return 0;;
*) return 1;;
esac
}
#
# HELPER: Check whether spigot can be built for a given MC Version.
#
# @param $1 MC Version.
#
# @exit 0: Valid MC Version for a spigot build
# 1: Invalid MC Version for a spigot build.
#
mc_version_is_valid(){
local version="$1"
## CHECK FORMAT ##
if [[ ! "$version" =~ ^(latest|[0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
return 1
fi
## CHECK AVAILABILITY ##
local version_refs
version_refs=$(spigot_version_refs "$version") || return 1
## RETURN ##
return 0
}
#
# HELPER: Request spigot version refs from server.
#
# @param $1 MC Version.
#
# @return Refs JSON.
# @exit 0: Refs lookup successful
# 1: Refs lookup failed.
#
spigot_version_refs(){
## FETCH DATA ##
# make request
local api_response
local curl_exit=0
api_response=$(curl -fsm 10 "https://hub.spigotmc.org/versions/${1}.json") || curl_exit="$?"
if [[ "$curl_exit" -gt 0 ]]; then
echo "[ERROR] Failed to fetch refs" 1>&2
return 1
fi
# validate returned data
local refs
local jq_exit=0
refs=$(echo -n "$api_response" | jq -r ".refs" 2> /dev/null) || jq_exit="$?"
if [[ "$jq_exit" -gt 0 ]]; then
echo "[ERROR] Failed to parse refs api response" 1>&2
return 1
fi
## RETURN ##
echo -n "$refs"
return 0
}
#
# HELPER: (Re-)build server jar if required.
#
build_check(){
## CHECK FOR MISSING BUILD ARTIFACTS ##
for f in $SERVER_JAR_PATH ${BUILD_META_DIR}/{mc_version,spigot,craftbukkit}; do
if [[ ! -f "$f" ]]; then
echo "[WARN ] Missing build artifact detected: '$f'" 1>&2
build_run; return 0
fi
done
## CHECK WHETHER MC VERSION WAS CHANGED ##
local mc_version_curr=$(cat "${BUILD_META_DIR}/mc_version") || exit 1
if [[ "$MC_VERSION" != "$mc_version_curr" ]]; then
echo "[INFO ] MC Version changed" 1>&2
build_run; return 0
fi
## IGNORE REST IF AUTO UPDATE IS OFF ##
[[ "$AUTO_UPDATE" -gt 0 ]] && return 0
## CHECK FOR REFS CHANGE ##
# get all refs
local version_refs
version_refs=$(spigot_version_refs "$MC_VERSION") || exit 1
# compare
for r in CraftBukkit Spigot; do
# parse remote ref
local ref_remote
local jq_exit=0
ref_remote=$(echo -n "$version_refs" | jq -r ".${r}" 2> /dev/null) || jq_exit="$?"
if [[ "$jq_exit" -gt 0 || ! "$ref_remote" =~ ^[0123456789abcdef]{40}$ ]]; then
echo "[ERROR] Failed to parse remote ref '$r': $ref_remote" 1>&2
exit 1
fi
# load local ref
local ref_local_path="${BUILD_META_DIR}/$(echo -n "$r" | tr A-Z a-z)"
local ref_local
ref_local=$(cat "$ref_local_path")
# compare
if [[ "$ref_local" != "$ref_remote" ]]; then
echo "[INFO ] Remote ref '$r' has changed: '$ref_remote'" 1>&2
build_run; return 0
fi
done
## NO BUILD REQUIRED ##
return 0
}
#
# HELPER: Run a server build.
#
build_run(){
echo "[INFO ] (Re-)building server" 1>&2
## CLEAN UP BUILD AREA ##
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
pushd "$BUILD_DIR" > /dev/null
## BUILD ##
# download build tools
wget -O BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar & proc_pid="$!"
wait $proc_pid; proc_pid="0"
# run build tools
java -jar BuildTools.jar --rev "$MC_VERSION" --final-name "server.jar" & proc_pid="$!"
wait $proc_pid; proc_pid="0"
# verify
if [[ ! -f "server.jar" ]]; then
echo "[ERROR] Build did not produce a server jar" 1>&2
exit 1
fi
## STORE BUILD METADATA ##
# mc version
echo -n "$MC_VERSION" > "${BUILD_META_DIR}/mc_version"
# git rev hashes
for d in CraftBukkit Spigot; do
pushd "$d" > /dev/null
local git_rev
git_rev=$(git rev-parse HEAD)
echo -n "$git_rev" > "${BUILD_META_DIR}/$(echo -n "$d" | tr A-Z a-z)"
popd > /dev/null
done
## FINISH ##
# return to previous working directory
popd > /dev/null
# switch to new server jar
build_switch
# print info
echo "[INFO ] (Re-)build successful" 1>&2
return 0
}
#
# HELPER: Switch spigot jar to build output.
#
build_switch(){
## MAYBE SET ASIDE OLD JAR ##
[[ -f "$SERVER_JAR_PATH" ]] && mv "$SERVER_JAR_PATH" "${SERVER_JAR_PATH}.old"
if ! mv "${BUILD_DIR}/server.jar" "$SERVER_JAR_PATH"; then
echo "[ERROR] Failed to switch to new server jar" 1>&2
[[ -f "${SERVER_JAR_PATH}.old" ]] && echo "[INFO ] Old jar file retained as '${SERVER_JAR_PATH}.old'" 1>&2
exit 1
fi
## MAYBE DELETE OLD JAR ##
[[ -f "${SERVER_JAR_PATH}.old" ]] && rm "${SERVER_JAR_PATH}.old"
return 0
}
#
# HELPER: Initialize spigot server files.
#
spigot_init(){
## MAYBE WRITE EULA ##
if [[ ! -f "${DATA_DIR}/eula.txt" ]]; then
echo "eula=true" > "${DATA_DIR}/eula.txt"
fi
}
## VALIDATE ENV ##
# eula
if [[ ! "${AGREE_MINECRAFT_EULA:-}" =~ ^(on|yes|true|1)$ ]]; then
echo "[ERROR] You must agree to the Minecraft EULA (https://aka.ms/MinecraftEULA)" 1>&2
echo "[INFO ] To agree, set AGREE_MINECRAFT_EULA environment variable to 'yes'" 1>&2
exit 1
fi
# mc version
if [[ -z "${MC_VERSION:+x}" ]]; then
MC_VERSION="latest"
echo "[INFO ] Using default mc version: '$MC_VERSION'" 1>&2
fi
if ! mc_version_is_valid "$MC_VERSION"; then
echo "[ERROR] Not a valid MC Version for a spigot build (yet): '$MC_VERSION'" 1>&2
exit 1
fi
# auto update
if [[ -z "${AUTO_UPDATE:+x}" ]]; then
AUTO_UPDATE="yes"
echo "[INFO ] Auto update enabled by default" 1>&2
fi
case "$AUTO_UPDATE" in
"on"|"yes"|"true"|1) AUTO_UPDATE="0";;
"off"|"no"|"false"|0) AUTO_UPDATE="1";;
*)
echo "[ERROR] Invalid option for auto update: '$AUTO_UPDATE'" 1>&2
exit 1
;;
esac
## SET UP USED DIRECTORIES ##
mkdir -p "$DATA_DIR"
mkdir -p "$BUILD_META_DIR"
mkdir -p "$SERVER_FD_DIR"
## MAYBE (RE-)BUILD SERVER JAR ##
build_check
## MAYBE INITIALIZE SERVER FILES ##
spigot_init
## RUN SERVER JAR ##
# prepare fds
echo -n "" > "${SERVER_FD_DIR}/stdin"
# switch into data directory
pushd "$DATA_DIR" > /dev/null
# start server process and stream its output
java -jar server.jar < <(tail -fn 0 "${SERVER_FD_DIR}/stdin") | while IFS= read -r line; do
echo "[LOG ] $line" 1>&2
done & spigot_pid="$!"
wait $spigot_pid