4
0
Эх сурвалжийг харах

feat(luanti): add Luanti server support (#5101)

Adds Luanti (formerly Minetest) as luantiserver, with installation, engine updates, details, session monitoring and graceful shutdown. Fresh installs include Minetest Game and a separate instance config; engine updates preserve games, mods, worlds and instance settings.

Uses the portable headless builds from rollerozxa/luantiserver, as recommended by the official Luanti Linux server documentation, selecting x86_64 or aarch64 assets and verifying SHA256 checksums. Adds dependency entries and the upstream game icon, with its attribution in a new Third-party assets section of LICENSE.md.

Fixes #2303
Daniel Gibbs 4 өдөр өмнө
parent
commit
0592b98f21

+ 6 - 0
LICENSE.md

@@ -19,3 +19,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
+
+## Third-party assets
+
+The following files are not covered by the MIT License above and remain under their own licenses.
+
+- `lgsm/data/gameicons/luanti-icon.png`: Luanti icon by erle, licensed under [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/). Copied without modification from [Luanti 5.17.0](https://github.com/luanti-org/luanti/blob/5.17.0/misc/luanti-icon-24x24.png); see the [upstream license and attribution](https://github.com/luanti-org/luanti/blob/5.17.0/LICENSE.txt).

+ 205 - 0
lgsm/config-default/config-lgsm/luantiserver/_default.cfg

@@ -0,0 +1,205 @@
+##################################
+######## 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
+ip="0.0.0.0"
+port="30000"
+gameid="minetest"
+worldname="world"
+
+## Server Parameters | https://docs.linuxgsm.com/configuration/start-parameters#additional-parameters
+startparameters="--config ${servercfgfullpath} --world ${serverfiles}/worlds/${worldname} --gameid ${gameid} --port ${port} --logfile ${gamelogdir}/${selfname}-game.log --terminal"
+
+## Extra Parameters | https://docs.linuxgsm.com/configuration/start-parameters#extra-parameters
+# Added to the end of startparameters.
+extraparams=""
+
+#### 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"
+
+## 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="2"
+
+## 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="Luanti"
+engine="luanti"
+glibc="2.31"
+
+#### Directories ####
+# Edit with care
+
+## Game Server Directories
+systemdir="${serverfiles}"
+executabledir="${serverfiles}/bin"
+executable="./luantiserver"
+servercfgdir="${systemdir}"
+servercfg="${selfname}.cfg"
+servercfgdefault="minetest.conf.example"
+servercfgfullpath="${servercfgdir}/${servercfg}"
+
+## Backup Directory
+backupdir="${lgsmdir}/backup"
+
+## Logging Directories
+[ -n "${LGSM_LOGDIR}" ] && logdir="${LGSM_LOGDIR}" || logdir="${rootdir}/log"
+gamelogdir="${logdir}/server"
+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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-21-openjdk
 mcb,libnsl
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-25-openjdk
 mcb,libnsl
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-21-openjdk
 mcb,libnsl
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-21-openjdk
 mcb,libnsl
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-25-openjdk
 mcb,libnsl
 mcv

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

@@ -70,6 +70,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-11-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-17-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-17-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4t64,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-25-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-8-jre
 mcb
 mcv

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


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

@@ -9,6 +9,7 @@ armar,libcurl,openssl11-libs
 ats,libatomic
 av
 bb
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 pw
 bb2,libcurl.i686
 bd

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-21-openjdk
 mcb,libnsl
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-25-openjdk
 mcb,libnsl
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-21-openjdk
 mcb,libnsl
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl,libstdc++,libzstd,ncurses-libs,sqlite-libs,zlib
 mc,java-25-openjdk
 mcb,libnsl
 mcv

+ 1 - 0
lgsm/data/serverlist.csv

@@ -70,6 +70,7 @@ kf,kfserver,Killing Floor,ubuntu-26.04
 kf2,kf2server,Killing Floor 2,ubuntu-26.04
 l4d,l4dserver,Left 4 Dead,ubuntu-26.04
 l4d2,l4d2server,Left 4 Dead 2,ubuntu-26.04
+luanti,luantiserver,Luanti,ubuntu-26.04
 mc,mcserver,Minecraft,ubuntu-26.04
 mcb,mcbserver,Minecraft Bedrock,ubuntu-26.04
 mcv,mcvserver,Military Conflict: Vietnam,ubuntu-26.04

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

@@ -9,6 +9,7 @@ armar,libcurl4
 ats,libatomic1
 av
 bb
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 pw
 bb2,libcurl4-gnutls-dev:i386
 bd

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

@@ -8,6 +8,7 @@ arma3
 armar,libcurl4
 ats,libatomic1
 av
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 pw
 bb
 bb2,libcurl4-gnutls-dev:i386

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-21-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-25-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-21-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-21-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4t64,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-25-jre
 mcb
 mcv

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

@@ -71,6 +71,7 @@ kf
 kf2
 l4d
 l4d2
+luanti,libcurl4t64,libncursesw6,libsqlite3-0,libstdc++6,libzstd1,zlib1g
 mc,openjdk-25-jre
 mcb
 mcv

+ 2 - 0
lgsm/modules/command_check_update.sh

@@ -32,6 +32,8 @@ elif [ "${shortname}" == "jk2" ]; then
 	update_jk2.sh
 elif [ "${shortname}" == "jk3" ]; then
 	update_jk3.sh
+elif [ "${shortname}" == "luanti" ]; then
+	update_luanti.sh
 elif [ "${shortname}" == "orar" ]; then
 	update_openra.sh
 elif [ "${shortname}" == "vints" ]; then

+ 2 - 0
lgsm/modules/command_update.sh

@@ -73,6 +73,8 @@ elif [ "${shortname}" == "jk2" ]; then
 	update_jk2.sh
 elif [ "${shortname}" == "jk3" ]; then
 	update_jk3.sh
+elif [ "${shortname}" == "luanti" ]; then
+	update_luanti.sh
 elif [ "${shortname}" == "orar" ]; then
 	update_openra.sh
 elif [ "${shortname}" == "vints" ]; then

+ 5 - 0
lgsm/modules/core_modules.sh

@@ -792,6 +792,11 @@ update_bb.sh() {
 	fn_fetch_module
 }
 
+update_luanti.sh() {
+	modulefile="${FUNCNAME[0]}"
+	fn_fetch_module
+}
+
 update_vints.sh() {
 	modulefile="${FUNCNAME[0]}"
 	fn_fetch_module

+ 18 - 0
lgsm/modules/info_game.sh

@@ -2292,6 +2292,22 @@ fn_info_game_vh() {
 	servername="${servername:-"NOT SET"}"
 }
 
+# Config Type: Key Value Pairs (Equals Delimited)
+# Comment: #
+fn_info_game_luanti() {
+	if [ -f "${servercfgfullpath}" ]; then
+		fn_info_game_keyvalue_pairs_equals "servername" "server_name"
+		fn_info_game_keyvalue_pairs_equals "serverpassword" "default_password"
+		fn_info_game_keyvalue_pairs_equals "maxplayers" "max_users"
+		fn_info_game_keyvalue_pairs_equals "configip" "bind_address"
+	fi
+	servername="${servername:-"NOT SET"}"
+	serverpassword="${serverpassword:-"NOT SET"}"
+	maxplayers="${maxplayers:-"20"}"
+	configip="${configip:-"0.0.0.0"}"
+	queryport="${port}"
+}
+
 # Parameters: true
 fn_info_game_openra() {
 	port="${port:-"1234"}"
@@ -2616,6 +2632,8 @@ elif [ "${shortname}" == "ut99" ]; then
 	fn_info_game_ut99
 elif [ "${shortname}" == "vh" ]; then
 	fn_info_game_vh
+elif [ "${shortname}" == "luanti" ]; then
+	fn_info_game_luanti
 elif [ "${shortname}" == "orar" ]; then
 	fn_info_game_openra
 elif [ "${shortname}" == "vints" ]; then

+ 10 - 1
lgsm/modules/info_messages.sh

@@ -747,7 +747,7 @@ fn_info_messages_ports_edit() {
 		fi
 	done
 	# engines/games that require editing the start parameters.
-	local ports_edit_array=("av" "ce" "ck" "col" "cs2" "fctr" "goldsrc" "hcu" "hw" "hyt" "iw3.0" "ioquake3" "openra" "pw" "qfusion" "rsdw" "rust" "scpsl" "sf" "sol" "spark" "source" "unreal4" "arma3" "dayz" "unt" "vh")
+	local ports_edit_array=("av" "ce" "ck" "col" "cs2" "fctr" "goldsrc" "hcu" "hw" "hyt" "luanti" "iw3.0" "ioquake3" "openra" "pw" "qfusion" "rsdw" "rust" "scpsl" "sf" "sol" "spark" "source" "unreal4" "arma3" "dayz" "unt" "vh")
 	for port_edit in "${ports_edit_array[@]}"; do
 		if [ "${engine}" == "${port_edit}" ] || [ "${gamename}" == "${port_edit}" ] || [ "${shortname}" == "${port_edit}" ]; then
 			startparameterslocation="${configdirserver}"
@@ -1828,6 +1828,13 @@ fn_info_messages_vh() {
 	} | column -s $'\t' -t
 }
 
+fn_info_messages_luanti() {
+	{
+		fn_port "header"
+		fn_port "Game" port udp
+	} | column -s $'\t' -t
+}
+
 fn_info_messages_openra() {
 	{
 		fn_port "header"
@@ -2051,6 +2058,8 @@ fn_info_messages_select_engine() {
 		fn_info_messages_unt
 	elif [ "${shortname}" == "vh" ]; then
 		fn_info_messages_vh
+	elif [ "${shortname}" == "luanti" ]; then
+		fn_info_messages_luanti
 	elif [ "${shortname}" == "orar" ]; then
 		fn_info_messages_openra
 	elif [ "${shortname}" == "vints" ]; then

+ 14 - 1
lgsm/modules/install_config.sh

@@ -256,7 +256,20 @@ fn_list_config_locations() {
 	echo -e "Config documentation: ${italic}https://docs.linuxgsm.com/configuration${default}"
 }
 
-if [ "${shortname}" == "orar" ]; then
+if [ "${shortname}" == "luanti" ]; then
+	fn_check_cfgdir
+	if [ ! -e "${servercfgfullpath}" ] && [ ! -L "${servercfgfullpath}" ]; then
+		cp "${serverfiles}/${servercfgdefault}" "${servercfgfullpath}"
+		{
+			echo "server_name = LinuxGSM"
+			echo "name = admin"
+			echo "server_announce = false"
+			echo "bind_address = ${ip}"
+			echo "max_users = 20"
+		} >> "${servercfgfullpath}"
+	fi
+	fn_list_config_locations
+elif [ "${shortname}" == "orar" ]; then
 	fn_check_cfgdir
 	fn_list_config_locations
 elif [ "${shortname}" == "sdtd" ]; then

+ 2 - 0
lgsm/modules/install_server_files.sh

@@ -279,6 +279,8 @@ elif [ "${shortname}" == "jk2" ]; then
 	update_jk2.sh
 elif [ "${shortname}" == "jk3" ]; then
 	update_jk3.sh
+elif [ "${shortname}" == "luanti" ]; then
+	update_luanti.sh
 elif [ "${shortname}" == "orar" ]; then
 	update_openra.sh
 elif [ "${shortname}" == "vints" ]; then

+ 162 - 0
lgsm/modules/update_luanti.sh

@@ -0,0 +1,162 @@
+#!/bin/bash
+# LinuxGSM update_luanti.sh module
+# Author: Daniel Gibbs
+# Contributors: https://linuxgsm.com/contrib
+# Website: https://linuxgsm.com
+# Description: Handles updating of Luanti servers.
+
+moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
+
+fn_update_dl() {
+	fn_fetch_file "${remotebuildurl}" "" "" "" "${tmpdir}" "${remotebuildfilename}" "nochmodx" "norun" "force" "${remotebuildhash}"
+	fn_dl_extract "${tmpdir}" "${remotebuildfilename}" "${serverfiles}" "luanti"
+	if [ "${firstcommandname}" == "INSTALL" ] && [ ! -d "${serverfiles}/games/minetest" ]; then
+		fn_fetch_file "https://github.com/luanti-org/minetest_game/archive/refs/tags/5.8.0.tar.gz" "" "" "" "${tmpdir}" "minetest_game-5.8.0.tar.gz" "nochmodx" "norun" "force" "33a3bb43b08497a0bdb2f49f140a2829e582d5c16c0ad52be1595c803f706912"
+		mkdir -p "${serverfiles}/games/minetest"
+		fn_dl_extract "${tmpdir}" "minetest_game-5.8.0.tar.gz" "${serverfiles}/games/minetest" "minetest_game-5.8.0"
+	fi
+	fn_clear_tmp
+}
+
+fn_update_localbuild() {
+	local mode="${1:-}"
+	if [ "${mode}" != "silent" ]; then
+		fn_print_dots "Checking local build: ${remotelocation}"
+	fi
+	localbuild=$("${executabledir}/${executable#./}" --version 2> /dev/null | awk '/^Luanti / {print $2; exit}')
+	localbuild="${localbuild:-0}"
+	if [ "${mode}" != "silent" ]; then
+		fn_print_ok "Checking local build: ${remotelocation}"
+	fi
+}
+
+fn_update_remotebuild() {
+	case "$(uname -m)" in
+		x86_64) luantiarch="x86_64" ;;
+		aarch64) luantiarch="aarch64" ;;
+		*)
+			fn_print_fail_nl "Unsupported Luanti architecture: $(uname -m)"
+			fn_script_log_fail "Unsupported Luanti architecture: $(uname -m)"
+			core_exit.sh
+			;;
+	esac
+	apiurl="https://api.github.com/repos/rollerozxa/luantiserver/releases/latest"
+	remotebuildresponse=$(curl -fsSL --retry 3 "${apiurl}")
+	remotebuild=$(echo "${remotebuildresponse}" | jq -r '.tag_name // empty')
+	remotebuildfilename="luantiserver-${remotebuild}-${luantiarch}.tar.gz"
+	remotebuildurl=$(echo "${remotebuildresponse}" | jq -r --arg name "${remotebuildfilename}" '.assets[]? | select(.name == $name) | .browser_download_url')
+	remotebuildhash=$(echo "${remotebuildresponse}" | jq -r --arg name "${remotebuildfilename}" '.assets[]? | select(.name == $name) | .digest // empty' | sed 's/^sha256://')
+	if [ -z "${remotebuild}" ] || [ -z "${remotebuildurl}" ] || [[ ! "${remotebuildhash}" =~ ^[0-9a-f]{64}$ ]]; then
+		fn_print_fail_nl "Unable to get Luanti release, download URL or SHA256 checksum"
+		fn_script_log_fail "Unable to get Luanti release, download URL or SHA256 checksum"
+		core_exit.sh
+	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
+			# Keep track of the build before updating for alert purposes.
+			previousbuild="${localbuild}"
+			unset updateonstart
+			fn_update_apply fn_update_dl
+			if [ "${updatestatus}" == "0" ] && [ "${localbuild}" == "0" ]; then
+				exitbypass=1
+				command_start.sh
+				fn_firstcommand_reset
+				exitbypass=1
+				fn_sleep_time_5
+				command_stop.sh
+				fn_firstcommand_reset
+			fi
+			# Refresh local build value after update (silent) so alerts show new version.
+			fn_update_localbuild silent
+			# Verify the update applied correctly unless forced update to same version.
+			if [ "${localbuild}" != "${remotebuild}" ]; then
+				# If forced update and version unchanged treat as acceptable; otherwise failure.
+				if [ "${forceupdate}" != "1" ] || [ "${previousbuild}" = "${localbuild}" ]; then
+					fn_script_log_error "Update verification failed: expected ${remotebuild}, got ${localbuild}"
+					fn_print_fail_nl "Update verification failed: expected ${remotebuild}, got ${localbuild}"
+					updatefailureexpected="${remotebuild}"
+					updatefailuregot="${localbuild}"
+					alert="update-failed"
+					alert.sh
+					core_exit.sh
+				fi
+			else
+				fn_script_log_pass "Update verification success: ${previousbuild} -> ${localbuild}"
+			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}"
+		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/rollerozxa/luantiserver"
+
+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