Quellcode durchsuchen

feat(jk3server): add Jedi Academy with OpenJK (#3891)

* Update port number for jk2server

* Add jk3server LinuxGSM configuration
This adds Jedi Knight: Jedi Academy configuation to LinuxGSM
Similar to jk2server configuration but with minor differences.

* Update port number and defaultmap for jk3server

* feat(new server): Jedi Knight: Jedi Academy

* fix(jk3): misplaced number

* Update serverlist.csv

Try to add new server to serverlist

* add update jk3 module
also added jk3 to serverlistmenu

* update jk3s lgsm config

* remove serverlistmenu.csv
not meant to be there

* Update missing info for lgsm modules - jk3server

* fix typos

* add condition for starting jk3server in tmux

* fix(jk3server): select Academy app and bound release lookups

* fix(jk3server): omit unavailable config version from details

* refactor(jk3server): follow shared updater conventions

---------

Co-authored-by: Alexander Silva <alexander.silva@my.jcu.edu.au>
Co-authored-by: Daniel Gibbs <me@danielgibbs.co.uk>
アレックス vor 3 Tagen
Ursprung
Commit
d9079242a3

+ 215 - 0
lgsm/config-default/config-lgsm/jk3server/_default.cfg

@@ -0,0 +1,215 @@
+##################################
+######## 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 ####
+
+## SteamCMD Login | https://docs.linuxgsm.com/steamcmd#steamcmd-login
+# Requires a Steam account that owns Jedi Academy.
+steamuser="username"
+steampass='password'
+
+## Predefined Parameters | https://docs.linuxgsm.com/configuration/start-parameters
+ip="0.0.0.0"
+port="29070"
+defaultmap="mp/ffa3"
+
+## Server Parameters | https://docs.linuxgsm.com/configuration/start-parameters#additional-parameters
+startparameters="+set dedicated 2 +set fs_basepath \"${systemdir}\" +set fs_homepath \"${systemdir}\" +set net_ip ${ip} +set net_port ${port} +exec ${servercfg} +map ${defaultmap}"
+
+#### 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=""
+
+# Matrix Alerts | https://docs.linuxgsm.com/alerts/matrix
+matrixalert="off"
+matrixhomeserver="matrix.org"
+matrixtoken="accesstoken"
+matrixroom=""
+
+# 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=""
+
+## Stop | https://docs.linuxgsm.com/commands/start-stop-restart
+stoponlyifnoplayers="off"
+stoponlyifnoplayersallcommands="on"
+
+## Player Warnings | https://docs.linuxgsm.com/features/player-warnings
+# Minutes to warn players in-game before a stop, restart or update. 0 to disable.
+# e.g. 15 warns at 15, 10, 5, 3 and 1 minutes.
+warnplayerstimer="0"
+
+## 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"
+
+## SteamCMD Settings | https://docs.linuxgsm.com/steamcmd
+# Server appid
+appid="6020"
+steamcmdforcewindows="yes"
+# SteamCMD Branch | https://docs.linuxgsm.com/steamcmd/branch
+branch=""
+betapassword=""
+# Master Server | https://docs.linuxgsm.com/steamcmd/steam-master-server
+steammaster="false"
+
+## 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="3"
+
+## Query mode
+# 1: session only
+# 2: gamedig (gsquery fallback)
+# 3: gamedig
+# 4: gsquery
+# 5: tcp
+querymode="2"
+querytype="protocol-quake3"
+
+## Console type
+consoleverbose="yes"
+consoleinteract="yes"
+
+## Game Server Details
+# Do not edit
+gamename="Jedi Knight: Jedi Academy"
+engine="idtech3"
+glibc="2.34"
+
+#### Directories ####
+# Edit with care
+
+## Game Server Directories
+systemdir="${serverfiles}/GameData"
+executabledir="${systemdir}"
+executable="./openjkded.x86_64"
+servercfg="${selfname}.cfg"
+servercfgdefault="server.cfg"
+servercfgdir="${systemdir}/base"
+servercfgfullpath="${servercfgdir}/${servercfg}"
+
+## Backup Directory
+backupdir="${lgsmdir}/backups"
+
+## 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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -62,6 +62,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

BIN
lgsm/data/gameicons/jk3-icon.png


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

@@ -64,6 +64,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++,zlib
 kf
 kf2
 l4d

+ 1 - 0
lgsm/data/serverlist.csv

@@ -62,6 +62,7 @@ jbep3,jbep3server,Jabroni Brawl: Episode 3,ubuntu-26.04
 jc2,jc2server,Just Cause 2,ubuntu-26.04
 jc3,jc3server,Just Cause 3,ubuntu-26.04
 jk2,jk2server,Jedi Knight II: Jedi Outcast,ubuntu-26.04
+jk3,jk3server,Jedi Knight: Jedi Academy,ubuntu-26.04
 kf,kfserver,Killing Floor,ubuntu-26.04
 kf2,kf2server,Killing Floor 2,ubuntu-26.04
 l4d,l4dserver,Left 4 Dead,ubuntu-26.04

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

@@ -64,6 +64,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -64,6 +64,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

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

@@ -63,6 +63,7 @@ jbep3
 jc2
 jc3
 jk2
+jk3,libstdc++6,zlib1g
 kf
 kf2
 l4d

+ 2 - 0
lgsm/modules/command_check_update.sh

@@ -30,6 +30,8 @@ elif [ "${shortname}" == "mta" ]; then
 	update_mta.sh
 elif [ "${shortname}" == "jk2" ]; then
 	update_jk2.sh
+elif [ "${shortname}" == "jk3" ]; then
+	update_jk3.sh
 elif [ "${shortname}" == "vints" ]; then
 	update_vints.sh
 elif [ "${shortname}" == "bb" ]; then

+ 2 - 0
lgsm/modules/command_update.sh

@@ -35,6 +35,8 @@ elif [ "${shortname}" == "mta" ]; then
 	update_mta.sh
 elif [ "${shortname}" == "jk2" ]; then
 	update_jk2.sh
+elif [ "${shortname}" == "jk3" ]; then
+	update_jk3.sh
 elif [ "${shortname}" == "vints" ]; then
 	update_vints.sh
 elif [ "${shortname}" == "bb" ]; then

+ 1 - 1
lgsm/modules/core_getopt.sh

@@ -68,7 +68,7 @@ currentopt=("${cmd_start[@]}" "${cmd_stop[@]}" "${cmd_restart[@]}" "${cmd_monito
 currentopt+=("${cmd_update_linuxgsm[@]}")
 
 # Exclude noupdate games here.
-if [ "${shortname}" == "jk2" ] || [ "${shortname}" == "etl" ] || [ "${engine}" != "idtech3" ]; then
+if [ "${shortname}" == "jk2" ] || [ "${shortname}" == "jk3" ] || [ "${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
 		currentopt+=("${cmd_update[@]}" "${cmd_check_update[@]}")
 		# force update for SteamCMD, Multi Theft Auto or Hytale only.

+ 5 - 0
lgsm/modules/core_modules.sh

@@ -722,6 +722,11 @@ update_jk2.sh() {
 	fn_fetch_module
 }
 
+update_jk3.sh() {
+	modulefile="${FUNCNAME[0]}"
+	fn_fetch_module
+}
+
 update_mc.sh() {
 	modulefile="${FUNCNAME[0]}"
 	fn_fetch_module

+ 23 - 0
lgsm/modules/info_game.sh

@@ -1467,6 +1467,27 @@ fn_info_game_jk2() {
 	serverversion="${serverversion:-"NOT SET"}"
 }
 
+# Config Type: QuakeC
+# Parameters: true
+# Comment: // or /* */
+# Example: set sv_hostname "SERVERNAME"
+# Filetype: cfg
+fn_info_game_jk3() {
+	if [ -f "${servercfgfullpath}" ]; then
+		fn_info_game_quakec "maxplayers" "sv_maxclients"
+		fn_info_game_quakec "rconpassword" "rconpassword"
+		fn_info_game_quakec "servername" "sv_hostname"
+		fn_info_game_quakec "serverpassword" "g_password"
+	fi
+	defaultmap="${defaultmap:-"NOT SET"}"
+	maxplayers="${maxplayers:-"0"}"
+	port="${port:-"0"}"
+	queryport="${port}"
+	rconpassword="${rconpassword:-"NOT SET"}"
+	servername="${servername:-"NOT SET"}"
+	serverpassword="${serverpassword:-"NOT SET"}"
+}
+
 # Config Type: Java properties
 # Comment: # or !
 # Example: motd=SERVERNAME
@@ -2452,6 +2473,8 @@ elif [ "${shortname}" == "jc3" ]; then
 	fn_info_game_jc3
 elif [ "${shortname}" == "jk2" ]; then
 	fn_info_game_jk2
+elif [ "${shortname}" == "jk3" ]; then
+	fn_info_game_jk3
 elif [ "${shortname}" == "kf" ]; then
 	fn_info_game_kf
 elif [ "${shortname}" == "kf2" ]; then

+ 9 - 0
lgsm/modules/info_messages.sh

@@ -1185,6 +1185,13 @@ fn_info_messages_jk2() {
 	} | column -s $'\t' -t
 }
 
+fn_info_messages_jk3() {
+	{
+		fn_port "header"
+		fn_port "Game" port udp
+	} | column -s $'\t' -t
+}
+
 fn_info_messages_kf() {
 	{
 		fn_port "header"
@@ -1909,6 +1916,8 @@ fn_info_messages_select_engine() {
 		fn_info_messages_jc3
 	elif [ "${shortname}" == "jk2" ]; then
 		fn_info_messages_jk2
+	elif [ "${shortname}" == "jk3" ]; then
+		fn_info_messages_jk3
 	elif [ "${shortname}" == "kf" ]; then
 		fn_info_messages_kf
 	elif [ "${shortname}" == "kf2" ]; then

+ 5 - 0
lgsm/modules/install_config.sh

@@ -661,6 +661,11 @@ elif [ "${shortname}" == "jk2" ]; then
 	array_configs+=(server.cfg)
 	fn_default_config_remote
 	fn_set_config_vars
+elif [ "${shortname}" == "jk3" ]; then
+	array_configs+=(server.cfg)
+	fn_default_config_remote
+	fn_set_config_vars
+	fn_list_config_locations
 elif [ "${shortname}" == "qw" ]; then
 	array_configs+=(server.cfg)
 	fn_default_config_remote

+ 2 - 0
lgsm/modules/install_server_files.sh

@@ -261,6 +261,8 @@ elif [ "${shortname}" == "hyt" ]; then
 	update_hyt.sh
 elif [ "${shortname}" == "jk2" ]; then
 	update_jk2.sh
+elif [ "${shortname}" == "jk3" ]; then
+	update_jk3.sh
 elif [ "${shortname}" == "vints" ]; then
 	update_vints.sh
 elif [ "${shortname}" == "bb" ]; then

+ 175 - 0
lgsm/modules/update_jk3.sh

@@ -0,0 +1,175 @@
+#!/bin/bash
+# LinuxGSM update_jk3.sh module
+# Author: Daniel Gibbs
+# Contributors: https://linuxgsm.com/contrib
+# Website: https://linuxgsm.com
+# Description: Handles updating of Jedi Academy OpenJK servers.
+
+# shellcheck disable=SC2034 # Used by the module loader.
+moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
+
+fn_update_dl() {
+	# Download and extract files to GameData.
+	fn_fetch_file "${remotebuildurl}" "" "" "" "${tmpdir}" "${remotebuildfilename}" "nochmodx" "norun" "forcedl" "${remotebuildhash:-nohash}"
+	# OpenJK archives already have the executable and base/ at their root.
+	fn_dl_extract "${tmpdir}" "${remotebuildfilename}" "${systemdir}"
+	if [ ! -x "${executabledir}/openjkded.x86_64" ] || [ ! -f "${systemdir}/base/jampgamex86_64.so" ]; then
+		fn_print_fail_nl "OpenJK server files are missing after extraction"
+		fn_script_log_fail "OpenJK server files are missing after extraction"
+		# shellcheck disable=SC2034 # Used by core_exit.sh.
+		exitcode=1
+		core_exit.sh
+	fi
+	if ! echo "${remotebuild}" > "${systemdir}/openjk-build.txt"; then
+		fn_print_fail_nl "Unable to save the OpenJK build ID"
+		fn_script_log_fail "Unable to save the OpenJK build ID"
+		# shellcheck disable=SC2034 # Used by core_exit.sh.
+		exitcode=1
+		core_exit.sh
+	fi
+	fn_clear_tmp
+}
+
+fn_update_localbuild() {
+	# Gets local build info.
+	fn_print_dots "Checking local build: ${remotelocation}"
+	# Uses build file to get local build.
+	localbuild=$(head -n 1 "${systemdir}/openjk-build.txt" 2> /dev/null)
+	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"
+	fi
+}
+
+fn_update_remotebuild() {
+	# Gets remote build info.
+	apiurl="https://api.github.com/repos/JACoders/OpenJK/releases/latest"
+	remotebuildfilename="OpenJK-linux-x86_64.tar.gz"
+	remotebuildresponse=$(curl --connect-timeout 10 --max-time 30 --retry 2 --fail --silent --show-error --location "${apiurl}") || remotebuildresponse=""
+	# Select exactly one dedicated Linux x86_64 asset.
+	remotebuildasset=$(echo "${remotebuildresponse}" | jq -er --arg filename "${remotebuildfilename}" '[.assets[]? | select(.name == $filename)] | select(length == 1) | .[0] | select((.id | type) == "number" and (.browser_download_url | type) == "string")') || remotebuildasset=""
+	remotebuildurl=$(echo "${remotebuildasset}" | jq -r '.browser_download_url')
+	# The rolling latest tag is reused; replacing its asset changes the asset ID.
+	remotebuild=$(echo "${remotebuildasset}" | jq -r '.id')
+	remotebuildhash=$(echo "${remotebuildasset}" | jq -r '.digest // empty' | sed -n 's/^sha256://p')
+
+	if [ "${firstcommandname}" != "INSTALL" ]; then
+		fn_print_dots "Checking remote build: ${remotelocation}"
+	fi
+	# Checks if the remote build and download URL have been set.
+	if [ -z "${remotebuild}" ] || [ "${remotebuild}" == "null" ] || [ -z "${remotebuildurl}" ] || [ "${remotebuildurl}" == "null" ]; then
+		if [ "${firstcommandname}" != "INSTALL" ]; then
+			fn_print_fail "Checking remote build: ${remotelocation}"
+			fn_script_log_fail "Checking remote build"
+		else
+			fn_print_fail_nl "Unable to get remote build"
+			fn_script_log_fail "Unable to get remote build"
+		fi
+		# shellcheck disable=SC2034 # Used by core_exit.sh.
+		exitcode=1
+		core_exit.sh
+	elif [ "${firstcommandname}" != "INSTALL" ]; then
+		fn_print_ok "Checking remote build: ${remotelocation}"
+		fn_script_log_pass "Checking remote build"
+	fi
+}
+
+fn_update_compare() {
+	fn_print_dots "Checking for update: ${remotelocation}"
+	# Update has been found or force update.
+	if [ "${localbuild}" != "${remotebuild}" ] || [ "${forceupdate}" == "1" ]; then
+		# Create update lockfile.
+		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}"
+		if [ -n "${branch}" ]; then
+			fn_print_nl "* Branch: ${branch}"
+		fi
+		if [ -f "${rootdir}/.dev-debug" ]; then
+			fn_print_nl "Remote build info"
+			fn_print_nl "* apiurl: ${apiurl}"
+			fn_print_nl "* remotebuildfilename: ${remotebuildfilename}"
+			fn_print_nl "* remotebuildurl: ${remotebuildurl}"
+			fn_print_nl "* remotebuild: ${remotebuild}"
+		fi
+		fn_print "\n"
+		fn_script_log_info "Update available"
+		fn_script_log_info "Local build: ${localbuild}"
+		fn_script_log_info "Remote build: ${remotebuild}"
+		if [ -n "${branch}" ]; then
+			fn_script_log_info "Branch: ${branch}"
+		fi
+		fn_script_log_info "${localbuild} > ${remotebuild}"
+
+		if [ "${commandname}" == "UPDATE" ]; then
+			unset updateonstart
+			check_status.sh
+			# If server stopped.
+			if [ "${status}" == "0" ]; then
+				fn_update_dl
+			# If server started.
+			else
+				fn_print_restart_warning
+				exitbypass=1
+				command_stop.sh
+				fn_firstcommand_reset
+				fn_update_dl
+				exitbypass=1
+				command_start.sh
+				fn_firstcommand_reset
+			fi
+			unset exitbypass
+			date +%s > "${lockdir:?}/last-updated.lock"
+			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}"
+		if [ -n "${branch}" ]; then
+			fn_print_nl "* Branch: ${branch}"
+		fi
+		fn_print "\n"
+		fn_script_log_info "No update available"
+		fn_script_log_info "Local build: ${localbuild}"
+		fn_script_log_info "Remote build: ${remotebuild}"
+		if [ -n "${branch}" ]; then
+			fn_script_log_info "Branch: ${branch}"
+		fi
+		if [ -f "${rootdir}/.dev-debug" ]; then
+			fn_print_nl "Remote build info"
+			fn_print_nl "* apiurl: ${apiurl}"
+			fn_print_nl "* remotebuildfilename: ${remotebuildfilename}"
+			fn_print_nl "* remotebuildurl: ${remotebuildurl}"
+			fn_print_nl "* remotebuild: ${remotebuild}"
+		fi
+	fi
+}
+
+# The location where the builds are checked and downloaded.
+remotelocation="github.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