Explorar o código

feat(newserver): add Hytale server support (#4928)

Adds the Hytale server (hyt): install, default config from Game-Server-Configs, Java 25 dependency check and first-start server login.
Andrew Quenehen hai 1 semana
pai
achega
004385134b
Modificáronse 40 ficheiros con 667 adicións e 8 borrados
  1. 195 0
      lgsm/config-default/config-lgsm/hytserver/_default.cfg
  2. 1 0
      lgsm/data/almalinux-8.csv
  3. 1 0
      lgsm/data/almalinux-9.csv
  4. 1 0
      lgsm/data/centos-7.csv
  5. 1 0
      lgsm/data/centos-8.csv
  6. 1 0
      lgsm/data/centos-9.csv
  7. 1 0
      lgsm/data/debian-10.csv
  8. 1 0
      lgsm/data/debian-11.csv
  9. 1 0
      lgsm/data/debian-12.csv
  10. 1 0
      lgsm/data/debian-13.csv
  11. 1 0
      lgsm/data/debian-9.csv
  12. BIN=BIN
      lgsm/data/gameicons/hyt-icon.png
  13. 1 0
      lgsm/data/rhel-7.csv
  14. 1 0
      lgsm/data/rhel-8.csv
  15. 1 0
      lgsm/data/rhel-9.csv
  16. 1 0
      lgsm/data/rocky-8.csv
  17. 1 0
      lgsm/data/rocky-9.csv
  18. 1 0
      lgsm/data/serverlist.csv
  19. 1 0
      lgsm/data/ubuntu-16.04.csv
  20. 1 0
      lgsm/data/ubuntu-18.04.csv
  21. 1 0
      lgsm/data/ubuntu-20.04.csv
  22. 1 0
      lgsm/data/ubuntu-22.04.csv
  23. 1 0
      lgsm/data/ubuntu-23.04.csv
  24. 1 0
      lgsm/data/ubuntu-23.10.csv
  25. 1 0
      lgsm/data/ubuntu-24.04.csv
  26. 1 0
      lgsm/data/ubuntu-26.04.csv
  27. 13 0
      lgsm/modules/alert.sh
  28. 16 3
      lgsm/modules/check_deps.sh
  29. 2 0
      lgsm/modules/check_system_requirements.sh
  30. 2 0
      lgsm/modules/command_check_update.sh
  31. 1 1
      lgsm/modules/command_monitor.sh
  32. 59 0
      lgsm/modules/command_start.sh
  33. 2 0
      lgsm/modules/command_update.sh
  34. 2 2
      lgsm/modules/core_getopt.sh
  35. 5 0
      lgsm/modules/core_modules.sh
  36. 23 0
      lgsm/modules/info_game.sh
  37. 11 2
      lgsm/modules/info_messages.sh
  38. 3 0
      lgsm/modules/install_config.sh
  39. 2 0
      lgsm/modules/install_server_files.sh
  40. 307 0
      lgsm/modules/update_hyt.sh

+ 195 - 0
lgsm/config-default/config-lgsm/hytserver/_default.cfg

@@ -0,0 +1,195 @@
+##################################
+######## Default Settings ########
+##################################
+# DO NOT EDIT, ANY CHANGES WILL BE OVERWRITTEN!
+# Copy settings from here and use them in either:
+# common.cfg - applies settings to every instance.
+# [instance].cfg - applies settings to a specific instance.
+
+#### Game Server Settings ####
+
+## Predefined Parameters | https://docs.linuxgsm.com/configuration/start-parameters
+# A Hytale account that owns the game is needed for two one-time logins:
+# 1. Install: the Hytale downloader shows a login URL to approve in a browser (also in docker logs).
+# 2. First start: LinuxGSM shows the server login URL (and sends it as an alert) to approve.
+#    The server saves it (Server/auth.enc) and restores it on restart. To log in again manually:
+#    ./hytserver send "/auth login device"
+# https://docs.linuxgsm.com/game-servers/hytale
+# Server name, password and max players are set in Server/config.json, created on first start.
+ip="0.0.0.0"
+port="5520"
+
+## Server Parameters | https://docs.linuxgsm.com/configuration/start-parameters#additional-parameters
+startparameters="--bind ${ip}:${port}"
+
+## Release Settings
+# Patchline (release|pre-release)
+hytalepatchline="release"
+
+#### LinuxGSM Settings ####
+
+## LinuxGSM Stats
+# Send useful stats to LinuxGSM developers.
+# https://docs.linuxgsm.com/configuration/linuxgsm-stats
+# (on|off)
+stats="off"
+
+## Notification Alerts
+# (on|off)
+
+# Display IP | https://docs.linuxgsm.com/alerts#display-ip
+displayip=""
+
+# More info | https://docs.linuxgsm.com/alerts#more-info
+postalert="off"
+
+# Alert on Start/Stop/Restart
+statusalert="off"
+
+# Discord Alerts | https://docs.linuxgsm.com/alerts/discord
+discordalert="off"
+discordwebhook="webhook"
+
+# Email Alerts | https://docs.linuxgsm.com/alerts/email
+emailalert="off"
+email="email@example.com"
+emailfrom=""
+
+# Gotify Alerts | https://docs.linuxgsm.com/alerts/gotify
+gotifyalert="off"
+gotifytoken="token"
+gotifywebhook="webhook"
+
+# IFTTT Alerts | https://docs.linuxgsm.com/alerts/ifttt
+iftttalert="off"
+ifttttoken="accesstoken"
+iftttevent="linuxgsm_alert"
+
+# ntfy Alerts | https://docs.linuxgsm.com/alerts/ntfy
+ntfyalert="off"
+ntfytopic="LinuxGSM"
+ntfyserver="https://ntfy.sh"
+ntfytoken=""
+ntfyusername=""
+ntfypassword=""
+ntfypriority=""
+ntfytags=""
+
+# Pushbullet Alerts | https://docs.linuxgsm.com/alerts/pushbullet
+pushbulletalert="off"
+pushbullettoken="accesstoken"
+channeltag=""
+
+# Pushover Alerts | https://docs.linuxgsm.com/alerts/pushover
+pushoveralert="off"
+pushovertoken="accesstoken"
+pushoveruserkey="userkey"
+
+# Rocket.Chat Alerts | https://docs.linuxgsm.com/alerts/rocket.chat
+rocketchatalert="off"
+rocketchatwebhook="webhook"
+
+# Slack Alerts | https://docs.linuxgsm.com/alerts/slack
+slackalert="off"
+slackwebhook="webhook"
+
+# Telegram Alerts | https://docs.linuxgsm.com/alerts/telegram
+# You can add a custom cURL string eg proxy (useful in Russia) in "curlcustomstring".
+# For example "--socks5 ipaddr:port" for socks5 proxy see more in "curl --help all".
+telegramapi="api.telegram.org"
+telegramalert="off"
+telegramtoken="accesstoken"
+telegramchatid=""
+telegramthreadid=""
+telegramsilentnotification="false"
+curlcustomstring=""
+
+## Updating | https://docs.linuxgsm.com/commands/update
+updateonstart="off"
+
+## Backup | https://docs.linuxgsm.com/commands/backup
+maxbackups="4"
+maxbackupdays="30"
+stoponbackup="on"
+
+## Logging | https://docs.linuxgsm.com/features/logging
+consolelogging="on"
+logdays="7"
+
+## Monitor | https://docs.linuxgsm.com/commands/monitor
+# Query delay time
+querydelay="1"
+
+## ANSI Colors | https://docs.linuxgsm.com/features/ansi-colors
+ansi="on"
+
+#### Advanced Settings ####
+
+## Message Display Time | https://docs.linuxgsm.com/features/message-display-time
+sleeptime="0.5"
+
+## Stop Mode | https://docs.linuxgsm.com/features/stop-mode
+# 1: tmux kill
+# 2: CTRL+c
+# 3: quit
+# 4: quit 120s
+# 5: stop
+# 6: q
+# 7: exit
+# 8: 7 Days to Die
+# 9: GoldSrc
+# 10: Avorion
+# 11: end
+stopmode="5"
+
+## Query mode
+# 1: session only
+# 2: gamedig (gsquery fallback)
+# 3: gamedig
+# 4: gsquery
+# 5: tcp
+querymode="1"
+querytype=""
+
+## Console type
+consoleverbose="yes"
+consoleinteract="yes"
+
+## Game Server Details
+# Do not edit
+gamename="Hytale"
+engine="hytale"
+glibc="null"
+
+#### Directories ####
+# Edit with care
+
+## Game Server Directories
+systemdir="${serverfiles}/Server"
+executabledir="${serverfiles}"
+executable="./start.sh"
+servercfgdir="${systemdir}"
+servercfg="config.json"
+servercfgdefault="config.json"
+servercfgfullpath="${servercfgdir}/${servercfg}"
+
+## Backup Directory
+backupdir="${lgsmdir}/backup"
+
+## Logging Directories
+[ -n "${LGSM_LOGDIR}" ] && logdir="${LGSM_LOGDIR}" || logdir="${rootdir}/log"
+gamelogdir="${systemdir}/logs"
+lgsmlogdir="${logdir}/script"
+consolelogdir="${logdir}/console"
+lgsmlog="${lgsmlogdir}/${selfname}-script.log"
+consolelog="${consolelogdir}/${selfname}-console.log"
+alertlog="${lgsmlogdir}/${selfname}-alert.log"
+postdetailslog="${lgsmlogdir}/${selfname}-postdetails.log"
+
+## Logs Naming
+lgsmlogdate="${lgsmlogdir}/${selfname}-script-$(date '+%Y-%m-%d-%H:%M:%S').log"
+consolelogdate="${consolelogdir}/${selfname}-console-$(date '+%Y-%m-%d-%H:%M:%S').log"
+
+## Log Parameters
+logtimestamp="off"
+logtimestampformat="%Y-%m-%d %H:%M:%S"

+ 1 - 0
lgsm/data/almalinux-8.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/almalinux-9.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/centos-7.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/centos-8.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/centos-9.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/debian-10.csv

@@ -53,6 +53,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/debian-11.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/debian-12.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/debian-13.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/debian-9.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

BIN=BIN
lgsm/data/gameicons/hyt-icon.png


+ 1 - 0
lgsm/data/rhel-7.csv

@@ -55,6 +55,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/rhel-8.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/rhel-9.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/rocky-8.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/rocky-9.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,zlib-devel
 hw,zlib-devel
+hyt,java-25-openjdk
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/serverlist.csv

@@ -53,6 +53,7 @@ hl2dm,hl2dmserver,Half-Life 2: Deathmatch,ubuntu-24.04
 hldm,hldmserver,Half-Life: Deathmatch,ubuntu-24.04
 hldm,hldmserver,Half-Life: Deathmatch,ubuntu-24.04
 hldms,hldmsserver,Half-Life Deathmatch: Source,ubuntu-24.04
 hldms,hldmsserver,Half-Life Deathmatch: Source,ubuntu-24.04
 hw,hwserver,Hurtworld,ubuntu-24.04
 hw,hwserver,Hurtworld,ubuntu-24.04
+hyt,hytserver,Hytale,ubuntu-24.04
 hz,hzserver,Humanitz,ubuntu-24.04
 hz,hzserver,Humanitz,ubuntu-24.04
 ins,insserver,Insurgency,ubuntu-24.04
 ins,insserver,Insurgency,ubuntu-24.04
 inss,inssserver,Insurgency: Sandstorm,ubuntu-24.04
 inss,inssserver,Insurgency: Sandstorm,ubuntu-24.04

+ 1 - 0
lgsm/data/ubuntu-16.04.csv

@@ -55,6 +55,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/ubuntu-18.04.csv

@@ -55,6 +55,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/ubuntu-20.04.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/ubuntu-22.04.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/ubuntu-23.04.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/ubuntu-23.10.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/ubuntu-24.04.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 1 - 0
lgsm/data/ubuntu-26.04.csv

@@ -54,6 +54,7 @@ hl2dm
 hldm
 hldm
 hldms
 hldms
 hw,lib32z1
 hw,lib32z1
+hyt,openjdk-25-jre
 hz
 hz
 ins
 ins
 inss
 inss

+ 13 - 0
lgsm/modules/alert.sh

@@ -169,6 +169,17 @@ fn_alert_backup() {
 	alertcolourdec="52480"
 	alertcolourdec="52480"
 }
 }
 
 
