Add one-click dedicated server scripts for Hytale F2P

- server/start.sh: Linux/macOS starter with auto-download, auto-update,
  F2P launcher detection (game files + bundled JRE), and token fetch
- server/start.bat: Windows equivalent using PowerShell for JSON/UUID
- server/README.md: Beginner-friendly guide with playit.gg networking
- Remove obsolete server_scripts_2.zip

Scripts auto-detect F2P launcher install (including custom installPath
from config.json), copy game files locally if available, download
missing files (HytaleServer.jar, Assets.zip, dualauth-agent.jar),
and check for updates on every launch via ETag/GitHub releases API.
This commit is contained in:
sanasol
2026-02-23 00:55:38 +01:00
parent fb90277be9
commit 552ec42d6c
3 changed files with 1071 additions and 0 deletions

516
server/start.sh Executable file
View File

@@ -0,0 +1,516 @@
#!/bin/bash
# ============================================================
# Hytale F2P Dedicated Server - One-Click Starter
# ============================================================
# Just run: ./start.sh
#
# The script will:
# 1. Look for game files in your F2P launcher install
# 2. Auto-download anything missing
# 3. Auto-update server, assets, and agent on each launch
# 4. Fetch auth tokens and start the server
# ============================================================
set -e
# Configuration (edit these or set as environment variables)
HYTALE_AUTH_DOMAIN="${HYTALE_AUTH_DOMAIN:-auth.sanasol.ws}"
AUTH_SERVER="${AUTH_SERVER:-https://$HYTALE_AUTH_DOMAIN}"
SERVER_NAME="${SERVER_NAME:-My Hytale Server}"
BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0:5520}"
AUTH_MODE="${AUTH_MODE:-authenticated}"
# Download URLs
DOWNLOAD_BASE="${DOWNLOAD_BASE:-https://download.sanasol.ws/download}"
AGENT_URL="https://github.com/sanasol/hytale-auth-server/releases/latest/download/dualauth-agent.jar"
AGENT_VERSION_API="https://api.github.com/repos/sanasol/hytale-auth-server/releases/latest"
# File names (in current directory)
AGENT_JAR="dualauth-agent.jar"
SERVER_JAR="HytaleServer.jar"
ASSETS_FILE="Assets.zip"
ASSETS_PATH="${ASSETS_PATH:-./Assets.zip}"
VERSION_DIR=".versions"
echo "============================================================"
echo " Hytale F2P Dedicated Server"
echo "============================================================"
echo ""
# --- Prerequisite Checks ---
if ! command -v curl &>/dev/null; then
echo "[ERROR] curl is required but not found"
echo " Install: sudo apt install curl"
exit 1
fi
mkdir -p "$VERSION_DIR"
# --- Find Local F2P Launcher Install ---
get_f2p_default_dir() {
case "$(uname -s)" in
Darwin) echo "$HOME/Library/Application Support/HytaleF2P" ;;
Linux) echo "$HOME/.hytalef2p" ;;
*) return 1 ;;
esac
}
# Read a JSON string field from config.json using available tools
read_config_field() {
local config_file="$1" field="$2"
if [ ! -f "$config_file" ]; then return 1; fi
if command -v python3 &>/dev/null; then
python3 -c "
import json
try:
c = json.load(open('$config_file'))
v = c.get('$field', '').strip()
if v: print(v)
except: pass
" 2>/dev/null
elif command -v jq &>/dev/null; then
jq -r ".$field // empty" "$config_file" 2>/dev/null
else
grep -o "\"$field\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$config_file" 2>/dev/null | cut -d'"' -f4
fi
}
find_f2p_install() {
local default_app_dir
default_app_dir=$(get_f2p_default_dir) || return 1
local search_dirs=()
# Check config.json for custom installPath
local config_file="$default_app_dir/config.json"
local custom_path
custom_path=$(read_config_field "$config_file" "installPath")
if [ -n "$custom_path" ]; then
local custom_f2p="$custom_path/HytaleF2P"
if [ -d "$custom_f2p" ]; then
search_dirs+=("$custom_f2p")
fi
fi
# Always also check default location
search_dirs+=("$default_app_dir")
for base in "${search_dirs[@]}"; do
for branch in "release" "pre-release"; do
local game_dir="$base/$branch/package/game/latest"
if [ -d "$game_dir" ]; then
echo "$game_dir"
return 0
fi
done
done
return 1
}
# Find bundled JRE from F2P launcher install
find_f2p_java() {
local default_app_dir
default_app_dir=$(get_f2p_default_dir) || return 1
local search_dirs=()
# Check config.json for custom javaPath first
local config_file="$default_app_dir/config.json"
local custom_java
custom_java=$(read_config_field "$config_file" "javaPath")
if [ -n "$custom_java" ] && [ -x "$custom_java" ]; then
echo "$custom_java"
return 0
fi
# Check custom installPath
local custom_path
custom_path=$(read_config_field "$config_file" "installPath")
if [ -n "$custom_path" ]; then
local custom_f2p="$custom_path/HytaleF2P"
[ -d "$custom_f2p" ] && search_dirs+=("$custom_f2p")
fi
search_dirs+=("$default_app_dir")
for base in "${search_dirs[@]}"; do
for branch in "release" "pre-release"; do
local jre_dir="$base/$branch/package/jre/latest"
# Standard path
if [ -x "$jre_dir/bin/java" ]; then
echo "$jre_dir/bin/java"
return 0
fi
# macOS bundle path
if [ -x "$jre_dir/Contents/Home/bin/java" ]; then
echo "$jre_dir/Contents/Home/bin/java"
return 0
fi
done
done
return 1
}
copy_from_f2p() {
local file="$1" local_path="$2" f2p_path="$3"
if [ -f "$local_path" ]; then
return 1 # Already exists locally
fi
if [ -f "$f2p_path" ]; then
echo "[INFO] Found $file in F2P launcher install"
echo "[INFO] Copying from: $f2p_path"
cp "$f2p_path" "$local_path"
echo "[INFO] Copied successfully"
return 0
fi
return 1
}
# --- Detect Java ---
JAVA_CMD="java"
# Try F2P bundled JRE first
F2P_JAVA=$(find_f2p_java 2>/dev/null) || true
if [ -n "$F2P_JAVA" ]; then
echo "[INFO] Found Java in F2P launcher: $F2P_JAVA"
JAVA_CMD="$F2P_JAVA"
elif ! command -v java &>/dev/null; then
echo "[ERROR] Java is not installed and no F2P launcher JRE found"
echo ""
echo " Options:"
echo " 1. Install the F2P launcher first (it includes Java)"
echo " 2. Install Java 25+ manually:"
echo " Windows: https://download.oracle.com/java/25/latest/jdk-25_windows-x64_bin.exe"
echo " macOS: brew install openjdk"
echo " Ubuntu/Debian: sudo apt install openjdk-25-jre"
echo ""
exit 1
fi
# Check Java version
JAVA_FULL=$("$JAVA_CMD" -version 2>&1 | head -1)
JAVA_VER=$(echo "$JAVA_FULL" | grep -oP '(?<=")\d+' 2>/dev/null || echo "$JAVA_FULL" | sed 's/.*"\([0-9]*\).*/\1/')
echo "[INFO] Java: $JAVA_FULL"
if [ -n "$JAVA_VER" ] && [ "$JAVA_VER" -lt 25 ] 2>/dev/null; then
echo "[ERROR] Java $JAVA_VER detected. Java 25+ is REQUIRED."
echo " The DualAuth agent requires Java 25 (class file version 69)."
echo ""
if [ -n "$F2P_JAVA" ]; then
echo " Your F2P launcher JRE is outdated. Update the launcher to get a newer Java."
else
echo " Install Java 25+:"
echo " Windows: https://download.oracle.com/java/25/latest/jdk-25_windows-x64_bin.exe"
echo " macOS: brew install openjdk"
echo " Ubuntu/Debian: sudo apt install openjdk-25-jre"
fi
echo ""
exit 1
fi
# --- Find F2P Game Files ---
F2P_DIR=""
if F2P_DIR=$(find_f2p_install 2>/dev/null); then
echo "[INFO] Found F2P launcher game files: $F2P_DIR"
# Try to copy HytaleServer.jar from F2P install
copy_from_f2p "HytaleServer.jar" "$SERVER_JAR" "$F2P_DIR/Server/HytaleServer.jar" || true
# Try to copy Assets.zip from F2P install
copy_from_f2p "Assets.zip" "$ASSETS_PATH" "$F2P_DIR/Assets.zip" || true
echo ""
else
echo "[INFO] No F2P launcher install found, will download files"
echo ""
fi
# --- Download / Update Functions ---
get_remote_version() {
local url="$1"
local headers
headers=$(curl -sI -L "$url" --connect-timeout 10 --max-time 15 2>/dev/null | tr -d '\r')
local etag
etag=$(echo "$headers" | grep -i "^etag:" | tail -1 | sed 's/^[^:]*: *//' | tr -d '"')
if [ -n "$etag" ]; then printf '%s' "$etag"; return 0; fi
local lastmod
lastmod=$(echo "$headers" | grep -i "^last-modified:" | tail -1 | sed 's/^[^:]*: *//')
if [ -n "$lastmod" ]; then printf '%s' "$lastmod"; return 0; fi
local length
length=$(echo "$headers" | grep -i "^content-length:" | tail -1 | sed 's/^[^:]*: *//')
if [ -n "$length" ]; then printf 'size:%s' "$length"; return 0; fi
return 1
}
needs_update() {
local url="$1" dest="$2" name="$3"
local version_file="${VERSION_DIR}/${name}.version"
if [ ! -f "$dest" ]; then
echo "[INFO] $name not found, will download"
return 0
fi
echo "[INFO] Checking for $name updates..."
local remote_version
remote_version=$(get_remote_version "$url" 2>/dev/null) || true
if [ -z "$remote_version" ]; then
echo "[INFO] Could not check for updates, using existing $name"
return 1
fi
local local_version=""
[ -f "$version_file" ] && local_version=$(cat "$version_file" 2>/dev/null)
if [ "$remote_version" = "$local_version" ]; then
echo "[INFO] $name is up to date"
return 1
fi
if [ -n "$local_version" ]; then
echo "[INFO] $name update available"
fi
return 0
}
save_version() {
local url="$1" name="$2"
local version_file="${VERSION_DIR}/${name}.version"
local ver
ver=$(get_remote_version "$url" 2>/dev/null) || true
[ -n "$ver" ] && printf '%s\n' "$ver" > "$version_file"
}
download_file() {
local url="$1" dest="$2" name="$3" expected_mb="${4:-0}"
local tmp="${dest}.tmp"
echo "[INFO] Downloading $name..."
[ "$expected_mb" -gt 0 ] 2>/dev/null && echo "[INFO] Expected size: ~${expected_mb} MB"
for attempt in 1 2 3; do
rm -f "$tmp" 2>/dev/null || true
if [ "$expected_mb" -gt 50 ] 2>/dev/null; then
curl -fL --progress-bar -o "$tmp" "$url" --connect-timeout 15 --max-time 3600 2>&1
else
curl -fL -# -o "$tmp" "$url" --connect-timeout 15 --max-time 300 2>&1
fi
if [ $? -eq 0 ] && [ -f "$tmp" ]; then
local size
size=$(stat -c%s "$tmp" 2>/dev/null || stat -f%z "$tmp" 2>/dev/null || echo 0)
if [ "$size" -gt 1000 ]; then
mv -f "$tmp" "$dest"
local mb=$((size / 1024 / 1024))
echo "[INFO] $name downloaded (${mb} MB)"
return 0
fi
echo "[WARN] $name download too small (${size} bytes), retrying..."
fi
echo "[WARN] Download attempt $attempt failed, retrying..."
rm -f "$tmp" 2>/dev/null || true
sleep 2
done
echo "[ERROR] Failed to download $name after 3 attempts"
rm -f "$tmp" 2>/dev/null || true
return 1
}
# --- Download / Update Server Files ---
# HytaleServer.jar
JAR_URL="${DOWNLOAD_BASE}/HytaleServer.jar"
if needs_update "$JAR_URL" "$SERVER_JAR" "HytaleServer.jar"; then
if download_file "$JAR_URL" "$SERVER_JAR" "HytaleServer.jar" "150"; then
save_version "$JAR_URL" "HytaleServer.jar"
else
if [ ! -f "$SERVER_JAR" ]; then
echo "[ERROR] HytaleServer.jar is required. Check your internet connection."
exit 1
fi
echo "[WARN] Update failed, using existing HytaleServer.jar"
fi
fi
# Assets.zip
ASSETS_URL="${DOWNLOAD_BASE}/Assets.zip"
if needs_update "$ASSETS_URL" "$ASSETS_PATH" "Assets.zip"; then
echo "[INFO] Assets.zip is large (~3.3 GB), this may take a while..."
if download_file "$ASSETS_URL" "$ASSETS_PATH" "Assets.zip" "3300"; then
save_version "$ASSETS_URL" "Assets.zip"
else
if [ ! -f "$ASSETS_PATH" ]; then
echo "[ERROR] Assets.zip is required. Check your internet connection."
exit 1
fi
echo "[WARN] Update failed, using existing Assets.zip"
fi
fi
# DualAuth Agent (uses GitHub releases API for version tracking)
check_agent_update() {
if [ -f "$AGENT_JAR" ]; then
local agent_size
agent_size=$(stat -c%s "$AGENT_JAR" 2>/dev/null || stat -f%z "$AGENT_JAR" 2>/dev/null || echo 0)
if [ "$agent_size" -lt 10000 ]; then
echo "[WARN] Agent JAR seems corrupt (${agent_size} bytes), re-downloading..."
rm -f "$AGENT_JAR"
return 0
fi
local version_file="${VERSION_DIR}/${AGENT_JAR}.version"
local local_version=""
[ -f "$version_file" ] && local_version=$(cat "$version_file" 2>/dev/null)
echo "[INFO] Checking for DualAuth Agent updates..."
local remote_version
remote_version=$(curl -sf "$AGENT_VERSION_API" --connect-timeout 5 --max-time 10 2>/dev/null | grep -o '"tag_name":"[^"]*"' | cut -d'"' -f4)
if [ -n "$remote_version" ]; then
if [ "$local_version" = "$remote_version" ]; then
echo "[INFO] DualAuth Agent up to date ($local_version)"
return 1
else
echo "[INFO] Agent update: ${local_version:-unknown} -> $remote_version"
return 0
fi
else
echo "[INFO] Could not check agent updates, using existing"
return 1
fi
else
return 0
fi
}
AGENT_REMOTE_VERSION=""
if check_agent_update; then
echo "[INFO] Downloading DualAuth Agent..."
if curl -fL -# -o "${AGENT_JAR}.tmp" "$AGENT_URL" --connect-timeout 15 --max-time 120 2>&1 && [ -f "${AGENT_JAR}.tmp" ]; then
dl_size=$(stat -c%s "${AGENT_JAR}.tmp" 2>/dev/null || stat -f%z "${AGENT_JAR}.tmp" 2>/dev/null || echo 0)
if [ "$dl_size" -gt 10000 ]; then
mv -f "${AGENT_JAR}.tmp" "$AGENT_JAR"
AGENT_REMOTE_VERSION=$(curl -sf "$AGENT_VERSION_API" --connect-timeout 5 --max-time 10 2>/dev/null | grep -o '"tag_name":"[^"]*"' | cut -d'"' -f4)
[ -n "$AGENT_REMOTE_VERSION" ] && printf '%s\n' "$AGENT_REMOTE_VERSION" > "${VERSION_DIR}/${AGENT_JAR}.version"
echo "[INFO] DualAuth Agent ready (${AGENT_REMOTE_VERSION:-latest})"
else
echo "[WARN] Downloaded agent too small, discarding"
rm -f "${AGENT_JAR}.tmp"
fi
else
rm -f "${AGENT_JAR}.tmp" 2>/dev/null
if [ -f "$AGENT_JAR" ]; then
echo "[WARN] Agent update failed, using existing"
else
echo "[ERROR] Failed to download DualAuth Agent"
echo "[ERROR] Download manually: $AGENT_URL"
exit 1
fi
fi
fi
# --- Final Checks ---
for required in "$SERVER_JAR" "$ASSETS_PATH" "$AGENT_JAR"; do
if [ ! -f "$required" ]; then
echo "[ERROR] Required file missing: $required"
exit 1
fi
done
# --- Generate Server ID ---
SERVER_ID_FILE=".server-id"
if [ -f "$SERVER_ID_FILE" ]; then
SERVER_ID=$(cat "$SERVER_ID_FILE")
echo "[INFO] Server ID: $SERVER_ID"
else
SERVER_ID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen 2>/dev/null || python3 -c "import uuid; print(uuid.uuid4())" 2>/dev/null)
if [ -z "$SERVER_ID" ]; then
echo "[ERROR] Could not generate UUID. Install uuidgen or python3."
exit 1
fi
printf '%s' "$SERVER_ID" > "$SERVER_ID_FILE"
echo "[INFO] Generated server ID: $SERVER_ID"
fi
# --- Fetch Tokens ---
echo ""
echo "[INFO] Fetching server tokens from $AUTH_SERVER..."
TEMP_RESPONSE=$(mktemp)
curl -s -X POST "$AUTH_SERVER/server/auto-auth" \
-H "Content-Type: application/json" \
-d "{\"server_id\": \"$SERVER_ID\", \"server_name\": \"$SERVER_NAME\"}" \
--connect-timeout 10 \
--max-time 30 \
-o "$TEMP_RESPONSE"
if [ $? -ne 0 ]; then
echo "[ERROR] Failed to connect to auth server at $AUTH_SERVER"
rm -f "$TEMP_RESPONSE"
exit 1
fi
if ! grep -q "sessionToken" "$TEMP_RESPONSE" 2>/dev/null; then
echo "[ERROR] Invalid response from auth server:"
cat "$TEMP_RESPONSE"
rm -f "$TEMP_RESPONSE"
exit 1
fi
# Extract tokens (python3 > jq > grep fallback)
if command -v python3 &>/dev/null; then
SESSION_TOKEN=$(python3 -c "import json,sys; print(json.load(open('$TEMP_RESPONSE'))['sessionToken'])")
IDENTITY_TOKEN=$(python3 -c "import json,sys; print(json.load(open('$TEMP_RESPONSE'))['identityToken'])")
elif command -v jq &>/dev/null; then
SESSION_TOKEN=$(jq -r '.sessionToken' "$TEMP_RESPONSE")
IDENTITY_TOKEN=$(jq -r '.identityToken' "$TEMP_RESPONSE")
else
SESSION_TOKEN=$(grep -o '"sessionToken":"[^"]*"' "$TEMP_RESPONSE" | cut -d'"' -f4)
IDENTITY_TOKEN=$(grep -o '"identityToken":"[^"]*"' "$TEMP_RESPONSE" | cut -d'"' -f4)
fi
rm -f "$TEMP_RESPONSE"
if [ -z "$SESSION_TOKEN" ] || [ -z "$IDENTITY_TOKEN" ]; then
echo "[ERROR] Could not extract tokens from auth server response"
exit 1
fi
echo "[INFO] Tokens received successfully"
# --- Start Server ---
JAVA_ARGS=""
[ -n "${JVM_XMS:-}" ] && JAVA_ARGS="$JAVA_ARGS -Xms$JVM_XMS"
[ -n "${JVM_XMX:-}" ] && JAVA_ARGS="$JAVA_ARGS -Xmx$JVM_XMX"
echo ""
echo "============================================================"
echo " Starting Hytale Server"
echo " Name: $SERVER_NAME"
echo " Bind: $BIND_ADDRESS"
echo " Agent: $AGENT_JAR"
echo "============================================================"
echo ""
exec "$JAVA_CMD" $JAVA_ARGS -javaagent:"$AGENT_JAR" -jar "$SERVER_JAR" \
--assets "$ASSETS_PATH" \
--bind "$BIND_ADDRESS" \
--auth-mode "$AUTH_MODE" \
--disable-sentry \
--session-token "$SESSION_TOKEN" \
--identity-token "$IDENTITY_TOKEN" \
"$@"