gameservergame-servergame-servershacktoberfestdedicated-game-serversgamelinuxgsmserverbashgaminglinuxmultiplayer-game-servershell
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
9.3 KiB
283 lines
9.3 KiB
#!/bin/bash
|
|
# LinuxGSM command_monitor.sh module
|
|
# Author: Daniel Gibbs
|
|
# Contributors: http://linuxgsm.com/contrib
|
|
# Website: https://linuxgsm.com
|
|
# Description: Monitors server by checking for running processes
|
|
# then passes to gamedig and gsquery.
|
|
|
|
commandname="MONITOR"
|
|
commandaction="Monitoring"
|
|
functionselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
|
|
fn_firstcommand_set
|
|
|
|
fn__restart_server() {
|
|
if [ -f "${lockdir}/${selfname}.lock" ]; then
|
|
alert="${1:?}}"
|
|
alert.sh
|
|
(
|
|
fn_print_info_nl "Restarting the server and skip immediate exit"
|
|
exitbypass=1
|
|
command_stop.sh
|
|
command_start.sh
|
|
echo "" # start doesn't always print newline
|
|
)
|
|
fn_firstcommand_reset
|
|
else
|
|
fn_print_warn_nl "Skipping server restart because lockfile is missing, probably server is restarted during monitor execution."
|
|
fi
|
|
}
|
|
|
|
fn_monitor_check_lockfile() {
|
|
fn_print_dots "Checking lockfile"
|
|
|
|
# Monitor does not run if lockfile is not found.
|
|
if [ ! -f "${lockdir}/${selfname}.lock" ]; then
|
|
fn_print_fail_nl "Checking lockfile: No lockfile found"
|
|
exitcode="3"
|
|
core_exit.sh
|
|
|
|
# Fix if lockfile is not unix time or contains letters
|
|
elif [[ "$(head -n 1 "${lockdir}/${selfname}.lock")" =~ [A-Za-z] ]]; then
|
|
fn_print_warn_nl "Checking lockfile: fixing illegal lockfile"
|
|
date '+%s' > "${lockdir}/${selfname}.lock"
|
|
echo "${version}" >> "${lockdir}/${selfname}.lock"
|
|
echo "${port}" >> "${lockdir}/${selfname}.lock"
|
|
else
|
|
fn_print_ok_nl "Checking lockfile"
|
|
fi
|
|
}
|
|
|
|
fn_monitor_check_update() {
|
|
fn_print_dots "Checking active updates"
|
|
|
|
# Monitor will check if update is already running.
|
|
if [ "$(pgrep "${selfname} update" | wc -l)" != "0" ]; then
|
|
fn_print_fail_nl "SteamCMD is currently checking for updates"
|
|
exitcode="2"
|
|
core_exit.sh
|
|
else
|
|
fn_print_ok_nl "Checking active updates"
|
|
fi
|
|
}
|
|
|
|
fn_monitor_is_server_running() {
|
|
fn_print_dots "Checking session"
|
|
|
|
# uses status var from check_status.sh
|
|
if [ "${status}" != "0" ]; then
|
|
fn_print_ok_nl "Checking session"
|
|
return 0
|
|
else
|
|
fn_print_error_nl "Checking session"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
fn_monitor_check_queryport() {
|
|
# Monitor will check queryport is set before continuing.
|
|
if [ -z "${queryport}" ] || [ "${queryport}" == "0" ]; then
|
|
fn_print_dots "Checking port: "
|
|
fn_print_checking_eol
|
|
fn_script_log_info "Checking port: CHECKING"
|
|
if [ -n "${rconenabled}" ] && [ "${rconenabled}" != "true" ] && [ "${shortname}" == "av" ]; then
|
|
fn_print_warn "Checking port: Unable to query, rcon is not enabled"
|
|
fn_script_log_warn "Checking port: Unable to query, rcon is not enabled"
|
|
else
|
|
fn_print_error "Checking port: Unable to query, queryport is not set"
|
|
fn_script_log_error "Checking port: Unable to query, queryport is not set"
|
|
fi
|
|
core_exit.sh
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
fn_query_gsquery() {
|
|
if [ ! -f "${functionsdir}/query_gsquery.py" ]; then
|
|
fn_fetch_file_github "lgsm/functions" "query_gsquery.py" "${functionsdir}" "chmodx" "norun" "noforce" "nohash"
|
|
fi
|
|
"${functionsdir}"/query_gsquery.py -a "${queryip}" -p "${queryport}" -e "${querytype}" > /dev/null 2>&1
|
|
querystatus="$?"
|
|
}
|
|
|
|
fn_query_tcp() {
|
|
bash -c "exec 3<> '/dev/tcp/${queryip}/${queryport}'" > /dev/null 2>&1
|
|
querystatus="$?"
|
|
}
|
|
|
|
fn_monitor_query() {
|
|
local fail_after="60" # seconds
|
|
local time_per_attempt="3"
|
|
local max_attempts="5"
|
|
local wait_between_attempts="$(( (fail_after-max_attempts*time_per_attempt) / (max_attempts-1) ))"
|
|
|
|
# Will loop and query up to 5 times every 15 seconds.
|
|
# Query will wait up to 60 seconds to confirm server is down as server can become non-responsive during map changes.
|
|
for queryattempt in $(seq 1 "${max_attempts}" ); do
|
|
|
|
for queryip in "${queryips[@]}"; do
|
|
local log_msg="Starting to query in mode \"${querymethod}\" to target \"${queryip}:${queryport}\" attempt ${queryattempt} / ${max_attempts}"
|
|
fn_print_dots "${log_msg}"
|
|
|
|
# will use query method selected in fn_monitor_loop
|
|
querystatus="100"
|
|
if [ "${querymethod}" == "gamedig" ]; then
|
|
query_gamedig.sh
|
|
|
|
elif [ "${querymethod}" == "gsquery" ]; then
|
|
fn_query_gsquery
|
|
|
|
elif [ "${querymethod}" == "tcp" ]; then
|
|
fn_query_tcp
|
|
|
|
else
|
|
fn_print_fail_nl "${log_msg} reason: unhandled query method \"${querymethod}\""
|
|
fi
|
|
|
|
# if serverquery is fine
|
|
if [ "${querystatus}" == "0" ]; then
|
|
fn_print_ok_nl "${log_msg}"
|
|
|
|
# Add query data to log.
|
|
if [ "${gdname}" ]; then
|
|
fn_script_log_info "Server name: ${gdname}"
|
|
fi
|
|
if [ "${gdplayers}" ]; then
|
|
fn_script_log_info "Players: ${gdplayers}/${gdmaxplayers}"
|
|
fi
|
|
if [ "${gdbots}" ]; then
|
|
fn_script_log_info "Bots: ${gdbots}"
|
|
fi
|
|
if [ "${gdmap}" ]; then
|
|
fn_script_log_info "Map: ${gdmap}"
|
|
fi
|
|
if [ "${gdgamemode}" ]; then
|
|
fn_script_log_info "Game Mode: ${gdgamemode}"
|
|
fi
|
|
|
|
# send LinuxGSM stats if monitor is OK.
|
|
if [ "${stats}" == "on" ]||[ "${stats}" == "y" ]; then
|
|
info_stats.sh
|
|
fi
|
|
|
|
return 0
|
|
else
|
|
fn_print_warn_nl "${log_msg} querystatus=\"${querystatus}\""
|
|
fi
|
|
done
|
|
|
|
# monitoring attempt failed, show details to resolve the issue:
|
|
if ! ss -tuplwn | grep -qFe ":${queryport} "; then
|
|
fn_print_warn_nl "Port is not in use right now \"${queryport}\". Check command details for ports, use provided command to check if every port is used + console to validate server is booted. Maybe server didn't boot, e.g. a second port which is needed is already in use by another application or the configuration for the queryport is incorrect."
|
|
else
|
|
# return value of following lines arent used so not an issue
|
|
#shellcheck disable=SC2155
|
|
local process_using_port="$( ss -tuplwn "( dport = :${queryport} or sport = :${queryport} )" | grep -o '[^ ]*$')"
|
|
#shellcheck disable=SC2155
|
|
local listen_on="$( ss -tuplwn "( dport = :${queryport} or sport = :${queryport} )" | grep -o "[^ ]*:${queryport} ")"
|
|
|
|
local msg="Found application \"${process_using_port}\" which listens on \"${listen_on}\""
|
|
if ! ss -tuplwn "( dport = :${queryport} or sport = :${queryport} )" | grep -qs '^[^ ]*\s*[^ ]*\s*0\s*'; then
|
|
fn_print_warn_nl "$msg but Recv-Q isn't empty. Server didn't read the message we send, e.g. server is booting, has an issue which prevents correct initialization or the port is in use by another program."
|
|
else
|
|
fn_print_info_nl "$msg and Recv-Q is empty, the application read our send message but didn't answer as expected. Maybe \"${queryport}\" is not the querypot or incorrect query method (e.g. gamedig protocol) used?"
|
|
fi
|
|
fi
|
|
|
|
# delay next init
|
|
if [ "${queryattempt}" != "${max_attempts}" ]; then
|
|
local explanation="e.g. maybe it failed because of server starting / map change / workshop download"
|
|
fn_print_info "delayed next attempt for ${wait_between_attempts}s, $explanation"
|
|
for i in $(seq 1 "${wait_between_attempts}"); do
|
|
sleep 1s
|
|
fn_print_info "delayed next attempt for $((wait_between_attempts - i))s, $explanation"
|
|
done
|
|
fn_print_info_nl "monitoring delayed for ${wait_between_attempts}s, $explanation"
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
fn_monitor_await_execution_time() {
|
|
# Add a querydelay of 1 min if var missing.
|
|
querydelay="${querydelay:-"1"}"
|
|
|
|
last_execution="$(head -n 1 "${lockdir}/${selfname}.lock")"
|
|
delay_seconds="$((querydelay * 60))"
|
|
next_allowed_execution="$((last_execution + delay_seconds))"
|
|
seconds_to_wait="$((next_allowed_execution - $(date '+%s')))"
|
|
|
|
if [ "${seconds_to_wait}" -gt "0" ]; then
|
|
fn_print_dots "monitoring delayed for ${seconds_to_wait}s"
|
|
for i in $(seq "${seconds_to_wait}" -1 1); do
|
|
sleep 1s
|
|
fn_print_info "monitoring delayed for ${i}s"
|
|
done
|
|
fn_print_info_nl "monitoring delayed for ${seconds_to_wait}s"
|
|
fi
|
|
}
|
|
|
|
fn_monitor_loop() {
|
|
is_gamedig_installed="$( command -v gamedig 2>/dev/null 1>&2 && command -v jq 2>/dev/null 1>&2 && echo true || echo false )"
|
|
|
|
# loop though query methods selected by querymode.
|
|
if [ "${querymode}" == "2" ]; then
|
|
local query_methods_array=(gamedig gsquery)
|
|
elif [ "${querymode}" == "3" ]; then
|
|
local query_methods_array=(gamedig)
|
|
elif [ "${querymode}" == "4" ]; then
|
|
local query_methods_array=( gsquery )
|
|
elif [ "${querymode}" == "5" ]; then
|
|
local query_methods_array=( tcp )
|
|
else
|
|
fn_print_fail_nl "monitoring function invoced but querymode has an illegal value ${querymode}"
|
|
return 1
|
|
fi
|
|
|
|
for querymethod in "${query_methods_array[@]}"; do
|
|
# Will check if gamedig is installed and bypass if not.
|
|
if [ "${querymethod}" == "gamedig" ] && ! "${is_gamedig_installed}"; then
|
|
fn_print_warn_nl "gamedig is not installed"
|
|
fn_print_warn_nl "https://docs.linuxgsm.com/requirements/gamedig"
|
|
elif fn_monitor_query; then
|
|
fn_print_complete_nl "monitoring successful"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
monitorflag=1
|
|
check.sh
|
|
core_logs.sh
|
|
info_game.sh
|
|
|
|
# query pre-checks
|
|
fn_monitor_await_execution_time
|
|
fn_monitor_check_lockfile
|
|
fn_monitor_check_update
|
|
check_only_if_running="$([ "${querymode}" == "1" ] && echo true || echo false )"
|
|
|
|
exitcode="1" # if not altered below, coding error => FATAL
|
|
if ! fn_monitor_is_server_running; then
|
|
fn__restart_server "restart"
|
|
exitcode="3"
|
|
|
|
# if monitor should only check only session
|
|
elif "${check_only_if_running}"; then
|
|
exitcode="0"
|
|
|
|
elif ! fn_monitor_is_queryport_valid; then
|
|
exitcode="2" # error because maybe unfixable
|
|
# no restart because config issue !
|
|
|
|
# server could be queried with tcp / gsquery / gamedig
|
|
elif fn_monitor_loop; then
|
|
exitcode="0"
|
|
|
|
else
|
|
fn__restart_server "restartquery"
|
|
exitcode="3"
|
|
fi
|
|
|
|
core_exit.sh
|
|
|