From 33ed09da09ce664bdebf6d397077ea29f67a28f5 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sat, 26 Sep 2026 13:47:19 -0700 Subject: [PATCH] Script cleanup. --- .classpath | 65 ++++++++++++++--- .env.example | 9 +++ .gitignore | 11 ++- .project | 11 +++ .settings/org.eclipse.jdt.apt.core.prefs | 2 + .settings/org.eclipse.jdt.core.prefs | 11 +-- tools/backup-server.sh | 64 +++++++++++++++++ tools/build-plugin.sh | 14 ++++ tools/deploy-plugin.sh | 29 ++++++++ tools/restore-server.sh | 68 ++++++++++++++++++ tools/setup-env.sh | 39 ++++++++++ tools/start-server.sh | 92 ++++++++++++++++++++++++ tools/stop-server.sh | 34 +++++++++ 13 files changed, 436 insertions(+), 13 deletions(-) create mode 100644 .env.example create mode 100644 .settings/org.eclipse.jdt.apt.core.prefs create mode 100755 tools/backup-server.sh create mode 100755 tools/build-plugin.sh create mode 100755 tools/deploy-plugin.sh create mode 100755 tools/restore-server.sh create mode 100755 tools/setup-env.sh create mode 100755 tools/start-server.sh create mode 100755 tools/stop-server.sh diff --git a/.classpath b/.classpath index 8fe0193..1dec708 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,57 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7fe0eda --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Dev-server config for this project's tools/ scripts. +# Plain KEY=value only (no shell expansion). Environment variables take precedence. + +PAPER_VERSION=26.2 +PAPER_BUILD=129 +MINECRAFT_SERVER_JAR=paper-26.2-129.jar +# Relative paths are resolved from this directory; ~/ is expanded. +MINECRAFT_SERVER_PATH=./server +MINECRAFT_BACKUP_PATH=./backups diff --git a/.gitignore b/.gitignore index 3633933..32a7f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,15 @@ +.gradle/ +build/ +target/ +.worktrees/ +bin/ +/backups/ +/server/ +.env + +# Old entries double check these later. /server /Plugin.jardesc /Plugin-Release.jardesc /lib /target -.worktrees/ diff --git a/.project b/.project index 9a2c887..7d50a9c 100644 --- a/.project +++ b/.project @@ -20,4 +20,15 @@ org.eclipse.m2e.core.maven2Nature org.eclipse.jdt.core.javanature + + + 1790453742830 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/.settings/org.eclipse.jdt.apt.core.prefs b/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..d4313d4 --- /dev/null +++ b/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index f6b2e38..c79b505 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,13 +1,16 @@ -#Sat Mar 03 21:02:20 PST 2012 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/tools/backup-server.sh b/tools/backup-server.sh new file mode 100755 index 0000000..8ba750a --- /dev/null +++ b/tools/backup-server.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Backs up plugins, config, worlds, and settings from MINECRAFT_SERVER_PATH +# to MINECRAFT_BACKUP_PATH/backup-[-label].tar.bz2. +# +# Usage: backup-server.sh [-f|--force] [label] +# -f Back up even if the server is running (worlds may be inconsistent). + +set -euo pipefail + +TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=setup-env.sh +source "$TOOLS_DIR/setup-env.sh" + +FORCE=false +LABEL="" +for arg in "$@"; do + case "$arg" in + -f|--force) FORCE=true ;; + -h|--help) sed -n '2,7p' "$0"; exit 0 ;; + -*) echo "Unknown option: $arg" >&2; exit 1 ;; + *) LABEL="$arg" ;; + esac +done + +[[ -d "$MINECRAFT_SERVER_PATH" ]] || { echo "Error: $MINECRAFT_SERVER_PATH does not exist." >&2; exit 1; } + +if pgrep -f "java.*paper-.*\.jar" >/dev/null && ! $FORCE; then + echo "Error: the server is running. Stop it first (tools/stop-server.sh) or pass --force." >&2 + exit 1 +fi + +cd "$MINECRAFT_SERVER_PATH" +shopt -s nullglob + +ITEMS=() +[[ -d plugins ]] && ITEMS+=(plugins) +[[ -d config ]] && ITEMS+=(config) +for dir in */; do + dir="${dir%/}" + [[ -f "$dir/level.dat" ]] && ITEMS+=("$dir") +done +for f in *.yml *.yaml *.json *.properties eula.txt; do + ITEMS+=("$f") +done + +if (( ${#ITEMS[@]} == 0 )); then + echo "Error: nothing to back up in $MINECRAFT_SERVER_PATH." >&2 + exit 1 +fi + +# Keep labels filename-safe. +LABEL="${LABEL//[^A-Za-z0-9._-]/_}" +NAME="backup-$(date +%Y%m%d-%H%M%S)${LABEL:+-$LABEL}.tar.bz2" +mkdir -p "$MINECRAFT_BACKUP_PATH" +ARCHIVE="$MINECRAFT_BACKUP_PATH/$NAME" + +echo "Backing up: ${ITEMS[*]}" +trap 'rm -f "$ARCHIVE.part"' EXIT +tar --exclude 'plugins/.paper-remapped' --exclude '*/session.lock' \ + -cjf "$ARCHIVE.part" "${ITEMS[@]}" +mv "$ARCHIVE.part" "$ARCHIVE" +trap - EXIT + +echo "Created $ARCHIVE ($(du -h "$ARCHIVE" | awk '{print $1}'))" diff --git a/tools/build-plugin.sh b/tools/build-plugin.sh new file mode 100755 index 0000000..6136d0d --- /dev/null +++ b/tools/build-plugin.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +# Find project root (one directory above this script) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_ROOT" + +if [[ -x ./mvnw ]]; then + ./mvnw package +else + mvn package +fi diff --git a/tools/deploy-plugin.sh b/tools/deploy-plugin.sh new file mode 100755 index 0000000..87b738d --- /dev/null +++ b/tools/deploy-plugin.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# shellcheck source=setup-env.sh +source "$SCRIPT_DIR/setup-env.sh" + +if [[ -z "$MINECRAFT_SERVER_PATH" ]]; then + echo "Error: MINECRAFT_SERVER_PATH environment variable is not set." + exit 1 +fi + +# Skip Maven side artifacts (shade originals, sources, javadoc). +PLUGIN_JAR=$(ls -t "$PROJECT_ROOT"/target/*.jar 2>/dev/null \ + | grep -vE '/original-[^/]*\.jar$|-(sources|javadoc|tests)\.jar$' | head -n1 || true) +if [[ ! -f "$PLUGIN_JAR" ]]; then + echo "Error: No plugin jar found in $PROJECT_ROOT/target. Build the plugin first." + exit 1 +fi + +if find "$PROJECT_ROOT/src/main/java" "$PROJECT_ROOT/src/main/resources" -type f -newer "$PLUGIN_JAR" | grep -q .; then + echo "Warning: The built plugin jar is older than some source files. Consider rebuilding." +fi + +mkdir -p "$MINECRAFT_SERVER_PATH/plugins" +cp "$PLUGIN_JAR" "$MINECRAFT_SERVER_PATH/plugins/" +echo "Deployed $PLUGIN_JAR to $MINECRAFT_SERVER_PATH/plugins/" diff --git a/tools/restore-server.sh b/tools/restore-server.sh new file mode 100755 index 0000000..63403bf --- /dev/null +++ b/tools/restore-server.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Restores a backup made by backup-server.sh into MINECRAFT_SERVER_PATH. +# Each top-level item in the archive (plugins, config, worlds, settings) is +# replaced; a "pre-restore" backup of the current state is taken first. +# +# Usage: restore-server.sh [-y|--yes] [--no-backup] [backup-file|name] +# Defaults to the newest backup in MINECRAFT_BACKUP_PATH. + +set -euo pipefail + +TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=setup-env.sh +source "$TOOLS_DIR/setup-env.sh" + +ASSUME_YES=false +SAFETY_BACKUP=true +ARCHIVE="" +for arg in "$@"; do + case "$arg" in + -y|--yes) ASSUME_YES=true ;; + --no-backup) SAFETY_BACKUP=false ;; + -h|--help) sed -n '2,8p' "$0"; exit 0 ;; + -*) echo "Unknown option: $arg" >&2; exit 1 ;; + *) ARCHIVE="$arg" ;; + esac +done + +if [[ -z "$ARCHIVE" ]]; then + ARCHIVE=$(ls -t "$MINECRAFT_BACKUP_PATH"/backup-*.tar.bz2 2>/dev/null | head -n1 || true) + [[ -n "$ARCHIVE" ]] || { echo "Error: no backups found in $MINECRAFT_BACKUP_PATH." >&2; exit 1; } +elif [[ ! -f "$ARCHIVE" && -f "$MINECRAFT_BACKUP_PATH/$ARCHIVE" ]]; then + ARCHIVE="$MINECRAFT_BACKUP_PATH/$ARCHIVE" +fi +[[ -f "$ARCHIVE" ]] || { echo "Error: backup not found: $ARCHIVE" >&2; exit 1; } +ARCHIVE="$(cd "$(dirname "$ARCHIVE")" && pwd)/$(basename "$ARCHIVE")" + +if pgrep -f "java.*paper-.*\.jar" >/dev/null; then + echo "Error: the server is running. Stop it first (tools/stop-server.sh)." >&2 + exit 1 +fi + +ENTRIES=$(tar -tjf "$ARCHIVE") +if grep -qE '^/|(^|/)\.\.(/|$)' <<<"$ENTRIES"; then + echo "Error: archive contains absolute or '..' paths; refusing to restore." >&2 + exit 1 +fi +TOP_LEVEL=$(sed -E 's#^\./##; s#/.*##' <<<"$ENTRIES" | { grep -vE '^\.?$' || true; } | sort -u) +[[ -n "$TOP_LEVEL" ]] || { echo "Error: backup is empty." >&2; exit 1; } + +echo "Restore $(basename "$ARCHIVE") into $MINECRAFT_SERVER_PATH" +echo "These items will be replaced:" +sed 's/^/ /' <<<"$TOP_LEVEL" +if ! $ASSUME_YES; then + read -r -p "Continue? [y/N] " reply + [[ "$reply" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; } +fi + +mkdir -p "$MINECRAFT_SERVER_PATH" +if $SAFETY_BACKUP && [[ -n "$(ls -A "$MINECRAFT_SERVER_PATH")" ]]; then + "$TOOLS_DIR/backup-server.sh" pre-restore +fi + +while IFS= read -r item; do + rm -rf "${MINECRAFT_SERVER_PATH:?}/$item" +done <<<"$TOP_LEVEL" + +tar -xjf "$ARCHIVE" -C "$MINECRAFT_SERVER_PATH" +echo "Restored $(basename "$ARCHIVE")." diff --git a/tools/setup-env.sh b/tools/setup-env.sh new file mode 100755 index 0000000..c0d4405 --- /dev/null +++ b/tools/setup-env.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Source (bash or zsh) to export the shared server config from ../.env +# (relative to this script): +# PAPER_VERSION, PAPER_BUILD, MINECRAFT_SERVER_PATH, MINECRAFT_SERVER_JAR, MINECRAFT_BACKUP_PATH + +_setup_env_self="${BASH_SOURCE[0]:-${(%):-%x}}" +_setup_env_root="$(cd "$(dirname "$_setup_env_self")/.." && pwd)" +_setup_env_file="${MINECRAFT_ENV_FILE:-$_setup_env_root/.env}" + +if [[ -f "$_setup_env_file" ]]; then + while IFS= read -r _line || [[ -n "$_line" ]]; do + _line="${_line%$'\r'}" + [[ "$_line" =~ ^[[:space:]]*(#|$) ]] && continue + _key="${_line%%=*}" + _value="${_line#*=}" + _key="${_key//[[:space:]]/}" + [[ "$_key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue + _value="${_value#\"}"; _value="${_value%\"}" + _value="${_value#\'}"; _value="${_value%\'}" + eval "_current=\"\${$_key:-}\"" + [[ -z "$_current" ]] && export "$_key=$_value" + done < "$_setup_env_file" +fi + +PAPER_VERSION="${PAPER_VERSION:-}" +PAPER_BUILD="${PAPER_BUILD:-}" +MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH:-MCServer}" +MINECRAFT_SERVER_PATH="${MINECRAFT_SERVER_PATH/#\~/$HOME}" +[[ "$MINECRAFT_SERVER_PATH" == /* ]] || MINECRAFT_SERVER_PATH="$_setup_env_root/$MINECRAFT_SERVER_PATH" +MINECRAFT_BACKUP_PATH="${MINECRAFT_BACKUP_PATH:-backups}" +MINECRAFT_BACKUP_PATH="${MINECRAFT_BACKUP_PATH/#\~/$HOME}" +[[ "$MINECRAFT_BACKUP_PATH" == /* ]] || MINECRAFT_BACKUP_PATH="$_setup_env_root/$MINECRAFT_BACKUP_PATH" + +if [[ -z "${MINECRAFT_SERVER_JAR:-}" && -n "$PAPER_VERSION" && -n "$PAPER_BUILD" ]]; then + MINECRAFT_SERVER_JAR="paper-${PAPER_VERSION}-${PAPER_BUILD}.jar" +fi + +export PAPER_VERSION PAPER_BUILD MINECRAFT_SERVER_PATH MINECRAFT_SERVER_JAR MINECRAFT_BACKUP_PATH +unset _setup_env_self _setup_env_root _setup_env_file _line _key _value _current diff --git a/tools/start-server.sh b/tools/start-server.sh new file mode 100755 index 0000000..d4ddce5 --- /dev/null +++ b/tools/start-server.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Downloads (if missing) and runs a Paper server via the Fill v3 API. +# Server settings come from ../.env relative to this script (see setup-env.sh). +# +# Env vars: +# JAVA_OPTS Extra JVM args (default: -Xms2G -Xmx2G) + +set -euo pipefail + +TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=setup-env.sh +source "$TOOLS_DIR/setup-env.sh" + +[[ -n "$PAPER_VERSION" ]] || { echo "Error: PAPER_VERSION is not set (see .env)." >&2; exit 1; } + +PAPER_PROJECT="paper" +JAVA_OPTS="${JAVA_OPTS:--Xms2G -Xmx2G}" +FILL_API="https://fill.papermc.io/v3/projects/${PAPER_PROJECT}/versions/${PAPER_VERSION}" +# Fill requires a non-generic User-Agent with contact info. +USER_AGENT="${PAPER_USER_AGENT:-majinnaibu-plugin-dev/1.0 (https://github.com/majinnaibu)}" + +for cmd in curl jq java shasum; do + command -v "$cmd" >/dev/null || { echo "Error: '$cmd' is required." >&2; exit 1; } +done + +api_get() { + curl -fsSL -H "User-Agent: $USER_AGENT" "$1" +} + +mkdir -p "$MINECRAFT_SERVER_PATH" +cd "$MINECRAFT_SERVER_PATH" + +JAR_BUILD="${MINECRAFT_SERVER_JAR:-}" +JAR_BUILD="${JAR_BUILD#paper-"$PAPER_VERSION"-}"; JAR_BUILD="${JAR_BUILD%.jar}" +if [[ -z "$PAPER_BUILD" && "$JAR_BUILD" =~ ^[0-9]+$ ]]; then + PAPER_BUILD="$JAR_BUILD" +fi + +if [[ -z "$PAPER_BUILD" ]]; then + echo "Resolving latest stable build for Paper $PAPER_VERSION..." + PAPER_BUILD=$(api_get "$FILL_API/builds" \ + | jq -r 'map(select(.channel == "STABLE")) | max_by(.id) | .id // empty') || true + if [[ -z "$PAPER_BUILD" ]]; then + # Offline or no stable build: fall back to the newest local jar for this version. + for f in paper-"$PAPER_VERSION"-*.jar; do + b="${f#paper-"$PAPER_VERSION"-}"; b="${b%.jar}" + [[ "$b" =~ ^[0-9]+$ ]] && (( b > ${PAPER_BUILD:-0} )) && PAPER_BUILD="$b" + done + [[ -n "$PAPER_BUILD" ]] || { echo "Error: no stable build found for $PAPER_VERSION." >&2; exit 1; } + echo "Could not resolve from API; using local build $PAPER_BUILD." + fi +fi + +EXPECTED_JAR="paper-${PAPER_VERSION}-${PAPER_BUILD}.jar" +if [[ -n "${MINECRAFT_SERVER_JAR:-}" && "$MINECRAFT_SERVER_JAR" != "$EXPECTED_JAR" ]]; then + echo "Error: MINECRAFT_SERVER_JAR=$MINECRAFT_SERVER_JAR does not match PAPER_VERSION/PAPER_BUILD ($EXPECTED_JAR)." >&2 + exit 1 +fi +MINECRAFT_SERVER_JAR="$EXPECTED_JAR" + +if [[ ! -f "$MINECRAFT_SERVER_JAR" ]]; then + echo "Downloading $MINECRAFT_SERVER_JAR..." + BUILD_JSON=$(api_get "$FILL_API/builds/$PAPER_BUILD") + DOWNLOAD_URL=$(jq -r '.downloads."server:default".url // empty' <<<"$BUILD_JSON") + EXPECTED_SHA=$(jq -r '.downloads."server:default".checksums.sha256 // empty' <<<"$BUILD_JSON") + [[ -n "$DOWNLOAD_URL" ]] || { echo "Error: no download for $PAPER_VERSION build $PAPER_BUILD." >&2; exit 1; } + + TMP_JAR="$MINECRAFT_SERVER_JAR.part" + trap 'rm -f "$TMP_JAR"' EXIT + curl -fL --progress-bar -H "User-Agent: $USER_AGENT" -o "$TMP_JAR" "$DOWNLOAD_URL" + + ACTUAL_SHA=$(shasum -a 256 "$TMP_JAR" | awk '{print $1}') + if [[ -n "$EXPECTED_SHA" && "$ACTUAL_SHA" != "$EXPECTED_SHA" ]]; then + echo "Error: checksum mismatch (expected $EXPECTED_SHA, got $ACTUAL_SHA)." >&2 + exit 1 + fi + mv "$TMP_JAR" "$MINECRAFT_SERVER_JAR" + trap - EXIT +fi + +export MINECRAFT_SERVER_PATH MINECRAFT_SERVER_JAR + +"$TOOLS_DIR/stop-server.sh" + +if ! grep -qs '^eula=true' eula.txt; then + echo "Note: accept the EULA by setting eula=true in $MINECRAFT_SERVER_PATH/eula.txt (server will exit until then)." +fi + +echo "Starting $MINECRAFT_SERVER_JAR in $MINECRAFT_SERVER_PATH..." +# shellcheck disable=SC2086 +exec java $JAVA_OPTS -jar "$MINECRAFT_SERVER_JAR" --nogui + diff --git a/tools/stop-server.sh b/tools/stop-server.sh new file mode 100755 index 0000000..fb23a5e --- /dev/null +++ b/tools/stop-server.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Stops any running Paper server (java process running a paper-*.jar). + +set -euo pipefail + +STOP_TIMEOUT="${STOP_TIMEOUT:-60}" + +SERVER_PIDS=$(pgrep -f "java.*paper-.*\.jar" || true) + +if [[ -z "$SERVER_PIDS" ]]; then + echo "Minecraft server is not running." + exit 0 +fi + +for pid in $SERVER_PIDS; do + echo "Stopping Minecraft server (PID: $pid)..." + # SIGTERM lets Paper run its shutdown hook and save worlds. + kill "$pid" +done + +for ((i = 0; i < STOP_TIMEOUT; i++)); do + remaining="" + for pid in $SERVER_PIDS; do + kill -0 "$pid" 2>/dev/null && remaining="$remaining $pid" + done + if [[ -z "$remaining" ]]; then + echo "Minecraft server stopped." + exit 0 + fi + sleep 1 +done + +echo "Server did not stop within ${STOP_TIMEOUT}s; forcing:$remaining" +kill -9 $remaining