lgsm local mirror
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

#!/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