+fn_alert_hytale_login() {
+	fn_script_log_info "Sending alert: ${selfname} needs a Hytale server login"
+	alertaction="Login Required"
+	alertemoji="🔑"
+	alertsound="2"
+	alertmessage="${selfname} needs a Hytale server login. Open ${hytloginurl} and log in with a Hytale account that owns the game. The code expires in 10 minutes."
+	# Blue
+	alertcolourhex="#1e90ff"
+	alertcolourdec="2003199"
+}
+
 fn_alert_permissions() {
 fn_alert_permissions() {
 	fn_script_log_info "Sending alert: ${selfname} has permissions issues"
 	fn_script_log_info "Sending alert: ${selfname} has permissions issues"
 	alertaction="Checked Permissions"
 	alertaction="Checked Permissions"
@@ -250,6 +261,8 @@ elif [ "${alert}" == "update-linuxgsm" ]; then
 	fn_alert_update_linuxgsm
 	fn_alert_update_linuxgsm
 elif [ "${alert}" == "backup" ]; then
 elif [ "${alert}" == "backup" ]; then
 	fn_alert_backup
 	fn_alert_backup
+elif [ "${alert}" == "hytale-login" ]; then
+	fn_alert_hytale_login
 else
 else
 	fn_print_fail_nl "Missing alert type"
 	fn_print_fail_nl "Missing alert type"
 	fn_script_log_fail "Missing alert type"
 	fn_script_log_fail "Missing alert type"

+ 16 - 3
lgsm/modules/check_deps.sh

@@ -285,9 +285,22 @@ fn_deps_detector() {
 	elif [[ ${deptocheck} == "openjdk"* ]] || [[ ${deptocheck} == "java"* ]]; then
 	elif [[ ${deptocheck} == "openjdk"* ]] || [[ ${deptocheck} == "java"* ]]; then
 		# Is java already installed?
 		# Is java already installed?
 		if [ -n "${javaversion}" ]; then
 		if [ -n "${javaversion}" ]; then
-			# Added for users using Oracle JRE to bypass check.
-			depstatus=0
-			deptocheck="${javaversion}"
+			javamajorversion="$(echo "${javaversion}" | grep -Eo 'version "[0-9]+' | grep -Eo '[0-9]+' | head -1)"
+			if [ "${shortname}" == "hyt" ] && { [ -z "${javamajorversion}" ] || [ "${javamajorversion}" -lt "25" ]; }; then
+				# Java 25 installed but not the active java: installing it again would not change that.
+				if compgen -G "/usr/lib/jvm/*25*/bin/java" > /dev/null; then
+					depstatus=0
+					deptocheck="${javaversion}"
+					fn_print_warn_nl "Java 25 is installed but the active java is version ${javamajorversion:-unknown}. Select Java 25 with: sudo update-alternatives --config java"
+					fn_script_log_warn "Java 25 is installed but the active java is version ${javamajorversion:-unknown}"
+				else
+					depstatus=1
+				fi
+			else
+				# Added for users using Oracle JRE to bypass check.
+				depstatus=0
+				deptocheck="${javaversion}"
+			fi
 		else
 		else
 			depstatus=1
 			depstatus=1
 		fi
 		fi

+ 2 - 0
lgsm/modules/check_system_requirements.sh

@@ -31,6 +31,8 @@ elif [ "${shortname}" == "dodr" ]; then
 	ramrequirementgb="2"
 	ramrequirementgb="2"
 elif [ "${shortname}" == "hw" ]; then
 elif [ "${shortname}" == "hw" ]; then
 	ramrequirementgb="3"
 	ramrequirementgb="3"
+elif [ "${shortname}" == "hyt" ]; then
+	ramrequirementgb="4"
 elif [ "${shortname}" == "mc" ]; then
 elif [ "${shortname}" == "mc" ]; then
 	ramrequirementgb="1"
 	ramrequirementgb="1"
 elif [ "${shortname}" == "pmc" ]; then
 elif [ "${shortname}" == "pmc" ]; then

+ 2 - 0
lgsm/modules/command_check_update.sh

@@ -24,6 +24,8 @@ elif [ "${shortname}" == "pmc" ] || [ "${shortname}" == "vpmc" ] || [ "${shortna
 	update_pmc.sh
 	update_pmc.sh
 elif [ "${shortname}" == "fctr" ]; then
 elif [ "${shortname}" == "fctr" ]; then
 	update_fctr.sh
 	update_fctr.sh
+elif [ "${shortname}" == "hyt" ]; then
+	update_hyt.sh
 elif [ "${shortname}" == "mta" ]; then
 elif [ "${shortname}" == "mta" ]; then
 	update_mta.sh
 	update_mta.sh
 elif [ "${shortname}" == "jk2" ]; then
 elif [ "${shortname}" == "jk2" ]; then

+ 1 - 1
lgsm/modules/command_monitor.sh

@@ -165,7 +165,7 @@ fn_monitor_check_update() {
 		fi
 		fi
 	fi
 	fi
 
 
-	if [ -f "${lockdir}/update.lock" ] && [[ "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} update")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} validate")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} v")" != "0" || "$(pgrep -fc force-update "${USER}" "/bin/bash ./${selfname} fu")" != "0" ]]; then
+	if [ -f "${lockdir}/update.lock" ] && [[ "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} update")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} validate")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} v")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} force-update")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} fu")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} update-restart")" != "0" || "$(pgrep -fcx -u "${USER}" "/bin/bash ./${selfname} ur")" != "0" ]]; then
 		fn_print_dots "Checking update"
 		fn_print_dots "Checking update"
 		fn_print_checking_eol
 		fn_print_checking_eol
 		fn_print_info "Checking update: LinuxGSM is updating the game server"
 		fn_print_info "Checking update: LinuxGSM is updating the game server"

