From a2f4a05dbd9983c3a0245e4ba6d116a78e822e89 Mon Sep 17 00:00:00 2001 From: NATroutter Date: Sat, 17 Jan 2026 19:07:32 +0200 Subject: [PATCH] Split up the code to multiple files for easier readability and maintainability --- Dockerfile | 4 + entrypoint.sh | 532 ++---------------------------------------- lib/authentication.sh | 250 ++++++++++++++++++++ lib/downloader.sh | 93 ++++++++ lib/plugins.sh | 28 +++ lib/system.sh | 56 +++++ lib/utilities.sh | 82 +++++++ start.sh | 2 - 8 files changed, 530 insertions(+), 517 deletions(-) create mode 100644 lib/authentication.sh create mode 100644 lib/downloader.sh create mode 100644 lib/plugins.sh create mode 100644 lib/system.sh create mode 100644 lib/utilities.sh diff --git a/Dockerfile b/Dockerfile index c99f633..9d447a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,10 @@ COPY --chmod=755 ./entrypoint.sh /entrypoint.sh # Strip Windows line endings (\r) just in case the file was edited on Windows RUN sed -i 's/\r$//' /entrypoint.sh +# Copy lib directory +COPY --chmod=755 ./lib /lib +RUN sed -i 's/\r$//' /lib/*.sh + # Create the container user RUN useradd -m -d /home/container -s /bin/bash container diff --git a/entrypoint.sh b/entrypoint.sh index f1fccc7..931d6d3 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -14,6 +14,12 @@ # Pelican or Pterodactyl panel instead. ################################################################################ +source "$(dirname "$0")/lib/utilities.sh" +source "$(dirname "$0")/lib/authentication.sh" +source "$(dirname "$0")/lib/system.sh" +source "$(dirname "$0")/lib/downloader.sh" +source "$(dirname "$0")/lib/plugins.sh" + DOWNLOAD_URL="https://downloader.hytale.com/hytale-downloader.zip" DOWNLOAD_FILE="hytale-downloader.zip" DOWNLOAD_CRED_FILE=".hytale-downloader-credentials.json" @@ -21,397 +27,22 @@ AUTH_CACHE_FILE=".hytale-auth-tokens.json" VERSION_FILE="version.txt" MODS_FOLDER="mods" -# Detect architecture and set appropriate downloader binary -ARCH=$(uname -m) -logger info "Platform: $ARCH" - -case "$ARCH" in - x86_64) - DOWNLOADER="./hytale-downloader-linux-amd64" - ;; - aarch64|arm64) - DOWNLOADER="./hytale-downloader-linux-arm64" - ;; - *) - logger error "Unsupported architecture: $ARCH" - logger info "Supported architectures: x86_64 (amd64), aarch64/arm64" - exit 1 - ;; -esac - -# Get and export timezone -TZ=${TZ:-UTC} -export TZ - -# Get and export the internal docker ip -INTERNAL_IP=$(ip route get 1 | awk '{print $(NF-2);exit}') -export INTERNAL_IP - -# Goto working directory -cd /home/container || exit 1 +# Initialize system +detect_architecture +setup_environment #Print java version echo " " java -version echo " " -# Setup colors -RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" RESET="" -if [ -t 1 ] || { [ -n "$TERM" ] && [ "$TERM" != "dumb" ]; }; then - # Helper to get color code (tput or ANSI fallback) - if command -v tput >/dev/null 2>&1 && tput setaf 1 >/dev/null 2>&1; then - _c() { tput setaf "$1"; } - _r() { tput sgr0; } - else - _c() { printf '\033[0;3%dm' "$1"; } - _r() { printf '\033[0m'; } - fi - - RED=$(_c 1) GREEN=$(_c 2) YELLOW=$(_c 3) - BLUE=$(_c 4) MAGENTA=$(_c 5) CYAN=$(_c 6) - RESET=$(_r) - unset -f _c _r -fi - -#Function to print colored text -printc() { - local text="$1" - - # Replace tags with color codes (or empty string if not supported) - text="${text//\{RED\}/$RED}" - text="${text//\{GREEN\}/$GREEN}" - text="${text//\{YELLOW\}/$YELLOW}" - text="${text//\{BLUE\}/$BLUE}" - text="${text//\{MAGENTA\}/$MAGENTA}" - text="${text//\{CYAN\}/$CYAN}" - text="${text//\{RESET\}/$RESET}" - printf "%b\n" "$text" -} - -# Logger function to print messages with different colors based on level -logger() { - local level="$1" - local message="$2" - - case "${level^^}" in - "INFO") printc "{BLUE}ℹ $message{RESET}" ;; - "WARN") printc "{YELLOW}⚠ $message{RESET}" ;; - "ERROR") printc "{RED}⨯ $message{RESET}" ;; - "SUCCESS") printc "{GREEN}✓ $message{RESET}" ;; - *) printc "$message" ;; - esac -} - -# Function to extract downloaded server files -extract_server_files() { - logger info "Extracting server files..." - SERVER_ZIP="server.zip" - - if [ -f "$SERVER_ZIP" ]; then - logger success "Found server archive: $SERVER_ZIP" - - # Extract to current directory - unzip -o "$SERVER_ZIP" - - if [ $? -ne 0 ]; then - logger error "Failed to extract $SERVER_ZIP" - exit 1 - fi - - logger success "Extraction completed successfully." - - # Move contents from Server folder to current directory - if [ -d "Server" ]; then - logger info "Moving server files from Server directory..." - cp -rf Server/* . - rm -rf ./Server - logger success "Server files moved to root directory." - fi - - # Clean up the zip file - logger info "Cleaning up archive file..." - rm "$SERVER_ZIP" - logger success "Archive removed." - else - logger 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 - logger warn "jq not found, cannot use cached tokens" - return 1 - fi - - # Validate JSON format - if ! jq empty "$AUTH_CACHE_FILE" 2>/dev/null; then - logger warn "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 - logger warn "Cached token file missing required keys, removing..." - rm "$AUTH_CACHE_FILE" - return 1 - fi - - logger success "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 - logger error "Incomplete cached tokens, re-authenticating..." - rm "$AUTH_CACHE_FILE" - return 1 - fi - - logger success "Loaded cached refresh token + profile UUID" - return 0 -} - -# Function to refresh access token using cached refresh token -refresh_access_token() { - logger info "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 - logger 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 - logger 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 - - logger success "Access token refreshed" - return 0 -} - -# Function to create a new game session -create_game_session() { - logger info "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 - logger error "Invalid JSON response from game session creation" - logger info "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 - logger error "Failed to create game server session" - logger info "Response: $SESSION_RESPONSE" - return 1 - fi - - logger success "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 - logger info "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 - logger info "Refresh token cached for future use" -} - -# Function to perform full authentication -perform_authentication() { - logger info "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 " " - printc "{MAGENTA}╔═════════════════════════════════════════════════════════════════════════════╗" - printc "{MAGENTA}║ {BLUE}HYTALE SERVER AUTHENTICATION REQUIRED {MAGENTA}║" - printc "{MAGENTA}╠═════════════════════════════════════════════════════════════════════════════╣" - printc "{MAGENTA}║ ║" - printc "{MAGENTA}║ {CYAN}Please authenticate the server by visiting the following URL: {MAGENTA}║" - printc "{MAGENTA}║ ║" - printc "{MAGENTA}║ {YELLOW}$VERIFICATION_URI {MAGENTA}║" - printc "{MAGENTA}║ ║" - printc "{MAGENTA}║ {CYAN}1. Click the link above or copy it to your browser {MAGENTA}║" - printc "{MAGENTA}║ {CYAN}2. Sign in with your Hytale account {MAGENTA}║" - printc "{MAGENTA}║ {CYAN}3. Authorize the server {MAGENTA}║" - printc "{MAGENTA}║ ║" - printc "{MAGENTA}║ {CYAN}Waiting for authentication... {MAGENTA}║" - printc "{MAGENTA}║ ║" - printc "{MAGENTA}╚═════════════════════════════════════════════════════════════════════════════╝" - printc " " - - # 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 - logger info "Still waiting for authentication..." - continue - elif [ -n "$ERROR" ]; then - logger error "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 "" - logger success "Authentication successful!" - echo "" - fi - done - - # Fetch available game profiles - logger info "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 - logger 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 - logger info "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 - logger error "Profile '$GAME_PROFILE' not found." - logger info "Available profiles:" - logger success "$PROFILES_RESPONSE" | jq -r '.profiles[] | " - \(.username)"' - exit 1 - fi - - logger success "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') - - logger success "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 logger info "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 - logger info "Backup directory does not exist. Creating it..." - mkdir -p backup - - if [ $? -ne 0 ]; then - logger error "Failed to create backup directory: /backup" - exit 1 - fi -else - logger info "Backup directory already exists." -fi -chmod -R 755 backup - -# Check if the downloader exists -if [ ! -f "$DOWNLOADER" ]; then - logger error "Hytale downloader not found!" - logger error "Please run the installation script first." - exit 1 -fi - -# Check if the downloader is executable -if [ ! -x "$DOWNLOADER" ]; then - logger info "Setting executable permissions..." - chmod +x "$DOWNLOADER" -fi +setup_backup_directory +ensure_downloader # Create version file if [ ! -f "$VERSION_FILE" ]; then @@ -433,128 +64,9 @@ if [ -f "$AUTH_CACHE_FILE" ] && { [ ! -r "$AUTH_CACHE_FILE" ] || [ ! -w "$AUTH_C 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 - logger warn "Credentials file not found, running initial setup..." - logger info "Downloading server files..." - - $DOWNLOADER -check-update - - echo " " - printc "{MAGENTA}╔══════════════════════════════════════════════════════════════════════════════════════╗" - printc "{MAGENTA}║ {BLUE}NOTE: You must have purchased Hytale on the account you are using to authenticate. {MAGENTA}║" - printc "{MAGENTA}╚══════════════════════════════════════════════════════════════════════════════════════╝" - echo " " - - if ! $DOWNLOADER -patchline $PATCHLINE -download-path server.zip; then - - echo "" - logger error "Failed to download Hytale server files." - logger error "This may indicate:" - logger error " - You haven't purchased Hytale" - logger error " - Authentication credentials are invalid or expired" - echo "" - - logger warn "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 - logger success "Saved version info!" - fi - - extract_server_files -fi - -# Run automatic update if enabled -if [ "$AUTOMATIC_UPDATE" = "1" ] && [ "$INITIAL_SETUP" = "0" ]; then - logger info "Checking for updates..." - - # Read local version from file - if [ -f "$VERSION_FILE" ]; then - LOCAL_VERSION=$(cat $VERSION_FILE) - else - logger warn "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 - logger error "Failed to get downloader version. This may indicate authentication issues." - logger error "Output: $DOWNLOADER_VERSION" - exit 1 - else - if [ -n "$LOCAL_VERSION" ]; then - logger info "Local version: $LOCAL_VERSION" - fi - logger info "Downloader version: $DOWNLOADER_VERSION" - - # Compare versions - if [ "$LOCAL_VERSION" != "$DOWNLOADER_VERSION" ]; then - logger warn "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 - logger success "Saved version info!" - - extract_server_files - else - logger info "Versions match, skipping update" - fi - fi -fi - -# Check if server files were downloaded correctly -if [ ! -f "HytaleServer.jar" ]; then - logger error "HytaleServer.jar not found!" - logger error "Server files were not downloaded correctly." - exit 1 -fi - -# Download the latest hytale-sourcequery plugin if enabled -if [ "$ENABLE_SOURCE_QUERY_SUPPORT" = "1" ]; then - logger info "Source Query support enabled, checking for plugin..." - - # Create mods directory if it doesn't exist - if [ ! -d "$MODS_FOLDER" ]; then - logger warn "Creating mods directory..." - mkdir -p $MODS_FOLDER - fi - - if [ -d "$MODS_FOLDER" ] && { [ ! -r "$MODS_FOLDER" ] || [ ! -w "$MODS_FOLDER" ] || [ ! -x "$MODS_FOLDER" ]; }; then - logger warn "Fixing permissions on directory $MODS_FOLDER..." - chmod 755 "$MODS_FOLDER" - fi - - logger info "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 - logger success "Successfully downloaded hytale-sourcequery plugin to mods folder" - else - logger error "Failed to download hytale-sourcequery plugin" - fi - else - logger error "Could not find hytale-sourcequery plugin download URL" - fi -fi +run_update_process +validate_server_files +install_sourcequery # Check if GSP mode (tokens provided externally) if [ -n "$OVERRIDE_SESSION_TOKEN" ] && [ -n "$OVERRIDE_IDENTITY_TOKEN" ]; then @@ -589,19 +101,9 @@ export SESSION_TOKEN export IDENTITY_TOKEN # Enforce file and folder permissions if enabled -if [ "$ENFORCE_PERMISSIONS" = "1" ]; then - logger warn "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 {} \; - logger success "Permissions enforced (files: 644, folders: 755)" -fi +enforce_permissions + +logger info "Starting Hytale server..." # Convert startup variables to from {{VARIABLE}} to ${VARIABLE} for the evaluating PARSED=$(echo "$STARTUP" | sed -e 's/{{/${/g' -e 's/}}/}/g') diff --git a/lib/authentication.sh b/lib/authentication.sh new file mode 100644 index 0000000..d06d08b --- /dev/null +++ b/lib/authentication.sh @@ -0,0 +1,250 @@ +# 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 + logger warn "jq not found, cannot use cached tokens" + return 1 + fi + + # Validate JSON format + if ! jq empty "$AUTH_CACHE_FILE" 2>/dev/null; then + logger warn "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 + logger warn "Cached token file missing required keys, removing..." + rm "$AUTH_CACHE_FILE" + return 1 + fi + + logger success "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 + logger error "Incomplete cached tokens, re-authenticating..." + rm "$AUTH_CACHE_FILE" + return 1 + fi + + logger success "Loaded cached refresh token + profile UUID" + return 0 +} + + + +# Function to refresh access token using cached refresh token +refresh_access_token() { + logger info "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 + logger 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 + logger 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 + + logger success "Access token refreshed" + return 0 +} + + + +# Function to create a new game session +create_game_session() { + logger info "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 + logger error "Invalid JSON response from game session creation" + logger info "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 + logger error "Failed to create game server session" + logger info "Response: $SESSION_RESPONSE" + return 1 + fi + + logger success "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 + logger info "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 + logger info "Refresh token cached for future use" +} + + + +# Function to perform full authentication +perform_authentication() { + logger info "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 " " + printc "{MAGENTA}╔═════════════════════════════════════════════════════════════════════════════╗" + printc "{MAGENTA}║ {BLUE}HYTALE SERVER AUTHENTICATION REQUIRED {MAGENTA}║" + printc "{MAGENTA}╠═════════════════════════════════════════════════════════════════════════════╣" + printc "{MAGENTA}║ ║" + printc "{MAGENTA}║ {CYAN}Please authenticate the server by visiting the following URL: {MAGENTA}║" + printc "{MAGENTA}║ ║" + printc "{MAGENTA}║ {YELLOW}$VERIFICATION_URI {MAGENTA}║" + printc "{MAGENTA}║ ║" + printc "{MAGENTA}║ {CYAN}1. Click the link above or copy it to your browser {MAGENTA}║" + printc "{MAGENTA}║ {CYAN}2. Sign in with your Hytale account {MAGENTA}║" + printc "{MAGENTA}║ {CYAN}3. Authorize the server {MAGENTA}║" + printc "{MAGENTA}║ ║" + printc "{MAGENTA}║ {CYAN}Waiting for authentication... {MAGENTA}║" + printc "{MAGENTA}║ ║" + printc "{MAGENTA}╚═════════════════════════════════════════════════════════════════════════════╝" + printc " " + + # 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 + logger info "Still waiting for authentication..." + continue + elif [ -n "$ERROR" ]; then + logger error "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 "" + logger success "Authentication successful!" + echo "" + fi + done + + # Fetch available game profiles + logger info "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 + logger 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 + logger info "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 + logger error "Profile '$GAME_PROFILE' not found." + logger info "Available profiles:" + logger success "$PROFILES_RESPONSE" | jq -r '.profiles[] | " - \(.username)"' + exit 1 + fi + + logger success "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') + + logger success "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 "" +} \ No newline at end of file diff --git a/lib/downloader.sh b/lib/downloader.sh new file mode 100644 index 0000000..8674a04 --- /dev/null +++ b/lib/downloader.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +ensure_downloader() { + if [ ! -f "$DOWNLOADER" ]; then + logger error "Hytale downloader not found!" + logger error "Please run the installation script first." + exit 1 + fi + + if [ ! -x "$DOWNLOADER" ]; then + logger info "Setting executable permissions for downloader..." + chmod +x "$DOWNLOADER" + fi +} + +run_update_process() { + local INITIAL_SETUP=0 + + # Check if credentials file exists, if not run the updater + if [ ! -f "$DOWNLOAD_CRED_FILE" ]; then + INITIAL_SETUP=1 + logger warn "Credentials file not found, running initial setup..." + logger info "Downloading server files..." + + $DOWNLOADER -check-update + + echo " " + printc "{MAGENTA}╔══════════════════════════════════════════════════════════════════════════════════════╗" + printc "{MAGENTA}║ {BLUE}NOTE: You must have purchased Hytale on the account you are using to authenticate. {MAGENTA}║" + printc "{MAGENTA}╚══════════════════════════════════════════════════════════════════════════════════════╝" + echo " " + + if ! $DOWNLOADER -patchline $PATCHLINE -download-path server.zip; then + echo "" + logger error "Failed to download Hytale server files." + logger warn "Removing invalid credential file..." + rm -f $DOWNLOAD_CRED_FILE + exit 1 + fi + + # Save version info after initial setup + local DOWNLOADER_VERSION=$($DOWNLOADER -print-version 2>&1) + if [ $? -eq 0 ] && [ -n "$DOWNLOADER_VERSION" ]; then + echo "$DOWNLOADER_VERSION" > $VERSION_FILE + logger success "Saved version info!" + fi + + extract_server_files + fi + + # Run automatic update if enabled + if [ "$AUTOMATIC_UPDATE" = "1" ] && [ "$INITIAL_SETUP" = "0" ]; then + logger info "Checking for updates..." + + local LOCAL_VERSION="" + if [ -f "$VERSION_FILE" ]; then + LOCAL_VERSION=$(cat $VERSION_FILE) + else + logger warn "Version file not found, forcing update" + fi + + local DOWNLOADER_VERSION=$($DOWNLOADER -print-version 2>&1) + + if [ $? -ne 0 ] || [ -z "$DOWNLOADER_VERSION" ]; then + logger error "Failed to get downloader version." + exit 1 + else + if [ -n "$LOCAL_VERSION" ]; then + logger info "Local version: $LOCAL_VERSION" + fi + logger info "Downloader version: $DOWNLOADER_VERSION" + + if [ "$LOCAL_VERSION" != "$DOWNLOADER_VERSION" ]; then + logger warn "Version mismatch, running update..." + $DOWNLOADER -check-update + $DOWNLOADER -patchline $PATCHLINE -download-path server.zip + echo "$DOWNLOADER_VERSION" > $VERSION_FILE + logger success "Saved version info!" + extract_server_files + else + logger info "Versions match, skipping update" + fi + fi + fi +} + +validate_server_files() { + if [ ! -f "HytaleServer.jar" ]; then + logger error "HytaleServer.jar not found!" + logger error "Server files were not downloaded correctly." + exit 1 + fi +} \ No newline at end of file diff --git a/lib/plugins.sh b/lib/plugins.sh new file mode 100644 index 0000000..ead8056 --- /dev/null +++ b/lib/plugins.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +install_sourcequery() { + if [ "$ENABLE_SOURCE_QUERY_SUPPORT" = "1" ]; then + logger info "Source Query support enabled, checking for plugin..." + + if [ ! -d "$MODS_FOLDER" ]; then + logger warn "Creating mods directory..." + mkdir -p $MODS_FOLDER + fi + + if [ -d "$MODS_FOLDER" ] && { [ ! -r "$MODS_FOLDER" ] || [ ! -w "$MODS_FOLDER" ] || [ ! -x "$MODS_FOLDER" ]; }; then + logger warn "Fixing permissions on directory $MODS_FOLDER..." + chmod 755 "$MODS_FOLDER" + fi + + logger info "Downloading latest hytale-sourcequery plugin..." + local 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 + if curl -sSL -o "${MODS_FOLDER}/hytale-sourcequery.jar" "$LATEST_URL"; then + logger success "Successfully downloaded hytale-sourcequery plugin" + else + logger error "Failed to download hytale-sourcequery plugin" + fi + fi + fi +} \ No newline at end of file diff --git a/lib/system.sh b/lib/system.sh new file mode 100644 index 0000000..51c2e19 --- /dev/null +++ b/lib/system.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +detect_architecture() { + local ARCH=$(uname -m) + logger info "Platform: $ARCH" + + case "$ARCH" in + x86_64) + DOWNLOADER="./hytale-downloader-linux-amd64" + ;; + aarch64|arm64) + DOWNLOADER="./hytale-downloader-linux-arm64" + ;; + *) + logger error "Unsupported architecture: $ARCH" + logger info "Supported architectures: x86_64 (amd64), aarch64/arm64" + exit 1 + ;; + esac +} + +setup_environment() { + # Get and export timezone + export TZ=${TZ:-UTC} + + # Get and export the internal docker ip + export INTERNAL_IP=$(ip route get 1 | awk '{print $(NF-2);exit}') + + # Goto working directory + cd /home/container || exit 1 +} + +setup_backup_directory() { + if [ ! -d "backup" ]; then + logger info "Backup directory does not exist. Creating it..." + mkdir -p backup + if [ $? -ne 0 ]; then + logger error "Failed to create backup directory: /backup" + exit 1 + fi + fi + chmod -R 755 backup +} + +enforce_permissions() { + if [ "$ENFORCE_PERMISSIONS" = "1" ]; then + logger warn "Enforcing permissions... This might take a while. Please be patient." + find . -type d -exec chmod 755 {} \; + find . -type f \ + ! -name "hytale-downloader-linux-amd64" \ + ! -name "hytale-downloader-linux-arm64" \ + ! -name "start.sh" \ + -exec chmod 644 {} \; + logger success "Permissions enforced (files: 644, folders: 755)" + fi +} \ No newline at end of file diff --git a/lib/utilities.sh b/lib/utilities.sh new file mode 100644 index 0000000..780f9b3 --- /dev/null +++ b/lib/utilities.sh @@ -0,0 +1,82 @@ +# Setup colors +RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" RESET="" +if [ -t 1 ] || { [ -n "$TERM" ] && [ "$TERM" != "dumb" ]; }; then + # Helper to get color code (tput or ANSI fallback) + if command -v tput >/dev/null 2>&1 && tput setaf 1 >/dev/null 2>&1; then + _c() { tput setaf "$1"; } + _r() { tput sgr0; } + else + _c() { printf '\033[0;3%dm' "$1"; } + _r() { printf '\033[0m'; } + fi + + RED=$(_c 1) GREEN=$(_c 2) YELLOW=$(_c 3) + BLUE=$(_c 4) MAGENTA=$(_c 5) CYAN=$(_c 6) + RESET=$(_r) + unset -f _c _r +fi + +#Function to print colored text +printc() { + local text="$1" + + # Replace tags with color codes (or empty string if not supported) + text="${text//\{RED\}/$RED}" + text="${text//\{GREEN\}/$GREEN}" + text="${text//\{YELLOW\}/$YELLOW}" + text="${text//\{BLUE\}/$BLUE}" + text="${text//\{MAGENTA\}/$MAGENTA}" + text="${text//\{CYAN\}/$CYAN}" + text="${text//\{RESET\}/$RESET}" + printf "%b\n" "$text" +} + +# Logger function to print messages with different colors based on level +logger() { + local level="$1" + local message="$2" + + case "${level^^}" in + "INFO") printc "{BLUE}ℹ $message{RESET}" ;; + "WARN") printc "{YELLOW}⚠ $message{RESET}" ;; + "ERROR") printc "{RED}⨯ $message{RESET}" ;; + "SUCCESS") printc "{GREEN}✓ $message{RESET}" ;; + *) printc "$message" ;; + esac +} + +# Function to extract downloaded server files +extract_server_files() { + logger info "Extracting server files..." + SERVER_ZIP="server.zip" + + if [ -f "$SERVER_ZIP" ]; then + logger success "Found server archive: $SERVER_ZIP" + + # Extract to current directory + unzip -o "$SERVER_ZIP" + + if [ $? -ne 0 ]; then + logger error "Failed to extract $SERVER_ZIP" + exit 1 + fi + + logger success "Extraction completed successfully." + + # Move contents from Server folder to current directory + if [ -d "Server" ]; then + logger info "Moving server files from Server directory..." + cp -rf Server/* . + rm -rf ./Server + logger success "Server files moved to root directory." + fi + + # Clean up the zip file + logger info "Cleaning up archive file..." + rm "$SERVER_ZIP" + logger success "Archive removed." + else + logger error "Server archive not found at $SERVER_ZIP" + exit 1 + fi +} \ No newline at end of file diff --git a/start.sh b/start.sh index b3f726d..e8de6d9 100644 --- a/start.sh +++ b/start.sh @@ -10,8 +10,6 @@ # Pelican or Pterodactyl panel instead. ################################################################################ -echo "Starting Hytale server..." - # Build the Java command JAVA_CMD="java"