Files
pt-minecraft-modpack/start.sh

351 lines
9.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_ROOT="${PT_INSTALL_ROOT:-$SCRIPT_DIR}"
SERVER_NAME="${PT_SERVER:-${1:-}}"
RUNTIME_BASE_DIR="${PT_RUNTIME_DIR:-$INSTALL_ROOT/.pt-panel-runtime}"
DOWNLOAD_DIR="$RUNTIME_BASE_DIR/downloads"
JAVA_BIN="${JAVA_BIN:-java}"
JAVA_ARGS="${PT_JAVA_ARGS:--Xms1G -Xmx2G}"
SERVER_JAR_OVERRIDE="${PT_SERVER_JAR:-}"
FORCE_UPDATE="${PT_FORCE_UPDATE:-0}"
DRY_RUN="${PT_DRY_RUN:-0}"
SKIP_HASH_CHECK="${PT_SKIP_HASH_CHECK:-0}"
AUTO_EULA="${PT_AUTO_EULA:-TRUE}"
PACK_DIR=""
MODS_DIR=""
STAMP_FILE=""
MOD_STATE_FILE=""
log() {
printf '[pt-panel] %s\n' "$*"
}
fail() {
printf '[pt-panel] ERROR: %s\n' "$*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "缺少命令: $1"
}
fetch() {
local url="$1"
local dest="$2"
if command -v curl >/dev/null 2>&1; then
curl -fL --retry 3 --retry-delay 2 -o "$dest" "$url"
elif command -v wget >/dev/null 2>&1; then
wget -O "$dest" "$url"
else
fail "缺少 curl 或 wget无法下载文件"
fi
}
fetch_to_stdout() {
local url="$1"
if command -v curl >/dev/null 2>&1; then
curl -fL --retry 3 --retry-delay 2 "$url"
elif command -v wget >/dev/null 2>&1; then
wget -O - "$url"
else
fail "缺少 curl 或 wget无法下载文件"
fi
}
trim_quotes() {
local value="$1"
value="${value#\"}"
value="${value%\"}"
printf '%s' "$value"
}
toml_value() {
local key="$1"
local file="$2"
local line
line="$(grep -E "^${key}[[:space:]]*=" "$file" | head -n 1 || true)"
[ -n "$line" ] || return 1
line="${line#*=}"
line="${line#${line%%[![:space:]]*}}"
trim_quotes "$line"
}
hash_cmd_for() {
case "$1" in
sha512) echo sha512sum ;;
sha256) echo sha256sum ;;
sha1) echo sha1sum ;;
md5) echo md5sum ;;
*) return 1 ;;
esac
}
verify_hash() {
local file="$1"
local expected="$2"
local format="$3"
local cmd actual
[ "$SKIP_HASH_CHECK" = "1" ] && return 0
cmd="$(hash_cmd_for "$format" || true)"
[ -n "$cmd" ] || return 0
command -v "$cmd" >/dev/null 2>&1 || return 0
actual="$($cmd "$file" | awk '{print $1}')"
[ "$actual" = "$expected" ]
}
list_available_servers() {
find "$SCRIPT_DIR" -mindepth 1 -maxdepth 1 -type d -name 'server-*' -exec basename {} \; | sort
}
select_server() {
local available count first
if [ -n "$SERVER_NAME" ]; then
PACK_DIR="$SCRIPT_DIR/$SERVER_NAME"
[ -f "$PACK_DIR/pack.toml" ] || fail "指定的服务端不存在: $SERVER_NAME"
return 0
fi
available="$(list_available_servers || true)"
count="$(printf '%s\n' "$available" | sed '/^$/d' | wc -l | awk '{print $1}')"
if [ "$count" = "1" ]; then
first="$(printf '%s\n' "$available" | sed -n '1p')"
SERVER_NAME="$first"
PACK_DIR="$SCRIPT_DIR/$SERVER_NAME"
return 0
fi
if [ "$count" = "0" ]; then
fail "仓库中没有找到任何 server-* 服务端目录"
fi
printf '[pt-panel] 可选服务端:\n%s\n' "$(printf '%s\n' "$available" | sed 's/^/ - /')" >&2
fail "检测到多个服务端,请通过 PT_SERVER=<目录名> 或第一个参数指定例如PT_SERVER=server-01-random-block bash start.sh"
}
ensure_dirs() {
mkdir -p "$INSTALL_ROOT" "$RUNTIME_BASE_DIR" "$DOWNLOAD_DIR"
MODS_DIR="${PT_MODS_DIR:-$INSTALL_ROOT/mods}"
mkdir -p "$MODS_DIR"
}
prepare_server_paths() {
local safe_server_name
safe_server_name="$(printf '%s' "$SERVER_NAME" | tr '/' '_')"
STAMP_FILE="$RUNTIME_BASE_DIR/fabric-install-${safe_server_name}.stamp"
MOD_STATE_FILE="$RUNTIME_BASE_DIR/mods-${safe_server_name}.txt"
}
load_pack_versions() {
[ -f "$PACK_DIR/pack.toml" ] || fail "找不到 $PACK_DIR/pack.toml请确认服务端 pack 存在"
MINECRAFT_VERSION="$(toml_value 'minecraft' "$PACK_DIR/pack.toml")"
FABRIC_LOADER_VERSION="$(toml_value 'fabric' "$PACK_DIR/pack.toml")"
[ -n "$MINECRAFT_VERSION" ] || fail "无法从 $PACK_DIR/pack.toml 读取 minecraft 版本"
[ -n "$FABRIC_LOADER_VERSION" ] || fail "无法从 $PACK_DIR/pack.toml 读取 fabric 版本"
}
load_latest_fabric_installer_version() {
local meta version
meta="$(fetch_to_stdout 'https://meta.fabricmc.net/v2/versions/installer')"
version="$(printf '%s\n' "$meta" | grep '"version"' | head -n 1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')"
[ -n "$version" ] || fail "无法获取最新 Fabric installer 版本"
printf '%s' "$version"
}
install_fabric_server() {
local installer_version installer_jar wanted_stamp current_stamp
installer_version="$(load_latest_fabric_installer_version)"
installer_jar="$DOWNLOAD_DIR/fabric-installer-${installer_version}.jar"
wanted_stamp="server=$SERVER_NAME mc=$MINECRAFT_VERSION loader=$FABRIC_LOADER_VERSION installer=$installer_version"
current_stamp="$(cat "$STAMP_FILE" 2>/dev/null || true)"
if [ "$FORCE_UPDATE" != "1" ] && [ -f "$INSTALL_ROOT/fabric-server-launch.jar" ] && [ "$wanted_stamp" = "$current_stamp" ]; then
log "Fabric 服务端已就绪,跳过重复安装"
return 0
fi
log "准备安装 Fabric 服务端:$SERVER_NAME (Minecraft $MINECRAFT_VERSION / Loader $FABRIC_LOADER_VERSION)"
fetch "https://maven.fabricmc.net/net/fabricmc/fabric-installer/${installer_version}/fabric-installer-${installer_version}.jar" "$installer_jar"
if [ "$DRY_RUN" = "1" ]; then
log "DRY RUN: 跳过执行 Fabric installer"
return 0
fi
(
cd "$INSTALL_ROOT"
"$JAVA_BIN" -jar "$installer_jar" server -mcversion "$MINECRAFT_VERSION" -loader "$FABRIC_LOADER_VERSION" -downloadMinecraft
)
printf '%s\n' "$wanted_stamp" > "$STAMP_FILE"
}
cleanup_stale_mods() {
local current_file stale_name
[ -f "$MOD_STATE_FILE" ] || return 0
while IFS= read -r stale_name; do
[ -n "$stale_name" ] || continue
current_file="$MODS_DIR/$stale_name"
if [ ! -f "$RUNTIME_BASE_DIR/current-mods.txt" ] || ! grep -Fxq "$stale_name" "$RUNTIME_BASE_DIR/current-mods.txt"; then
if [ -f "$current_file" ]; then
log "移除旧模组: $stale_name"
[ "$DRY_RUN" = "1" ] || rm -f "$current_file"
fi
fi
done < "$MOD_STATE_FILE"
}
sync_mods() {
local meta file_name url hash_format hash dest tmp
local tmp_state_file="$RUNTIME_BASE_DIR/current-mods.txt"
: > "$tmp_state_file"
shopt -s nullglob
local files=("$PACK_DIR"/mods/*.pw.toml)
shopt -u nullglob
[ "${#files[@]}" -gt 0 ] || fail "$PACK_DIR/mods 下没有找到任何 .pw.toml 模组定义"
for meta in "${files[@]}"; do
file_name="$(toml_value 'filename' "$meta")"
url="$(toml_value 'url' "$meta")"
hash_format="$(toml_value 'hash-format' "$meta" || true)"
hash="$(toml_value 'hash' "$meta" || true)"
[ -n "$file_name" ] || fail "无法读取 $meta 中的 filename"
[ -n "$url" ] || fail "无法读取 $meta 中的 download.url"
printf '%s\n' "$file_name" >> "$tmp_state_file"
dest="$MODS_DIR/$file_name"
if [ "$FORCE_UPDATE" != "1" ] && [ -f "$dest" ]; then
if [ -n "$hash_format" ] && [ -n "$hash" ]; then
if verify_hash "$dest" "$hash" "$hash_format"; then
log "模组已存在且校验通过: $file_name"
continue
fi
log "模组校验失败,重新下载: $file_name"
else
log "模组已存在,跳过下载: $file_name"
continue
fi
else
log "下载模组: $file_name"
fi
if [ "$DRY_RUN" = "1" ]; then
log "DRY RUN: 跳过下载 $url"
continue
fi
tmp="$dest.part"
fetch "$url" "$tmp"
if [ -n "$hash_format" ] && [ -n "$hash" ] && ! verify_hash "$tmp" "$hash" "$hash_format"; then
rm -f "$tmp"
fail "模组校验失败: $file_name"
fi
mv "$tmp" "$dest"
done
cleanup_stale_mods
[ "$DRY_RUN" = "1" ] || mv "$tmp_state_file" "$MOD_STATE_FILE"
}
write_eula() {
if [ "$AUTO_EULA" = "TRUE" ] || [ "$AUTO_EULA" = "true" ] || [ "$AUTO_EULA" = "1" ]; then
printf 'eula=true\n' > "$INSTALL_ROOT/eula.txt"
log "已写入 eula.txt"
else
log "已跳过自动写入 EULAPT_AUTO_EULA=$AUTO_EULA"
fi
}
find_server_jar() {
if [ -n "$SERVER_JAR_OVERRIDE" ]; then
printf '%s' "$SERVER_JAR_OVERRIDE"
return 0
fi
if [ -f "$INSTALL_ROOT/fabric-server-launch.jar" ]; then
printf '%s' "$INSTALL_ROOT/fabric-server-launch.jar"
return 0
fi
local candidate
candidate="$(find "$INSTALL_ROOT" -maxdepth 1 -type f \( -name 'fabric-server-launch.jar' -o -name 'fabric-server-*.jar' -o -name 'server.jar' \) | sort | head -n 1 || true)"
[ -n "$candidate" ] || return 1
printf '%s' "$candidate"
}
start_server() {
local server_jar
server_jar="$(find_server_jar || true)"
if [ "$DRY_RUN" = "1" ]; then
if [ -n "$server_jar" ]; then
log "使用服务端文件: $server_jar"
else
log "DRY RUN: 当前尚未生成服务端 jar正式运行时会先执行 Fabric installer"
fi
log "安装目录: $INSTALL_ROOT"
log "启动参数: $JAVA_ARGS"
log "DRY RUN: 跳过实际启动"
return 0
fi
[ -n "$server_jar" ] || fail "找不到可启动的服务端 jar请检查 Fabric 安装是否成功"
log "使用服务端: $SERVER_NAME"
log "使用服务端文件: $server_jar"
log "安装目录: $INSTALL_ROOT"
log "启动参数: $JAVA_ARGS"
cd "$INSTALL_ROOT"
exec "$JAVA_BIN" $JAVA_ARGS -jar "$server_jar" nogui
}
main() {
need_cmd "$JAVA_BIN"
need_cmd grep
need_cmd sed
need_cmd awk
need_cmd find
select_server
ensure_dirs
prepare_server_paths
load_pack_versions
log "脚本目录: $SCRIPT_DIR"
log "安装目录: $INSTALL_ROOT"
log "选择服务端: $SERVER_NAME"
log "Pack 目录: $PACK_DIR"
log "Minecraft 版本: $MINECRAFT_VERSION"
log "Fabric Loader 版本: $FABRIC_LOADER_VERSION"
install_fabric_server
sync_mods
write_eula
start_server
}
main "$@"