#!/usr/bin/env bash set -euo pipefail # ── Minecraft Java Server Installer (VM) ── v3.0 ── # Uses PaperMC Fill v3 API (fill.papermc.io) USER_AGENT="minecraft-server-Proxmox/3.0 (https://github.com/TimInTech/minecraft-server-Proxmox)" sudo apt update && sudo apt upgrade -y sudo apt install -y screen wget curl jq unzip ca-certificates gnupg ensure_java() { if command -v java >/dev/null 2>&1; then local ver ver=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d. -f1) if [[ -n "$ver" ]] && (( ver >= 25 )); then return 0 fi fi # Prefer OpenJDK 25 / 21; fallback to Amazon Corretto 25 via APT keyring. if sudo apt-get install -y openjdk-25-jre-headless 2>/dev/null || sudo apt-get install -y openjdk-21-jre-headless 2>/dev/null; then return 0; fi # NOTE: Adding a vendor APT source; restrict with signed-by keyring. sudo install -d -m 0755 /usr/share/keyrings curl -fsSL https://apt.corretto.aws/corretto.key | sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/corretto.gpg echo "deb [signed-by=/usr/share/keyrings/corretto.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list >/dev/null sudo apt-get update sudo apt-get install -y java-25-amazon-corretto-jdk || sudo apt-get install -y java-21-amazon-corretto-jdk } ensure_java sudo mkdir -p /opt/minecraft if ! id -u minecraft >/dev/null 2>&1; then sudo useradd -r -m -s /bin/bash minecraft; fi sudo chown -R minecraft:minecraft /opt/minecraft cd /opt/minecraft printf '%s\n' "eula=true" > eula.txt # Autosize memory: Xms=RAM/4, Xmx=RAM/2; floors 1024M/2048M; cap Xmx ≤16G. mem_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo); mem_mb=$((mem_kb/1024)) xmx=$(( mem_mb/2 )) if (( xmx < 2048 )); then xmx=2048 fi (( xmx > 16384 )) && xmx=16384 xms=$(( mem_mb/4 )) if (( xms < 1024 )); then xms=1024 fi (( xms > xmx )) && xms=$xmx # ── Download latest stable PaperMC via Fill v3 API ── FILL_API="https://fill.papermc.io/v3/projects/paper" # NOTE: Fill v3 groups versions under keys (e.g. "26.1" → ["26.1.2","26.1.1"]). # Semantic-sort the group keys (not lexicographic, to handle 1.9 vs 1.21), pick the # newest group, then take its newest patch. Returning the bare group key would 404 # on /versions//builds (issue #74). LATEST_VERSION=$(curl -fsSL -H "User-Agent: ${USER_AGENT}" "${FILL_API}" | \ jq -r '.versions as $v | ($v | keys | map(split(".") | map(tonumber)) | sort | last | map(tostring) | join(".")) as $g | $v[$g][0]') echo "Latest Minecraft version: ${LATEST_VERSION}" BUILDS_JSON=$(curl -fsSL -H "User-Agent: ${USER_AGENT}" "${FILL_API}/versions/${LATEST_VERSION}/builds") # Filter for STABLE channel; fall back to latest build if no stable exists yet STABLE_BUILD=$(printf '%s' "$BUILDS_JSON" | jq -r ' (map(select(.channel == "STABLE")) | sort_by(.id) | last) // (sort_by(.id) | last)') if [[ -z "$STABLE_BUILD" || "$STABLE_BUILD" == "null" ]]; then echo "ERROR: No builds found for version ${LATEST_VERSION}" >&2 exit 1 fi LATEST_BUILD=$(printf '%s' "$STABLE_BUILD" | jq -r '.id') DOWNLOAD_URL=$(printf '%s' "$STABLE_BUILD" | jq -r '.downloads."server:default".url // empty') EXPECTED_SHA=$(printf '%s' "$STABLE_BUILD" | jq -r '.downloads."server:default".checksums.sha256 // empty') if [[ -z "$DOWNLOAD_URL" ]]; then echo "ERROR: No download URL in API response for build ${LATEST_BUILD}" >&2 exit 1 fi echo "Downloading PaperMC build ${LATEST_BUILD} for ${LATEST_VERSION}..." # NOTE: Enforce integrity and basic size sanity to avoid HTML error pages saved as JAR. curl -fL -H "User-Agent: ${USER_AGENT}" --retry 3 --retry-delay 2 -o server.jar "$DOWNLOAD_URL" ACTUAL_SHA=$(sha256sum server.jar | awk '{print $1}') if [[ -n "$EXPECTED_SHA" && "$EXPECTED_SHA" != "null" && "$ACTUAL_SHA" != "$EXPECTED_SHA" ]]; then echo "ERROR: SHA256 mismatch for PaperMC (expected ${EXPECTED_SHA}, got ${ACTUAL_SHA})" >&2 exit 1 fi jar_size=$(stat -c '%s' server.jar) if (( jar_size < 5242880 )); then echo "ERROR: Downloaded server.jar is too small (${jar_size} bytes). Likely an error page." >&2 exit 1 fi echo "SHA256 verified: ${ACTUAL_SHA}" cat > start.sh < update.sh <<'E2' #!/usr/bin/env bash set -euo pipefail cd /opt/minecraft || exit 1 USER_AGENT="minecraft-server-Proxmox/3.0 (https://github.com/TimInTech/minecraft-server-Proxmox)" FILL_API="https://fill.papermc.io/v3/projects/paper" LATEST_VERSION=$(curl -fsSL -H "User-Agent: ${USER_AGENT}" "${FILL_API}" | \ jq -r '.versions as $v | ($v | keys | map(split(".") | map(tonumber)) | sort | last | map(tostring) | join(".")) as $g | $v[$g][0]') echo "Latest Minecraft version: ${LATEST_VERSION}" BUILDS_JSON=$(curl -fsSL -H "User-Agent: ${USER_AGENT}" "${FILL_API}/versions/${LATEST_VERSION}/builds") STABLE_BUILD=$(printf '%s' "$BUILDS_JSON" | jq -r ' (map(select(.channel == "STABLE")) | sort_by(.id) | last) // (sort_by(.id) | last)') if [[ -z "$STABLE_BUILD" || "$STABLE_BUILD" == "null" ]]; then echo "ERROR: No builds found for version ${LATEST_VERSION}" >&2 exit 1 fi LATEST_BUILD=$(printf '%s' "$STABLE_BUILD" | jq -r '.id') DOWNLOAD_URL=$(printf '%s' "$STABLE_BUILD" | jq -r '.downloads."server:default".url // empty') EXPECTED_SHA=$(printf '%s' "$STABLE_BUILD" | jq -r '.downloads."server:default".checksums.sha256 // empty') if [[ -z "$DOWNLOAD_URL" ]]; then echo "ERROR: No download URL in API response for build ${LATEST_BUILD}" >&2 exit 1 fi echo "Downloading PaperMC build ${LATEST_BUILD}..." curl -fL -H "User-Agent: ${USER_AGENT}" --retry 3 --retry-delay 2 -o server.jar "$DOWNLOAD_URL" jar_size=$(stat -c '%s' server.jar) if (( jar_size < 5242880 )); then echo "ERROR: Downloaded server.jar is too small (${jar_size} bytes). Likely an error page." >&2 exit 1 fi ACTUAL_SHA=$(sha256sum server.jar | awk '{print $1}') if [[ -n "${EXPECTED_SHA}" && "${EXPECTED_SHA}" != "null" ]]; then if [[ "${ACTUAL_SHA}" != "${EXPECTED_SHA}" ]]; then echo "ERROR: SHA256 mismatch for PaperMC (expected ${EXPECTED_SHA}, got ${ACTUAL_SHA})" >&2 exit 1 fi echo "SHA256 verified: ${ACTUAL_SHA}" else echo "WARNING: No upstream SHA provided; computed: ${ACTUAL_SHA}" fi echo "✅ Update complete to version ${LATEST_VERSION} (build ${LATEST_BUILD})" E2 chmod +x update.sh sudo chown -R minecraft:minecraft /opt/minecraft # Ensure screen runtime directory exists with mode 0777 (required for non-root screen sessions) sudo install -d -m 0777 -o root -g root /run/screen || true sudo chmod 0777 /run/screen || true printf 'd /run/screen 0777 root root -\n' | sudo tee /etc/tmpfiles.d/screen.conf >/dev/null sudo systemd-tmpfiles --create /etc/tmpfiles.d/screen.conf || true if command -v runuser >/dev/null 2>&1; then runuser -u minecraft -- bash -lc 'cd /opt/minecraft && screen -dmS minecraft ./start.sh' else sudo -u minecraft bash -lc 'cd /opt/minecraft && screen -dmS minecraft ./start.sh' fi echo "✅ Minecraft Java setup complete. Attach: screen -r minecraft"