mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-04-23 15:36:07 +00:00
167 lines
5.1 KiB
Bash
167 lines
5.1 KiB
Bash
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
SERVER_DIR="/srv/mc"
|
||
SERVER_EXE="Minecraft.Server.exe"
|
||
SERVER_PORT="${SERVER_PORT:-25565}"
|
||
SERVER_BIND_IP="${SERVER_BIND_IP:-0.0.0.0}"
|
||
|
||
PERSIST_DIR="/srv/persist"
|
||
WINE_CMD=""
|
||
|
||
ensure_persist_file() {
|
||
local persist_path="$1"
|
||
local runtime_path="$2"
|
||
local default_text="$3"
|
||
|
||
if [ ! -f "${persist_path}" ]; then
|
||
if [ -f "${runtime_path}" ] && [ ! -L "${runtime_path}" ]; then
|
||
cp -f "${runtime_path}" "${persist_path}"
|
||
else
|
||
printf '%b' "${default_text}" > "${persist_path}"
|
||
fi
|
||
fi
|
||
|
||
if [ -e "${runtime_path}" ] && [ ! -L "${runtime_path}" ]; then
|
||
rm -f "${runtime_path}"
|
||
fi
|
||
|
||
ln -sfn "${persist_path}" "${runtime_path}"
|
||
}
|
||
|
||
wait_for_xvfb_ready() {
|
||
local display="$1"
|
||
local xvfb_pid="$2"
|
||
local wait_seconds="${XVFB_WAIT_SECONDS:-10}"
|
||
local wait_ticks=$((wait_seconds * 10))
|
||
local display_number="${display#:}"
|
||
display_number="${display_number%%.*}"
|
||
|
||
if [ -z "${display_number}" ] || ! [[ "${display_number}" =~ ^[0-9]+$ ]]; then
|
||
echo "[error] Invalid DISPLAY format for Xvfb wait: ${display}" >&2
|
||
return 1
|
||
fi
|
||
|
||
local socket_path="/tmp/.X11-unix/X${display_number}"
|
||
local elapsed=0
|
||
|
||
while [ "${elapsed}" -lt "${wait_ticks}" ]; do
|
||
if ! kill -0 "${xvfb_pid}" 2>/dev/null; then
|
||
echo "[error] Xvfb exited before the display became ready." >&2
|
||
if [ -f /tmp/xvfb.log ]; then
|
||
echo "[error] ---- /tmp/xvfb.log ----" >&2
|
||
tail -n 200 /tmp/xvfb.log >&2 || true
|
||
echo "[error] ----------------------" >&2
|
||
fi
|
||
return 1
|
||
fi
|
||
|
||
if [ -S "${socket_path}" ]; then
|
||
# Keep a short extra delay so Wine does not race display handshake.
|
||
sleep 0.2
|
||
if kill -0 "${xvfb_pid}" 2>/dev/null && [ -S "${socket_path}" ]; then
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
# One more liveness check after the ready probe branch.
|
||
if ! kill -0 "${xvfb_pid}" 2>/dev/null; then
|
||
echo "[error] Xvfb exited during display readiness probe." >&2
|
||
if [ -f /tmp/xvfb.log ]; then
|
||
echo "[error] ---- /tmp/xvfb.log ----" >&2
|
||
tail -n 200 /tmp/xvfb.log >&2 || true
|
||
echo "[error] ----------------------" >&2
|
||
fi
|
||
return 1
|
||
fi
|
||
|
||
sleep 0.1
|
||
elapsed=$((elapsed + 1))
|
||
done
|
||
|
||
echo "[error] Timed out waiting for Xvfb display ${display}." >&2
|
||
if [ -f /tmp/xvfb.log ]; then
|
||
echo "[error] ---- /tmp/xvfb.log ----" >&2
|
||
tail -n 200 /tmp/xvfb.log >&2 || true
|
||
echo "[error] ----------------------" >&2
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
if [ ! -d "$SERVER_DIR" ]; then
|
||
echo "[error] Server directory not found: $SERVER_DIR" >&2
|
||
exit 1
|
||
fi
|
||
|
||
cd "$SERVER_DIR"
|
||
|
||
if [ ! -f "$SERVER_EXE" ]; then
|
||
echo "[error] ${SERVER_EXE} not found in ${SERVER_DIR}" >&2
|
||
echo "[hint] Rebuild image with a valid MC_RUNTIME_DIR build arg that contains dedicated server runtime files." >&2
|
||
exit 1
|
||
fi
|
||
|
||
mkdir -p "${PERSIST_DIR}"
|
||
|
||
# created because it is not implemented on the server side
|
||
mkdir -p "${PERSIST_DIR}/GameHDD"
|
||
|
||
ensure_persist_file "${PERSIST_DIR}/server.properties" "server.properties" ""
|
||
ensure_persist_file "${PERSIST_DIR}/banned-players.json" "banned-players.json" "[]\n"
|
||
ensure_persist_file "${PERSIST_DIR}/banned-ips.json" "banned-ips.json" "[]\n"
|
||
|
||
# differs from the structure, but it’s reorganized into a more manageable structure to the host side
|
||
if [ -e "Windows64/GameHDD" ] && [ ! -L "Windows64/GameHDD" ]; then
|
||
rm -rf "Windows64/GameHDD"
|
||
fi
|
||
ln -sfn "${PERSIST_DIR}/GameHDD" "Windows64/GameHDD"
|
||
|
||
# for compatibility with other images
|
||
if command -v wine64 >/dev/null 2>&1; then
|
||
WINE_CMD="wine64"
|
||
elif [ -x "/usr/lib/wine/wine64" ]; then
|
||
WINE_CMD="/usr/lib/wine/wine64"
|
||
elif command -v wine >/dev/null 2>&1; then
|
||
WINE_CMD="wine"
|
||
else
|
||
echo "[error] No Wine executable found (wine64/wine)." >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [ ! -d "${WINEPREFIX}" ] || [ -z "$(ls -A "${WINEPREFIX}" 2>/dev/null)" ]; then
|
||
mkdir -p "${WINEPREFIX}"
|
||
fi
|
||
|
||
# in the current implementation, a virtual screen is required because the client-side logic is being called for compatibility
|
||
if [ -z "${DISPLAY:-}" ]; then
|
||
export DISPLAY="${XVFB_DISPLAY:-:99}"
|
||
XVFB_SCREEN="${XVFB_SCREEN:-64x64x16}"
|
||
DISPLAY_NUMBER="${DISPLAY#:}"
|
||
DISPLAY_NUMBER="${DISPLAY_NUMBER%%.*}"
|
||
if [ -z "${DISPLAY_NUMBER}" ] || ! [[ "${DISPLAY_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||
echo "[error] Invalid XVFB_DISPLAY format: ${DISPLAY}" >&2
|
||
exit 1
|
||
fi
|
||
XVFB_SOCKET="/tmp/.X11-unix/X${DISPLAY_NUMBER}"
|
||
XVFB_LOCK="/tmp/.X${DISPLAY_NUMBER}-lock"
|
||
# The check is performed assuming the same container will be restarted.
|
||
if [ -S "${XVFB_SOCKET}" ] || [ -e "${XVFB_LOCK}" ]; then
|
||
echo "[warn] Removing stale Xvfb state for ${DISPLAY} before startup." >&2
|
||
rm -f "${XVFB_SOCKET}" "${XVFB_LOCK}"
|
||
fi
|
||
Xvfb "${DISPLAY}" -nolisten tcp -screen 0 "${XVFB_SCREEN}" >/tmp/xvfb.log 2>&1 &
|
||
XVFB_PID=$!
|
||
wait_for_xvfb_ready "${DISPLAY}" "${XVFB_PID}"
|
||
echo "[info] Xvfb ready on ${DISPLAY} (pid=${XVFB_PID}, screen=${XVFB_SCREEN})"
|
||
else
|
||
echo "[info] Using existing DISPLAY=${DISPLAY}; skipping Xvfb startup"
|
||
fi
|
||
|
||
args=(
|
||
-port "${SERVER_PORT}"
|
||
-bind "${SERVER_BIND_IP}"
|
||
)
|
||
|
||
echo "[info] Starting ${SERVER_EXE} on ${SERVER_BIND_IP}:${SERVER_PORT}"
|
||
exec "${WINE_CMD}" "${SERVER_EXE}" "${args[@]}"
|