mirror of
https://github.com/NATroutter/egg-hytale.git
synced 2026-03-01 11:21:13 +03:00
Improved the default Jvm args Improved messages in entry Removed the zip restriction from Asset packs (Do not use directories for assets yet) Added script for local dev building
544 lines
19 KiB
Bash
544 lines
19 KiB
Bash
#!/bin/bash
|
||
|
||
################################################################################
|
||
# egg-hytale Entry Script
|
||
#
|
||
# This script handles:
|
||
# - Hytale server download and updates
|
||
# - Authentication with Hytale services
|
||
# - Session token generation
|
||
# - Launching the main server startup script
|
||
#
|
||
# DO NOT EDIT THIS FILE - it is managed by the Docker image.
|
||
# To customize server settings, use the egg configuration variables in your
|
||
# Pelican or Pterodactyl panel instead.
|
||
################################################################################
|
||
|
||
DOWNLOAD_URL="https://downloader.hytale.com/hytale-downloader.zip"
|
||
DOWNLOAD_FILE="hytale-downloader.zip"
|
||
DOWNLOAD_CRED_FILE=".hytale-downloader-credentials.json"
|
||
AUTH_CACHE_FILE=".hytale-auth-tokens.json"
|
||
VERSION_FILE="version.txt"
|
||
MODS_FOLDER="mods"
|
||
|
||
# Detect architecture and set appropriate downloader binary
|
||
ARCH=$(uname -m)
|
||
echo "Platform: $ARCH"
|
||
|
||
case "$ARCH" in
|
||
x86_64)
|
||
DOWNLOADER="./hytale-downloader-linux-amd64"
|
||
;;
|
||
aarch64|arm64)
|
||
DOWNLOADER="./hytale-downloader-linux-arm64"
|
||
;;
|
||
*)
|
||
echo "Error: Unsupported architecture: $ARCH"
|
||
echo "Supported architectures: x86_64 (amd64), aarch64/arm64"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
# Function to extract downloaded server files
|
||
extract_server_files() {
|
||
echo "Extracting server files..."
|
||
SERVER_ZIP="server.zip"
|
||
|
||
if [ -f "$SERVER_ZIP" ]; then
|
||
echo "✓ Found server archive: $SERVER_ZIP"
|
||
|
||
# Extract to current directory
|
||
unzip -o "$SERVER_ZIP"
|
||
|
||
if [ $? -ne 0 ]; then
|
||
echo "Error: Failed to extract $SERVER_ZIP"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✓ Extraction completed successfully."
|
||
|
||
# Move contents from Server folder to current directory
|
||
if [ -d "Server" ]; then
|
||
echo "Moving server files from Server directory..."
|
||
mv -f Server/* .
|
||
rm -rf ./Server
|
||
echo "✓ Server files moved to root directory."
|
||
fi
|
||
|
||
# Clean up the zip file
|
||
echo "Cleaning up archive file..."
|
||
rm "$SERVER_ZIP"
|
||
echo "✓ Archive removed."
|
||
else
|
||
echo "Error: Server archive not found at $SERVER_ZIP"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Function to check if cached tokens exist
|
||
check_cached_tokens() {
|
||
if [ -f "$AUTH_CACHE_FILE" ]; then
|
||
# Check if jq is available
|
||
if ! command -v jq &> /dev/null; then
|
||
echo "Warning: jq not found, cannot use cached tokens"
|
||
return 1
|
||
fi
|
||
|
||
# Validate JSON format
|
||
if ! jq empty "$AUTH_CACHE_FILE" 2>/dev/null; then
|
||
echo "Warning: Invalid cached token file, removing..."
|
||
rm "$AUTH_CACHE_FILE"
|
||
return 1
|
||
fi
|
||
|
||
# Check if required keys exist
|
||
REFRESH_TOKEN_EXISTS=$(jq -r 'has("refresh_token")' "$AUTH_CACHE_FILE")
|
||
PROFILE_UUID_EXISTS=$(jq -r 'has("profile_uuid")' "$AUTH_CACHE_FILE")
|
||
|
||
if [ "$REFRESH_TOKEN_EXISTS" != "true" ] || [ "$PROFILE_UUID_EXISTS" != "true" ]; then
|
||
echo "Warning: Cached token file missing required keys, removing..."
|
||
rm "$AUTH_CACHE_FILE"
|
||
return 1
|
||
fi
|
||
|
||
echo "✓ Found cached authentication tokens"
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# Function to load cached tokens (refresh_token + profile_uuid only)
|
||
load_cached_tokens() {
|
||
REFRESH_TOKEN=$(jq -r '.refresh_token' "$AUTH_CACHE_FILE")
|
||
PROFILE_UUID=$(jq -r '.profile_uuid' "$AUTH_CACHE_FILE")
|
||
|
||
# Validate required tokens are present
|
||
if [ -z "$REFRESH_TOKEN" ] || [ "$REFRESH_TOKEN" = "null" ] || \
|
||
[ -z "$PROFILE_UUID" ] || [ "$PROFILE_UUID" = "null" ]; then
|
||
echo "Error: Incomplete cached tokens, re-authenticating..."
|
||
rm "$AUTH_CACHE_FILE"
|
||
return 1
|
||
fi
|
||
|
||
echo "✓ Loaded cached refresh token + profile UUID"
|
||
return 0
|
||
}
|
||
|
||
# Function to refresh access token using cached refresh token
|
||
refresh_access_token() {
|
||
echo "Refreshing access token..."
|
||
|
||
TOKEN_RESPONSE=$(curl -s -X POST "https://oauth.accounts.hytale.com/oauth2/token" \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "client_id=hytale-server" \
|
||
-d "grant_type=refresh_token" \
|
||
-d "refresh_token=$REFRESH_TOKEN")
|
||
|
||
ERROR=$(echo "$TOKEN_RESPONSE" | jq -r '.error // empty')
|
||
if [ -n "$ERROR" ]; then
|
||
echo "Error: Failed to refresh access token: $ERROR"
|
||
return 1
|
||
fi
|
||
|
||
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
|
||
NEW_REFRESH_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.refresh_token // empty')
|
||
|
||
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
|
||
echo "Error: No access token in refresh response"
|
||
return 1
|
||
fi
|
||
|
||
# Update refresh token if a new one was provided
|
||
if [ -n "$NEW_REFRESH_TOKEN" ] && [ "$NEW_REFRESH_TOKEN" != "null" ]; then
|
||
REFRESH_TOKEN="$NEW_REFRESH_TOKEN"
|
||
fi
|
||
|
||
echo "✓ Access token refreshed"
|
||
return 0
|
||
}
|
||
|
||
# Function to create a new game session
|
||
create_game_session() {
|
||
echo "Creating game server session..."
|
||
|
||
SESSION_RESPONSE=$(curl -s -X POST "https://sessions.hytale.com/game-session/new" \
|
||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"uuid\": \"$PROFILE_UUID\"}")
|
||
|
||
# Validate JSON response
|
||
if ! echo "$SESSION_RESPONSE" | jq empty 2>/dev/null; then
|
||
echo "Error: Invalid JSON response from game session creation"
|
||
echo "Response: $SESSION_RESPONSE"
|
||
return 1
|
||
fi
|
||
|
||
# Extract session and identity tokens
|
||
SESSION_TOKEN=$(echo "$SESSION_RESPONSE" | jq -r '.sessionToken')
|
||
IDENTITY_TOKEN=$(echo "$SESSION_RESPONSE" | jq -r '.identityToken')
|
||
|
||
if [ -z "$SESSION_TOKEN" ] || [ "$SESSION_TOKEN" = "null" ]; then
|
||
echo "Error: Failed to create game server session"
|
||
echo "Response: $SESSION_RESPONSE"
|
||
return 1
|
||
fi
|
||
|
||
echo "✓ Game server session created successfully!"
|
||
return 0
|
||
}
|
||
|
||
# Function to save authentication tokens (refresh_token + profile_uuid only)
|
||
save_auth_tokens() {
|
||
|
||
# Create auth cache file (only in standard mode, not GSP mode)
|
||
if [ ! -f "$AUTH_CACHE_FILE" ]; then
|
||
echo "Creating auth cache file..."
|
||
touch $AUTH_CACHE_FILE
|
||
fi
|
||
|
||
cat > "$AUTH_CACHE_FILE" << EOF
|
||
{
|
||
"refresh_token": "$REFRESH_TOKEN",
|
||
"profile_uuid": "$PROFILE_UUID",
|
||
"timestamp": $(date +%s)
|
||
}
|
||
EOF
|
||
echo "✓ Refresh token cached for future use"
|
||
}
|
||
|
||
# Function to perform full authentication
|
||
perform_authentication() {
|
||
echo "Obtaining authentication tokens..."
|
||
|
||
# Step 1: Request device code
|
||
AUTH_RESPONSE=$(curl -s -X POST "https://oauth.accounts.hytale.com/oauth2/device/auth" \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "client_id=hytale-server" \
|
||
-d "scope=openid offline auth:server")
|
||
|
||
# Extract device_code and verification_uri_complete using jq
|
||
DEVICE_CODE=$(echo "$AUTH_RESPONSE" | jq -r '.device_code')
|
||
VERIFICATION_URI=$(echo "$AUTH_RESPONSE" | jq -r '.verification_uri_complete')
|
||
POLL_INTERVAL=$(echo "$AUTH_RESPONSE" | jq -r '.interval')
|
||
|
||
# Display authentication banner
|
||
echo " "
|
||
echo "╔═════════════════════════════════════════════════════════════════════════════╗"
|
||
echo "║ HYTALE SERVER AUTHENTICATION REQUIRED ║"
|
||
echo "╠═════════════════════════════════════════════════════════════════════════════╣"
|
||
echo "║ ║"
|
||
echo "║ Please authenticate the server by visiting the following URL: ║"
|
||
echo "║ ║"
|
||
echo "║ $VERIFICATION_URI ║"
|
||
echo "║ ║"
|
||
echo "║ 1. Click the link above or copy it to your browser ║"
|
||
echo "║ 2. Sign in with your Hytale account ║"
|
||
echo "║ 3. Authorize the server ║"
|
||
echo "║ ║"
|
||
echo "║ Waiting for authentication... ║"
|
||
echo "║ ║"
|
||
echo "╚═════════════════════════════════════════════════════════════════════════════╝"
|
||
echo " "
|
||
|
||
# Step 2: Poll for access token
|
||
ACCESS_TOKEN=""
|
||
while [ -z "$ACCESS_TOKEN" ]; do
|
||
sleep $POLL_INTERVAL
|
||
|
||
TOKEN_RESPONSE=$(curl -s -X POST "https://oauth.accounts.hytale.com/oauth2/token" \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "client_id=hytale-server" \
|
||
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
|
||
-d "device_code=$DEVICE_CODE")
|
||
|
||
# Check if we got an error
|
||
ERROR=$(echo "$TOKEN_RESPONSE" | jq -r '.error // empty')
|
||
|
||
if [ "$ERROR" = "authorization_pending" ]; then
|
||
echo "Still waiting for authentication..."
|
||
continue
|
||
elif [ -n "$ERROR" ]; then
|
||
echo "⨯ Authentication error: $ERROR"
|
||
exit 1
|
||
else
|
||
# Successfully authenticated
|
||
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
|
||
REFRESH_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.refresh_token')
|
||
echo ""
|
||
echo "✓ Authentication successful!"
|
||
echo ""
|
||
fi
|
||
done
|
||
|
||
# Fetch available game profiles
|
||
echo "Fetching game profiles..."
|
||
|
||
PROFILES_RESPONSE=$(curl -s -X GET "https://account-data.hytale.com/my-account/get-profiles" \
|
||
-H "Authorization: Bearer $ACCESS_TOKEN")
|
||
|
||
# Check if profiles list is empty
|
||
PROFILES_COUNT=$(echo "$PROFILES_RESPONSE" | jq '.profiles | length')
|
||
|
||
if [ "$PROFILES_COUNT" -eq 0 ]; then
|
||
echo "Error: No game profiles found. You need to purchase Hytale to run a server."
|
||
exit 1
|
||
fi
|
||
|
||
# Select profile based on GAME_PROFILE variable
|
||
if [ -n "$GAME_PROFILE" ]; then
|
||
# User specified a profile username, find matching UUID
|
||
echo "Looking for profile: $GAME_PROFILE"
|
||
PROFILE_UUID=$(echo "$PROFILES_RESPONSE" | jq -r ".profiles[] | select(.username == \"$GAME_PROFILE\") | .uuid")
|
||
|
||
if [ -z "$PROFILE_UUID" ] || [ "$PROFILE_UUID" = "null" ]; then
|
||
echo "Error: Profile '$GAME_PROFILE' not found."
|
||
echo "Available profiles:"
|
||
echo "$PROFILES_RESPONSE" | jq -r '.profiles[] | " - \(.username)"'
|
||
exit 1
|
||
fi
|
||
|
||
echo "✓ Using profile: $GAME_PROFILE (UUID: $PROFILE_UUID)"
|
||
else
|
||
# Use first profile from the list
|
||
PROFILE_UUID=$(echo "$PROFILES_RESPONSE" | jq -r '.profiles[0].uuid')
|
||
PROFILE_USERNAME=$(echo "$PROFILES_RESPONSE" | jq -r '.profiles[0].username')
|
||
|
||
echo "✓ Using default profile: $PROFILE_USERNAME (UUID: $PROFILE_UUID)"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# Save refresh token + profile for future use
|
||
save_auth_tokens
|
||
|
||
# Create game server session
|
||
if ! create_game_session; then
|
||
exit 1
|
||
fi
|
||
echo ""
|
||
}
|
||
|
||
# Copy start.sh template to /home/container
|
||
echo "Copying start.sh template to /home/container..."
|
||
cp -f /usr/local/bin/start.sh start.sh
|
||
chmod 755 start.sh
|
||
|
||
# Check if the backup directory exists
|
||
if [ ! -d "backup" ]; then
|
||
echo "Backup directory does not exist. Creating it..."
|
||
mkdir -p backup
|
||
|
||
if [ $? -ne 0 ]; then
|
||
echo "Failed to create backup directory: /backup"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "Backup directory already exists."
|
||
fi
|
||
chmod -R 755 backup
|
||
|
||
# Check if the downloader exists
|
||
if [ ! -f "$DOWNLOADER" ]; then
|
||
echo "Error: Hytale downloader not found!"
|
||
echo "Please run the installation script first."
|
||
exit 1
|
||
fi
|
||
|
||
# Check if the downloader is executable
|
||
if [ ! -x "$DOWNLOADER" ]; then
|
||
echo "Setting executable permissions..."
|
||
chmod +x "$DOWNLOADER"
|
||
fi
|
||
|
||
# Create version file
|
||
if [ ! -f "$VERSION_FILE" ]; then
|
||
echo "Creating version check file..."
|
||
touch $VERSION_FILE
|
||
fi
|
||
|
||
#Fix system permissions
|
||
if [ -f "$VERSION_FILE" ] && { [ ! -r "$VERSION_FILE" ] || [ ! -w "$VERSION_FILE" ]; }; then
|
||
echo "Fixing permissions on $VERSION_FILE..."
|
||
chmod 644 "$VERSION_FILE"
|
||
fi
|
||
if [ -f "$DOWNLOAD_CRED_FILE" ] && { [ ! -r "$DOWNLOAD_CRED_FILE" ] || [ ! -w "$DOWNLOAD_CRED_FILE" ]; }; then
|
||
echo "Fixing permissions on $DOWNLOAD_CRED_FILE..."
|
||
chmod 644 "$DOWNLOAD_CRED_FILE"
|
||
fi
|
||
if [ -f "$AUTH_CACHE_FILE" ] && { [ ! -r "$AUTH_CACHE_FILE" ] || [ ! -w "$AUTH_CACHE_FILE" ]; }; then
|
||
echo "Fixing permissions on $AUTH_CACHE_FILE..."
|
||
chmod 644 "$AUTH_CACHE_FILE"
|
||
fi
|
||
|
||
INITIAL_SETUP=0
|
||
|
||
# Check if credentials file exists, if not run the updater
|
||
if [ ! -f "$DOWNLOAD_CRED_FILE" ]; then
|
||
INITIAL_SETUP=1
|
||
echo "Credentials file not found, running initial setup..."
|
||
echo "Downloading server files..."
|
||
|
||
$DOWNLOADER -check-update
|
||
|
||
echo " "
|
||
echo "════════════════════════════════════════════════════════════════"
|
||
echo " NOTE: You must have purchased Hytale on the account you are using to authenticate."
|
||
echo "════════════════════════════════════════════════════════════════"
|
||
echo " "
|
||
|
||
if ! $DOWNLOADER -patchline $PATCHLINE -download-path server.zip; then
|
||
|
||
echo ""
|
||
echo "Error: Failed to download Hytale server files."
|
||
echo "This may indicate:"
|
||
echo " - You haven't purchased Hytale"
|
||
echo " - Authentication credentials are invalid or expired"
|
||
echo ""
|
||
|
||
echo "Removing invalid credential file..."
|
||
rm -f $DOWNLOAD_CRED_FILE
|
||
exit 1
|
||
fi
|
||
|
||
# Save version info after initial setup
|
||
DOWNLOADER_VERSION=$($DOWNLOADER -print-version 2>&1)
|
||
|
||
if [ $? -eq 0 ] && [ -n "$DOWNLOADER_VERSION" ]; then
|
||
echo "$DOWNLOADER_VERSION" > $VERSION_FILE
|
||
echo "✓ Saved version info!"
|
||
fi
|
||
|
||
extract_server_files
|
||
fi
|
||
|
||
# Run automatic update if enabled
|
||
if [ "$AUTOMATIC_UPDATE" = "1" ] && [ "$INITIAL_SETUP" = "0" ]; then
|
||
echo "Checking for updates..."
|
||
|
||
# Read local version from file
|
||
if [ -f "$VERSION_FILE" ]; then
|
||
LOCAL_VERSION=$(cat $VERSION_FILE)
|
||
else
|
||
echo "version file not found, forcing update"
|
||
LOCAL_VERSION=""
|
||
fi
|
||
|
||
# Get remote/downloader version
|
||
DOWNLOADER_VERSION=$($DOWNLOADER -print-version 2>&1)
|
||
|
||
# Check if version command failed
|
||
if [ $? -ne 0 ] || [ -z "$DOWNLOADER_VERSION" ]; then
|
||
echo "Error: Failed to get downloader version. This may indicate authentication issues."
|
||
echo "Output: $DOWNLOADER_VERSION"
|
||
exit 1
|
||
else
|
||
if [ -n "$LOCAL_VERSION" ]; then
|
||
echo "Local version: $LOCAL_VERSION"
|
||
fi
|
||
echo "Downloader version: $DOWNLOADER_VERSION"
|
||
|
||
# Compare versions
|
||
if [ "$LOCAL_VERSION" != "$DOWNLOADER_VERSION" ]; then
|
||
echo "⨯ Version mismatch, running update..."
|
||
|
||
$DOWNLOADER -check-update
|
||
$DOWNLOADER -patchline $PATCHLINE -download-path server.zip
|
||
|
||
# Update version file after successful update
|
||
echo "$DOWNLOADER_VERSION" > $VERSION_FILE
|
||
echo "✓ Saved version info!"
|
||
|
||
extract_server_files
|
||
else
|
||
echo "⨯ Versions match, skipping update"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Check if server files were downloaded correctly
|
||
if [ ! -f "HytaleServer.jar" ]; then
|
||
echo "Error: HytaleServer.jar not found!"
|
||
echo "Server files were not downloaded correctly."
|
||
exit 1
|
||
fi
|
||
|
||
# Download the latest hytale-sourcequery plugin if enabled
|
||
if [ "$ENABLE_SOURCE_QUERY_SUPPORT" = "1" ]; then
|
||
echo "Source Query support enabled, checking for plugin..."
|
||
|
||
# Create mods directory if it doesn't exist
|
||
if [ ! -d "$MODS_FOLDER" ]; then
|
||
echo "Creating mods directory..."
|
||
mkdir -p $MODS_FOLDER
|
||
fi
|
||
|
||
if [ -d "$MODS_FOLDER" ] && { [ ! -r "$MODS_FOLDER" ] || [ ! -w "$MODS_FOLDER" ] || [ ! -x "$MODS_FOLDER" ]; }; then
|
||
echo "Fixing permissions on directory $MODS_FOLDER..."
|
||
chmod 755 "$MODS_FOLDER"
|
||
fi
|
||
|
||
echo "Downloading latest hytale-sourcequery plugin..."
|
||
LATEST_URL=$(curl -sSL https://api.github.com/repos/physgun-com/hytale-sourcequery/releases/latest | jq -r '.assets[0].browser_download_url // empty')
|
||
|
||
if [ -n "$LATEST_URL" ]; then
|
||
curl -sSL -o mods/hytale-sourcequery.jar "$LATEST_URL"
|
||
|
||
if [ $? -eq 0 ]; then
|
||
echo "✓ Successfully downloaded hytale-sourcequery plugin to mods folder"
|
||
else
|
||
echo "⨯ Failed to download hytale-sourcequery plugin"
|
||
fi
|
||
else
|
||
echo "⨯ Could not find hytale-sourcequery plugin download URL"
|
||
fi
|
||
fi
|
||
|
||
# Check if GSP mode (tokens provided externally)
|
||
if [ -n "$OVERRIDE_SESSION_TOKEN" ] && [ -n "$OVERRIDE_IDENTITY_TOKEN" ]; then
|
||
echo "Using provided session and identity tokens..."
|
||
SESSION_TOKEN="$OVERRIDE_SESSION_TOKEN"
|
||
IDENTITY_TOKEN="$OVERRIDE_IDENTITY_TOKEN"
|
||
else
|
||
# Standard mode: perform authentication
|
||
if check_cached_tokens && load_cached_tokens; then
|
||
echo "Using cached authentication..."
|
||
if refresh_access_token; then
|
||
# Update cache in case refresh token rotated
|
||
save_auth_tokens
|
||
# Create fresh game session
|
||
if ! create_game_session; then
|
||
exit 1
|
||
fi
|
||
else
|
||
# Refresh failed, need full re-auth
|
||
echo "Refresh token expired, re-authenticating..."
|
||
rm -f "$AUTH_CACHE_FILE"
|
||
perform_authentication
|
||
fi
|
||
else
|
||
# Perform full authentication if no valid cache exists
|
||
perform_authentication
|
||
fi
|
||
fi
|
||
|
||
# Export the session tokens so they're available to start.sh
|
||
export SESSION_TOKEN
|
||
export IDENTITY_TOKEN
|
||
|
||
# Enforce file and folder permissions if enabled
|
||
if [ "$ENFORCE_PERMISSIONS" = "1" ]; then
|
||
echo "Enforcing permissions... This might take a while. Please be patient."
|
||
# Set all directories to 755
|
||
find . -type d -exec chmod 755 {} \;
|
||
# Set all files to 644, excluding executables that need to remain executable
|
||
find . -type f \
|
||
! -name "hytale-downloader-linux-amd64" \
|
||
! -name "hytale-downloader-linux-arm64" \
|
||
! -name "qemu-x86_64-static" \
|
||
! -name "start.sh" \
|
||
-exec chmod 644 {} \;
|
||
echo "✓ Permissions enforced (files: 644, folders: 755)"
|
||
fi
|
||
|
||
# Now call the pterodactyl entrypoint which will execute start.sh
|
||
exec /bin/bash /entrypoint.sh |