+ 59 - 0
lgsm/modules/command_start.sh

@@ -18,6 +18,55 @@ fn_start_jk2() {
 	TERM=screen tmux -L "${socketname}" end -t "${sessionname}" version ENTER > /dev/null 2>&1
 	TERM=screen tmux -L "${socketname}" end -t "${sessionname}" version ENTER > /dev/null 2>&1
 }
 }
 
 
+# Hytale: after the server boots, check whether it logged in. If it didn't (no saved login, or one that
+# can't be decrypted, e.g. created on another machine or container), start the login, then show the
+# login URL and send it as an alert, so it can be approved without opening the console.
+# Prints the server console: the console log, or the tmux pane when console logging is off.
+fn_start_hyt_console() {
+	if [ "${consolelogging}" == "off" ]; then
+		TERM=screen tmux -L "${socketname}" capture-pane -p -J -S - -t "${sessionname}" 2> /dev/null
+	else
+		cat "${consolelog}" 2> /dev/null
+	fi
+}
+
+fn_start_hyt_login() {
+	hytloginurl=""
+	local hytloginsent="${hytloginpending}"
+	for _ in {1..120}; do
+		hytloginurl=$(fn_start_hyt_console | grep -a -oE "https://[^ ]*device/verify\?user_code=[A-Za-z0-9]+" | tail -1)
+		if [ -n "${hytloginurl}" ]; then
+			break
+		fi
+		# Logged in with the saved server login: nothing to do.
+		if fn_start_hyt_console | grep -a -q "Authentication successful"; then
+			fn_script_log_info "Hytale server login restored from saved credentials"
+			return
+		fi
+		# Booted without a usable login: ask the server to start one.
+		if [ "${hytloginsent}" != "1" ] && fn_start_hyt_console | grep -a -q "Hytale Server Booted" && fn_start_hyt_console | grep -a -q "No server tokens configured"; then
+			fn_script_log_info "Hytale server has no usable saved login, starting login"
+			TERM=screen tmux -L "${socketname}" send-keys -t "${sessionname}" "auth login device" ENTER
+			hytloginsent="1"
+		fi
+		sleep 1
+	done
+	echo -e ""
+	if [ -n "${hytloginurl}" ]; then
+		echo -e "${bold}${lightyellow}Hytale server login required${default}"
+		echo -e "Open the URL below in a browser and log in with a Hytale account that owns the game."
+		echo -e "The code expires in 10 minutes. This is only needed once; the server saves the login."
+		echo -e "${hytloginurl}"
+		fn_script_log_info "Hytale server login required: ${hytloginurl}"
+		alert="hytale-login"
+		alert.sh
+	else
+		fn_print_warn_nl "Hytale server login required, but no login URL was found in the console yet"
+		echo -e "* Run ./${selfname} send \"/auth login device\" and approve the code shown in the console."
+		fn_script_log_warn "Hytale server login required, but no login URL was found in the console"
+	fi
+}
+
 fn_start_tmux() {
 fn_start_tmux() {
 	# check for tmux size variables.
 	# check for tmux size variables.
 	if [[ "${servercfgtmuxwidth}" =~ ^[0-9]+$ ]]; then
 	if [[ "${servercfgtmuxwidth}" =~ ^[0-9]+$ ]]; then
@@ -48,6 +97,13 @@ fn_start_tmux() {
 
 
 	fn_reload_startparameters
 	fn_reload_startparameters
 
 
+	# Hytale: with no saved server login file, have the server start its login (OAuth device code) on boot.
+	# A saved login that can't be used is handled after boot by fn_start_hyt_login.
+	if [ "${shortname}" == "hyt" ] && [ ! -f "${systemdir}/auth.enc" ]; then
+		startparameters="${startparameters} --boot-command 'auth login device'"
+		hytloginpending="1"
+	fi
+
 	# Create uid to ensure unique tmux socket name.
 	# Create uid to ensure unique tmux socket name.
 	if [ ! -f "${datadir}/${selfname}.uid" ]; then
 	if [ ! -f "${datadir}/${selfname}.uid" ]; then
 		check_status.sh
 		check_status.sh
@@ -160,6 +216,9 @@ fn_start_tmux() {
 			alert="restarted"
 			alert="restarted"
 			alert.sh
 			alert.sh
 		fi
 		fi
+		if [ "${shortname}" == "hyt" ]; then
+			fn_start_hyt_login
+		fi
 	fi
 	fi
 	rm -f "${lgsmlogdir:?}/.${selfname}-tmux-error.tmp" 2> /dev/null
 	rm -f "${lgsmlogdir:?}/.${selfname}-tmux-error.tmp" 2> /dev/null
 	echo -en "\n"
 	echo -en "\n"

+ 2 - 0
lgsm/modules/command_update.sh

@@ -25,6 +25,8 @@ elif [ "${shortname}" == "pmc" ] || [ "${shortname}" == "vpmc" ] || [ "${shortna
 	update_pmc.sh
 	update_pmc.sh
 elif [ "${shortname}" == "fctr" ]; then
 elif [ "${shortname}" == "fctr" ]; then
 	update_fctr.sh
 	update_fctr.sh
+elif [ "${shortname}" == "hyt" ]; then
+	update_hyt.sh
 elif [ "${shortname}" == "mta" ]; then
 elif [ "${shortname}" == "mta" ]; then
 	update_mta.sh
 	update_mta.sh
 elif [ "${shortname}" == "jk2" ]; then
 elif [ "${shortname}" == "jk2" ]; then

+ 2 - 2
lgsm/modules/core_getopt.sh

@@ -69,8 +69,8 @@ currentopt+=("${cmd_update_linuxgsm[@]}")
 if [ "${shortname}" == "jk2" ] || [ "${shortname}" == "etl" ] || [ "${engine}" != "idtech3" ]; then
 if [ "${shortname}" == "jk2" ] || [ "${shortname}" == "etl" ] || [ "${engine}" != "idtech3" ]; then
 	if [ "${shortname}" != "bf1942" ] && [ "${shortname}" != "bfv" ] && [ "${engine}" != "idtech2" ] && [ "${engine}" != "iw2.0" ] && [ "${engine}" != "iw3.0" ] && [ "${engine}" != "quake" ] && [ "${shortname}" != "samp" ] && [ "${shortname}" != "ut2k4" ]; then
 	if [ "${shortname}" != "bf1942" ] && [ "${shortname}" != "bfv" ] && [ "${engine}" != "idtech2" ] && [ "${engine}" != "iw2.0" ] && [ "${engine}" != "iw3.0" ] && [ "${engine}" != "quake" ] && [ "${shortname}" != "samp" ] && [ "${shortname}" != "ut2k4" ]; then
 		currentopt+=("${cmd_update[@]}" "${cmd_check_update[@]}")
 		currentopt+=("${cmd_update[@]}" "${cmd_check_update[@]}")
-		# force update for SteamCMD or Multi Theft Auto only.
-		if [ "${appid}" ] || [ "${shortname}" == "mta" ]; then
+		# force update for SteamCMD, Multi Theft Auto or Hytale only.
+		if [ "${appid}" ] || [ "${shortname}" == "mta" ] || [ "${shortname}" == "hyt" ]; then
 			currentopt+=("${cmd_force_update[@]}")
 			currentopt+=("${cmd_force_update[@]}")
 		fi
 		fi
 	fi
 	fi

+ 5 - 0
lgsm/modules/core_modules.sh

@@ -680,6 +680,11 @@ update_fctr.sh() {
 	fn_fetch_module
 	fn_fetch_module
 }
 }
 
 
+update_hyt.sh() {
+	modulefile="${FUNCNAME[0]}"
+	fn_fetch_module
+}
+
 update_jk2.sh() {
 update_jk2.sh() {
 	modulefile="${FUNCNAME[0]}"
 	modulefile="${FUNCNAME[0]}"
 	fn_fetch_module
 	fn_fetch_module

+ 23 - 0
lgsm/modules/info_game.sh

@@ -1350,6 +1350,27 @@ fn_info_game_hz() {
 	servername="${servername:-"NOT SET"}"
 	servername="${servername:-"NOT SET"}"
 }
 }
 
 
+# Config Type: json
+# Parameters: true
+# Comment:
+# Example: "ServerName": "SERVERNAME"
+# Filetype: json
+fn_info_game_hyt() {
+	# config.json is created by the server on first start.
+	if [ -f "${servercfgfullpath}" ]; then
+		fn_info_game_json "maxplayers" ".MaxPlayers // empty"
+		fn_info_game_json "servername" ".ServerName // empty"
+		fn_info_game_json "serverpassword" ".Password // empty"
+	fi
+	configtype="json"
+	configip="${ip:-"0.0.0.0"}"
+	maxplayers="${maxplayers:-"0"}"
+	port="${port:-"5520"}"
+	queryport="${port}"
+	servername="${servername:-"NOT SET"}"
+	serverpassword="${serverpassword:-"NOT SET"}"
+}
+
 # Config Type: parameters
 # Config Type: parameters
 # Parameters: true
 # Parameters: true
 # Comment:
 # Comment:
@@ -2397,6 +2418,8 @@ elif [ "${shortname}" == "hw" ]; then
 	fn_info_game_hw
 	fn_info_game_hw
 elif [ "${shortname}" == "hz" ]; then
 elif [ "${shortname}" == "hz" ]; then
 	fn_info_game_hz
 	fn_info_game_hz
+elif [ "${shortname}" == "hyt" ]; then
+	fn_info_game_hyt
 elif [ "${shortname}" == "inss" ]; then
 elif [ "${shortname}" == "inss" ]; then
 	fn_info_game_inss
 	fn_info_game_inss
 elif [ "${shortname}" == "jc2" ]; then
 elif [ "${shortname}" == "jc2" ]; then

+ 11 - 2
lgsm/modules/info_messages.sh

@@ -730,7 +730,7 @@ fn_info_messages_ports_edit() {
 		fi
 		fi
 	done
 	done
 	# engines/games that require editing the start parameters.
 	# engines/games that require editing the start parameters.
-	local ports_edit_array=("av" "ck" "col" "cs2" "fctr" "goldsrc" "hcu" "hw" "iw3.0" "ioquake3" "pw" "qfusion" "rsdw" "rust" "scpsl" "scpslsm" "sf" "sol" "spark" "source" "unreal4" "arma3" "dayz" "unt" "vh")
+	local ports_edit_array=("av" "ck" "col" "cs2" "fctr" "goldsrc" "hcu" "hw" "hyt" "iw3.0" "ioquake3" "pw" "qfusion" "rsdw" "rust" "scpsl" "scpslsm" "sf" "sol" "spark" "source" "unreal4" "arma3" "dayz" "unt" "vh")
 	for port_edit in "${ports_edit_array[@]}"; do
 	for port_edit in "${ports_edit_array[@]}"; do
 		if [ "${engine}" == "${port_edit}" ] || [ "${gamename}" == "${port_edit}" ] || [ "${shortname}" == "${port_edit}" ]; then
 		if [ "${engine}" == "${port_edit}" ] || [ "${gamename}" == "${port_edit}" ] || [ "${shortname}" == "${port_edit}" ]; then
 			startparameterslocation="${configdirserver}"
 			startparameterslocation="${configdirserver}"
@@ -754,7 +754,7 @@ fn_info_messages_ports() {
 		portcommand="ss -tuplwn | grep enfMain"
 		portcommand="ss -tuplwn | grep enfMain"
 	elif [ "${shortname}" == "q4" ]; then
 	elif [ "${shortname}" == "q4" ]; then
 		portcommand="ss -tuplwn | grep q4ded.x86"
 		portcommand="ss -tuplwn | grep q4ded.x86"
-	elif [ "${shortname}" == "mc" ] || [ "${shortname}" == "nec" ] || [ "${shortname}" == "pmc" ] || [ "${shortname}" == "vpmc" ] || [ "${shortname}" == "wmc" ]; then
+	elif [ "${shortname}" == "hyt" ] || [ "${shortname}" == "mc" ] || [ "${shortname}" == "nec" ] || [ "${shortname}" == "pmc" ] || [ "${shortname}" == "vpmc" ] || [ "${shortname}" == "wmc" ]; then
 		portcommand="ss -tuplwn | grep java"
 		portcommand="ss -tuplwn | grep java"
 	elif [ "${shortname}" == "terraria" ]; then
 	elif [ "${shortname}" == "terraria" ]; then
 		portcommand="ss -tuplwn | grep Main"
 		portcommand="ss -tuplwn | grep Main"
@@ -1131,6 +1131,13 @@ fn_info_messages_hz() {
 	} | column -s $'\t' -t
 	} | column -s $'\t' -t
 }
 }
 
 
+fn_info_messages_hyt() {
+	{
+		fn_port "header"
+		fn_port "Game" port udp
+	} | column -s $'\t' -t
+}
+
 fn_info_messages_ins() {
 fn_info_messages_ins() {
 	{
 	{
 		fn_port "header"
 		fn_port "header"
@@ -1874,6 +1881,8 @@ fn_info_messages_select_engine() {
 		fn_info_messages_hw
 		fn_info_messages_hw
 	elif [ "${shortname}" == "hz" ]; then
 	elif [ "${shortname}" == "hz" ]; then
 		fn_info_messages_hz
 		fn_info_messages_hz
+	elif [ "${shortname}" == "hyt" ]; then
+		fn_info_messages_hyt
 	elif [ "${shortname}" == "ins" ]; then
 	elif [ "${shortname}" == "ins" ]; then
 		fn_info_messages_ins
 		fn_info_messages_ins
 	elif [ "${shortname}" == "inss" ]; then
 	elif [ "${shortname}" == "inss" ]; then

+ 3 - 0
lgsm/modules/install_config.sh

@@ -520,6 +520,9 @@ elif [ "${shortname}" == "hl2dm" ]; then
 elif [ "${shortname}" == "hz" ]; then
 elif [ "${shortname}" == "hz" ]; then
 	# Config is generated on first run
 	# Config is generated on first run
 	:
 	:
+elif [ "${shortname}" == "hyt" ]; then
+	# Config is generated on first run
+	:
 elif [ "${shortname}" == "ins" ]; then
 elif [ "${shortname}" == "ins" ]; then
 	array_configs+=(server.cfg)
 	array_configs+=(server.cfg)
 	fn_default_config_remote
 	fn_default_config_remote

+ 2 - 0
lgsm/modules/install_server_files.sh

@@ -256,6 +256,8 @@ elif [ "${shortname}" == "mta" ]; then
 elif [ "${shortname}" == "fctr" ]; then
 elif [ "${shortname}" == "fctr" ]; then
 	update_fctr.sh
 	update_fctr.sh
 	install_factorio_save.sh
 	install_factorio_save.sh
+elif [ "${shortname}" == "hyt" ]; then
+	update_hyt.sh
 elif [ "${shortname}" == "jk2" ]; then
 elif [ "${shortname}" == "jk2" ]; then
 	update_jk2.sh
 	update_jk2.sh
 elif [ "${shortname}" == "vints" ]; then
 elif [ "${shortname}" == "vints" ]; then

+ 307 - 0
lgsm/modules/update_hyt.sh

@@ -0,0 +1,307 @@
+#!/bin/bash
+# LinuxGSM update_hyt.sh module
+# Author: Andrew dos Santos
+# Contributors: https://linuxgsm.com/contrib
+# Website: https://linuxgsm.com
+# Description: Handles updating of Hytale servers.
+
+moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
+
+fn_update_hyt_downloader_arch() {
+	case "${arch}" in
+		x86_64 | amd64)
+			hytaledownloaderfile="hytale-downloader-linux-amd64"
+			;;
+		aarch64 | arm64)
+			fn_print_failure "Hytale supports arm64 servers, but the official downloader archive currently used by LinuxGSM does not include a Linux arm64 downloader."
+			fn_script_log_fail "Unsupported Hytale downloader architecture: ${arch}"
+			core_exit.sh
+			;;
+		*)
+			fn_print_failure "Unknown or unsupported hytale-downloader architecture: ${arch}"
+			fn_script_log_fail "Unknown or unsupported hytale-downloader architecture: ${arch}"
+			core_exit.sh
+			;;
+	esac
+	hytaledownloader="${serverfiles}/${hytaledownloaderfile}"
+}
+
+fn_update_downloader() {
+	fn_update_hyt_downloader_arch
+	if [ ! -f "${hytaledownloader}" ] || [ "${forceupdate}" == "1" ]; then
+		fn_fetch_file "https://downloader.hytale.com/hytale-downloader.zip" "" "" "" "${tmpdir}" "hytale-downloader.zip" "nochmodx" "norun" "forcedl" "nohash"
+		echo -en "extracting hytale-downloader..."
+		unzip -oq "${tmpdir}/hytale-downloader.zip" "${hytaledownloaderfile}" "QUICKSTART.md" -d "${serverfiles}"
+		exitcode=$?
+		if [ "${exitcode}" -ne 0 ]; then
+			fn_print_fail_eol_nl
+			fn_script_log_fail "Extracting hytale-downloader"
+			core_exit.sh
+		else
+			chmod +x "${hytaledownloader}"
+			fn_print_ok_eol_nl
+			fn_script_log_pass "Extracting hytale-downloader"
+		fi
+	fi
+}
+
+fn_update_hyt_patchline_args() {
+	hytalepatchline="${hytalepatchline:-release}"
+	hytalepatchlineargs=()
+	if [ "${hytalepatchline}" != "release" ]; then
+		hytalepatchlineargs=(-patchline "${hytalepatchline}")
+	fi
+}
+
+# The downloader needs a Hytale account login (OAuth device code) the first time it runs, and again
+# if the saved credentials stop working. It waits for the login with no time limit, so it must
+# never run unattended without credentials, otherwise cron jobs hang.
+# Install always shows the login and waits: someone is watching, either in a terminal or in the
+# container logs (e.g. docker logs) during the first install.
+fn_update_hyt_auth() {
+	hytalecredentials="${serverfiles}/.hytale-downloader-credentials.json"
+	if [ "${firstcommandname}" == "INSTALL" ] || { [ -t 0 ] && [ -t 1 ]; }; then
+		hytaledownloadertimeout="900"
+		if [ ! -f "${hytalecredentials}" ]; then
+			echo -e ""
+			echo -e "${bold}${lightyellow}Hytale account login required${default}"
+			echo -e "The Hytale downloader needs you to log in with a Hytale account that owns the game."
+			echo -e "Open the URL shown below in a browser and approve the code within 15 minutes. This is only needed once."
+			echo -e "Running in Docker? The URL is also shown in the container logs (docker logs)."
+			echo -e ""
+			fn_script_log_info "Hytale downloader login required"
+		fi
+	else
+		hytaledownloadertimeout="300"
+		if [ ! -f "${hytalecredentials}" ]; then
+			fn_print_failure_nl "Hytale downloader is not logged in"
+			echo -e "* Run ./${selfname} update in a terminal and approve the Hytale account login once."
+			fn_script_log_fail "Hytale downloader is not logged in: ${hytalecredentials} not found"
+			core_exit.sh
+		fi
+	fi
+}
+
+# Report a downloader timeout, which usually means it was waiting for a login.
+fn_update_hyt_timeout_check() {
+	if [ "${exitcode}" -eq 124 ]; then
+		fn_print_failure_nl "Hytale downloader timed out, it is probably waiting for a Hytale account login"
+		echo -e "* The saved credentials may have expired. Run ./${selfname} update in a terminal to log in again."
+		fn_script_log_fail "Hytale downloader timed out after ${hytaledownloadertimeout}s"
+		core_exit.sh
+	fi
+}
+
+fn_update_extract() {
+	extractdir="${tmpdir}/hytale-game"
+	rm -rf "${extractdir:?}"
+	mkdir -p "${extractdir}"
+	echo -en "extracting ${remotebuildfilename}..."
+	unzip -oq "${tmpdir}/${remotebuildfilename}" -d "${extractdir}"
+	exitcode=$?
+	if [ "${exitcode}" -ne 0 ]; then
+		fn_print_fail_eol_nl
+		fn_script_log_fail "Extracting ${remotebuildfilename}"
+		core_exit.sh
+	fi
+
+	if [ -d "${extractdir}/game/Server" ]; then
+		extractedroot="${extractdir}/game"
+	else
+		extractedroot="${extractdir}"
+	fi
+
+	mkdir -p "${serverfiles}"
+	if [ "${firstcommandname}" == "INSTALL" ]; then
+		cp -a "${extractedroot}/." "${serverfiles}/"
+	else
+		for item in "${extractedroot}"/*; do
+			[ -e "${item}" ] || continue
+			itemname="$(basename "${item}")"
+			case "${itemname}" in
+				Server | .hytale-downloader-credentials.json)
+					continue
+					;;
+				jvm.options)
+					if [ -f "${serverfiles}/jvm.options" ]; then
+						continue
+					fi
+					;;
+			esac
+			cp -a "${item}" "${serverfiles}/"
+		done
+		mkdir -p "${serverfiles}/Server"
+		for item in "${extractedroot}/Server"/*; do
+			[ -e "${item}" ] || continue
+			itemname="$(basename "${item}")"
+			case "${itemname}" in
+				backups | bans.json | config.json | logs | mods | permissions.json | universe | whitelist.json)
+					continue
+					;;
+			esac
+			cp -a "${item}" "${serverfiles}/Server/"
+		done
+	fi
+	[ -f "${serverfiles}/start.sh" ] && chmod +x "${serverfiles}/start.sh"
+	fn_print_ok_eol_nl
+	fn_script_log_pass "Extracting ${remotebuildfilename}"
+	rm -rf "${extractdir:?}"
+}
+
+fn_update_dl() {
+	fn_update_downloader
+	fn_update_hyt_patchline_args
+	remotebuildfilename="hytale-server-${remotebuild:-latest}.zip"
+	echo -e "downloading file [ ${italic}${remotebuildfilename}${default} ]"
+	fn_sleep_time
+	cd "${serverfiles}" || exit
+	fn_update_hyt_auth
+	timeout --foreground "${hytaledownloadertimeout}" "${hytaledownloader}" -download-path "${tmpdir}/${remotebuildfilename}" "${hytalepatchlineargs[@]}" -skip-update-check
+	exitcode=$?
+	fn_update_hyt_timeout_check
+	if [ "${exitcode}" -ne 0 ]; then
+		fn_print_failure_nl "Downloading ${remotebuildfilename}"
+		fn_script_log_fail "Downloading ${remotebuildfilename}"
+		core_exit.sh
+	fi
+	fn_update_extract
+	if [ -n "${remotebuild}" ]; then
+		echo "${remotebuild}" > "${localbuildfile}"
+	fi
+	fn_clear_tmp
+}
+
+fn_update_hyt_check_localbuild_tracking() {
+	if [ -f "${localbuildfile}" ] && [ -f "${serverfiles}/Server/HytaleServer.jar" ] && [ "${serverfiles}/Server/HytaleServer.jar" -nt "${localbuildfile}" ]; then
+		fn_print_warn_nl "Hytale server files are newer than LinuxGSM build tracking"
+		echo -e "* Hytale may have updated itself outside LinuxGSM."
+		echo -e "* Run ./${selfname} update to refresh LinuxGSM version tracking."
+		fn_script_log_warn "Hytale server files are newer than LinuxGSM build tracking"
+	fi
+}
+
+fn_update_localbuild() {
+	fn_print_dots "Checking local build: ${remotelocation}"
+	if [ -f "${localbuildfile}" ]; then
+		localbuild="$(cat "${localbuildfile}")"
+	fi
+	if [ -z "${localbuild}" ]; then
+		fn_print_error "Checking local build: ${remotelocation}: missing local build info"
+		fn_script_log_error "Missing local build info"
+		fn_script_log_error "Set localbuild to 0"
+		localbuild="0"
+	else
+		fn_print_ok "Checking local build: ${remotelocation}"
+		fn_script_log_pass "Checking local build"
+		fn_update_hyt_check_localbuild_tracking
+	fi
+}
+
+fn_update_remotebuild() {
+	fn_update_downloader
+	fn_update_hyt_patchline_args
+	cd "${serverfiles}" || exit
+	remotebuildresponsefile="${tmpdir}/hytale-print-version-response.txt"
+	rm -f "${remotebuildresponsefile}"
+	fn_update_hyt_auth
+	timeout --foreground "${hytaledownloadertimeout}" "${hytaledownloader}" -print-version "${hytalepatchlineargs[@]}" -skip-update-check 2>&1 | tee "${remotebuildresponsefile}"
+	exitcode=${PIPESTATUS[0]}
+	fn_update_hyt_timeout_check
+	remotebuildresponse="$(cat "${remotebuildresponsefile}")"
+	if [ -f "${lgsmlog}" ]; then
+		cat "${remotebuildresponsefile}" >> "${lgsmlog}"
+	fi
+	remotebuild=$(echo "${remotebuildresponse}" | grep -Eo "[0-9]{4}\.[0-9]{2}\.[0-9]{2}-[A-Za-z0-9]+" | tail -1)
+	if [ -z "${remotebuild}" ]; then
+		remotebuild=$(echo "${remotebuildresponse}" | tail -1)
+	fi
+
+	if [ "${firstcommandname}" != "INSTALL" ]; then
+		fn_print_dots "Checking remote build: ${remotelocation}"
+		if [ "${exitcode}" -ne 0 ] || [ -z "${remotebuild}" ] || [ "${remotebuild}" == "null" ]; then
+			fn_print_fail "Checking remote build: ${remotelocation}"
+			fn_script_log_fail "Checking remote build"
+			core_exit.sh
+		else
+			fn_print_ok "Checking remote build: ${remotelocation}"
+			fn_script_log_pass "Checking remote build"
+		fi
+	else
+		if [ "${exitcode}" -ne 0 ] || [ -z "${remotebuild}" ] || [ "${remotebuild}" == "null" ]; then
+			fn_print_failure "Unable to get remote build"
+			fn_script_log_fail "Unable to get remote build"
+			core_exit.sh
+		fi
+	fi
+}
+
+fn_update_compare() {
+	fn_print_dots "Checking for update: ${remotelocation}"
+	if [ "${localbuild}" != "${remotebuild}" ] || [ "${forceupdate}" == "1" ]; then
+		date '+%s' > "${lockdir:?}/update.lock"
+		fn_print_ok_nl "Checking for update: ${remotelocation}"
+		fn_print "\n"
+		fn_print_nl "${bold}${underline}Update${default} available"
+		fn_print_nl "* Local build: ${red}${localbuild}${default}"
+		fn_print_nl "* Remote build: ${green}${remotebuild}${default}"
+		fn_print_nl "* Patchline: ${hytalepatchline}"
+		fn_print "\n"
+		fn_script_log_info "Update available"
+		fn_script_log_info "Local build: ${localbuild}"
+		fn_script_log_info "Remote build: ${remotebuild}"
+		fn_script_log_info "Patchline: ${hytalepatchline}"
+
+		if [ "${commandname}" == "UPDATE" ]; then
+			date +%s > "${lockdir:?}/last-updated.lock"
+			unset updateonstart
+			check_status.sh
+			if [ "${status}" == "0" ]; then
+				fn_update_dl
+			else
+				fn_print_restart_warning
+				exitbypass=1
+				command_stop.sh
+				fn_firstcommand_reset
+				exitbypass=1
+				fn_update_dl
+				exitbypass=1
+				command_start.sh
+				fn_firstcommand_reset
+			fi
+			unset exitbypass
+			alert="update"
+		elif [ "${commandname}" == "CHECK-UPDATE" ]; then
+			alert="check-update"
+		fi
+		alert.sh
+	else
+		fn_print_ok_nl "Checking for update: ${remotelocation}"
+		fn_print "\n"
+		fn_print_nl "${bold}${underline}No update${default} available"
+		fn_print_nl "* Local build: ${green}${localbuild}${default}"
+		fn_print_nl "* Remote build: ${green}${remotebuild}${default}"
+		fn_print_nl "* Patchline: ${hytalepatchline}"
+		fn_print "\n"
+		fn_script_log_info "No update available"
+		fn_script_log_info "Local build: ${localbuild}"
+		fn_script_log_info "Remote build: ${remotebuild}"
+		fn_script_log_info "Patchline: ${hytalepatchline}"
+	fi
+}
+
+info_distro.sh
+
+localbuildfile="${datadir}/${selfname}-hytale-version"
+remotelocation="hytale.com"
+
+if [ "${firstcommandname}" == "INSTALL" ]; then
+	fn_update_remotebuild
+	fn_update_dl
+else
+	fn_print_dots "Checking for update"
+	fn_print_dots "Checking for update: ${remotelocation}"
+	fn_script_log_info "Checking for update: ${remotelocation}"
+	fn_update_localbuild
+	fn_update_remotebuild
+	fn_update_compare
+fi