mirror of
https://github.com/NATroutter/egg-hytale.git
synced 2026-03-01 11:21:13 +03:00
488 lines
17 KiB
Bash
488 lines
17 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"
|
||
AUTH_CACHE_FILE=".hytale-auth-tokens.json"
|
||
DOWNLOADER="./hytale-downloader-linux-amd64"
|
||
USE_DOWNLOADER=1;
|
||
|
||
# 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 Server/* .
|
||
rmdir 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() {
|
||
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 if it doesn't exist
|
||
echo "Copying start.sh template to /home/container..."
|
||
cp /usr/local/bin/start.sh /home/container/start.sh
|
||
chmod 755 /home/container/start.sh
|
||
|
||
# Check if the backup directory exists
|
||
if [ ! -d "/home/container/backup" ]; then
|
||
echo "Backup directory does not exist. Creating it..."
|
||
mkdir -p /home/container/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 /home/container/backup
|
||
|
||
# Only proceed with downloader if on x86_64 architecture
|
||
if [ "$USE_DOWNLOADER" = "1" ]; then
|
||
# 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
|
||
|
||
INITIAL_SETUP=0
|
||
|
||
# Check if credentials file exists, if not run the updater
|
||
if [ ! -f ".hytale-downloader-credentials.json" ]; 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 .hytale-downloader-credentials.json
|
||
exit 1
|
||
fi
|
||
|
||
extract_server_files
|
||
|
||
# Save version info after initial setup
|
||
DOWNLOADER_VERSION=$($DOWNLOADER -print-version 2>&1)
|
||
|
||
if [ $? -eq 0 ] && [ -n "$DOWNLOADER_VERSION" ]; then
|
||
echo "$DOWNLOADER_VERSION" > version.txt
|
||
echo "✓ Saved version info to version.txt!"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Run automatic update if enabled (only on x86_64)
|
||
if [ "$USE_DOWNLOADER" = "1" ] && [ "${AUTOMATIC_UPDATE}" = "1" ] && [ "${INITIAL_SETUP}" = "0" ]; then
|
||
echo "Checking for updates..."
|
||
|
||
# Read local version from file
|
||
if [ -f "version.txt" ]; then
|
||
LOCAL_VERSION=$(cat version.txt)
|
||
else
|
||
echo "version.txt 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
|
||
echo "Local version: $LOCAL_VERSION"
|
||
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
|
||
extract_server_files
|
||
|
||
# Update version.txt after successful update
|
||
echo "$DOWNLOADER_VERSION" > version.txt
|
||
echo "✓ Saved version info to version.txt!"
|
||
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" ]; then
|
||
echo "Creating mods directory..."
|
||
mkdir -p mods
|
||
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 for cached authentication tokens
|
||
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
|
||
|
||
# Export the session tokens so they're available to start.sh
|
||
export SESSION_TOKEN
|
||
export IDENTITY_TOKEN
|
||
export PROFILE_UUID
|
||
|
||
# Now call the pterodactyl entrypoint which will execute start.sh
|
||
exec /bin/bash /entrypoint.sh |