mirror of
https://github.com/GameServerManagers/LinuxGSM
synced 2026-08-18 20:23:04 -04:00
* add no stop if players online
* remove dev message
* monitor will now restart the server if requested earlier
* changed var name
* add npm check to install gamedig
* new line
* add var
* remove from non supported servers
* typo
* word
* move restart request
* change lock file name
* restart and monitor
* refactor(stop): share player-online check + add stoponlyifnoplayersallcommands
- Extract the duplicated gamedig player-count query (previously copy-pasted
in command_stop, command_restart and command_monitor) into a new shared
check_players_online.sh module that sets ${playersonline}; registered in
core_modules.sh.
- Add stoponlyifnoplayersallcommands (default on) so the stoponlyifnoplayers
protection applies to every command that stops the server (e.g. update).
Set to off to limit it to explicit stop/restart, allowing update to stop
the server even while players are online.
- Add the new setting to all game server _default.cfg files.
- Resolve merge with develop (check_gamedig now keeps develop's npm-update
branch plus the 'install Node.js' hint).
* fix(stop): clear stale restart-request lock + warn on failed player query
Addresses Copilot review feedback on #4595:
- command_restart.sh: clear the *-restart-request.lock once a restart
actually proceeds. Previously the lock was only written when postponing
and never removed, so every subsequent monitor run would see the stale
lock and trigger a repeat restart indefinitely. Fixed and verified against
a live server: postponed restart -> monitor executes it and clears the
lock -> a second monitor run no longer restarts.
- check_players_online.sh: if the gamedig query fails for every configured
IP (e.g. gamedig/jq missing), playersonline now still fails open (so a
broken query can't permanently block stop/restart) but prints a clear
warning that the safety check could not run, instead of silently
behaving as if the server were empty.
427 lines
14 KiB
Bash
Executable file
427 lines
14 KiB
Bash
Executable file
#!/bin/bash
|
|
# LinuxGSM command_stop.sh module
|
|
# Author: Daniel Gibbs
|
|
# Contributors: https://linuxgsm.com/contrib
|
|
# Website: https://linuxgsm.com
|
|
# Description: Stops the server.
|
|
|
|
commandname="STOP"
|
|
commandaction="Stopping"
|
|
moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
|
|
fn_firstcommand_set
|
|
|
|
# Only stop the server if there are no players online.
|
|
# An explicit stop or restart always respects players when the feature is on.
|
|
# Other commands that stop the server as a side effect (e.g. update) only respect
|
|
# players when stoponlyifnoplayersallcommands is on.
|
|
fn_stop_players_online() {
|
|
if [ "${firstcommandname}" == "STOP" ] || [ "${firstcommandname}" == "RESTART" ] || [ "${stoponlyifnoplayersallcommands}" == "on" ]; then
|
|
check_players_online.sh
|
|
if [ -n "${playersonline}" ]; then
|
|
fn_print_info_nl "${playersonline} players are on the server: stop prevented"
|
|
fn_script_log_info "${playersonline} players are on the server: stop prevented"
|
|
echo "${playersonline}" > "${lockdir:?}/${selfname}-player-numbers.lock"
|
|
core_exit.sh
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Attempts graceful shutdown by sending 'CTRL+c'.
|
|
fn_stop_graceful_ctrlc() {
|
|
fn_print_dots "Graceful: CTRL+c"
|
|
fn_script_log_info "Graceful: CTRL+c"
|
|
# Sends CTRL+c.
|
|
TERM=screen tmux -L "${socketname}" send-keys -t "${sessionname}" C-c > /dev/null 2>&1
|
|
# Waits up to 30 seconds giving the server time to shutdown gracefully.
|
|
for seconds in {1..30}; do
|
|
check_status.sh
|
|
if [ "${status}" == "0" ]; then
|
|
fn_print_ok "Graceful: CTRL+c: ${seconds}"
|
|
fn_print_ok_eol_nl
|
|
fn_script_log_pass "Graceful: CTRL+c: OK: ${seconds} seconds"
|
|
if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
|
|
alert="stopped"
|
|
alert.sh
|
|
fi
|
|
break
|
|
fi
|
|
fn_sleep_time_1
|
|
fn_print_dots "Graceful: CTRL+c: ${seconds}"
|
|
done
|
|
check_status.sh
|
|
if [ "${status}" != "0" ]; then
|
|
fn_print_error "Graceful: CTRL+c: "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_error "Graceful: CTRL+c: FAIL"
|
|
fi
|
|
}
|
|
|
|
# Attempts graceful shutdown by sending a specified command.
|
|
# Usage: fn_stop_graceful_cmd "console_command" "timeout_in_seconds"
|
|
# e.g.: fn_stop_graceful_cmd "quit" "30"
|
|
fn_stop_graceful_cmd() {
|
|
fn_print_dots "Graceful: sending \"${1}\""
|
|
fn_script_log_info "Graceful: sending \"${1}\""
|
|
# Sends specific stop command.
|
|
TERM=screen tmux -L "${socketname}" send -t "${sessionname}" ENTER "${1}" ENTER > /dev/null 2>&1
|
|
# Waits up to ${seconds} seconds giving the server time to shutdown gracefully.
|
|
for ((seconds = 1; seconds <= ${2}; seconds++)); do
|
|
check_status.sh
|
|
if [ "${status}" == "0" ]; then
|
|
fn_print_ok "Graceful: sending \"${1}\": ${seconds}"
|
|
fn_print_ok_eol_nl
|
|
fn_script_log_pass "Graceful: sending \"${1}\": OK: ${seconds} seconds"
|
|
if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
|
|
alert="stopped"
|
|
alert.sh
|
|
fi
|
|
break
|
|
fi
|
|
fn_sleep_time_1
|
|
fn_print_dots "Graceful: sending \"${1}\": ${seconds}"
|
|
done
|
|
check_status.sh
|
|
if [ "${status}" != "0" ]; then
|
|
fn_print_error "Graceful: sending \"${1}\": "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_error "Graceful: sending \"${1}\": FAIL"
|
|
fi
|
|
}
|
|
|
|
# Attempts graceful shutdown of goldsrc using rcon 'quit' command.
|
|
# There is only a 3 second delay before a forced a tmux shutdown
|
|
# as GoldSrc servers 'quit' command does a restart rather than shutdown.
|
|
fn_stop_graceful_goldsrc() {
|
|
fn_print_dots "Graceful: sending \"quit\""
|
|
fn_script_log_info "Graceful: sending \"quit\""
|
|
# sends quit
|
|
TERM=screen tmux -L "${socketname}" send -t "${sessionname}" quit ENTER > /dev/null 2>&1
|
|
# Waits 3 seconds as goldsrc servers restart with the quit command.
|
|
for seconds in {1..3}; do
|
|
fn_sleep_time_1
|
|
fn_print_dots "Graceful: sending \"quit\": ${seconds}"
|
|
done
|
|
fn_print_ok "Graceful: sending \"quit\": ${seconds}"
|
|
fn_print_ok_eol_nl
|
|
fn_script_log_pass "Graceful: sending \"quit\": OK: ${seconds} seconds"
|
|
if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
|
|
alert="stopped"
|
|
alert.sh
|
|
fi
|
|
}
|
|
|
|
# telnet command for sdtd graceful shutdown.
|
|
fn_stop_graceful_sdtd_telnet() {
|
|
if [ -z "${telnetpassword}" ] || [ "${telnetpassword}" == "NOT SET" ]; then
|
|
sdtdtelnetshutdown=$(expect -c '
|
|
proc abort {} {
|
|
puts "Timeout or EOF\n"
|
|
exit 1
|
|
}
|
|
spawn telnet '"${telnetip}"' '"${telnetport}"'
|
|
expect {
|
|
"session." { send "shutdown\r" }
|
|
default abort
|
|
}
|
|
expect { eof }
|
|
puts "Completed.\n"
|
|
')
|
|
else
|
|
sdtdtelnetshutdown=$(expect -c '
|
|
proc abort {} {
|
|
puts "Timeout or EOF\n"
|
|
exit 1
|
|
}
|
|
spawn telnet '"${telnetip}"' '"${telnetport}"'
|
|
expect {
|
|
"password:" { send "'"${telnetpassword}"'\r" }
|
|
default abort
|
|
}
|
|
expect {
|
|
"session." { send "shutdown\r" }
|
|
default abort
|
|
}
|
|
expect { eof }
|
|
puts "Completed.\n"
|
|
')
|
|
fi
|
|
}
|
|
|
|
# Attempts graceful shutdown of 7 Days To Die using telnet.
|
|
fn_stop_graceful_sdtd() {
|
|
fn_print_dots "Graceful: telnet"
|
|
fn_script_log_info "Graceful: telnet"
|
|
if [ "${telnetenabled}" == "false" ]; then
|
|
fn_print_info_nl "Graceful: telnet: DISABLED: Enable in ${servercfg}"
|
|
elif [ "$(command -v expect 2> /dev/null)" ]; then
|
|
# Tries to shutdown with both localhost and server IP.
|
|
for telnetip in 127.0.0.1 ${ip}; do
|
|
fn_print_dots "Graceful: telnet: ${telnetip}:${telnetport}"
|
|
fn_script_log_info "Graceful: telnet: ${telnetip}:${telnetport}"
|
|
fn_stop_graceful_sdtd_telnet
|
|
completed=$(echo -en "\n ${sdtdtelnetshutdown}" | grep "Completed.")
|
|
refused=$(echo -en "\n ${sdtdtelnetshutdown}" | grep "Timeout or EOF")
|
|
if [ "${refused}" ]; then
|
|
fn_print_error "Graceful: telnet: ${telnetip}:${telnetport} : "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_error "Graceful: telnet: ${telnetip}:${telnetport} : FAIL"
|
|
elif [ "${completed}" ]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
# If telnet shutdown was successful will use telnet again to check
|
|
# the connection has closed, confirming that the tmux session can now be killed.
|
|
if [ "${completed}" ]; then
|
|
for seconds in {1..30}; do
|
|
fn_stop_graceful_sdtd_telnet
|
|
refused=$(echo -en "\n ${sdtdtelnetshutdown}" | grep "Timeout or EOF")
|
|
if [ "${refused}" ]; then
|
|
fn_print_ok "Graceful: telnet: ${telnetip}:${telnetport} : "
|
|
fn_print_ok_eol_nl
|
|
fn_script_log_pass "Graceful: telnet: ${telnetip}:${telnetport} : ${seconds} seconds"
|
|
if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
|
|
alert="stopped"
|
|
alert.sh
|
|
fi
|
|
break
|
|
fi
|
|
fn_sleep_time_1
|
|
fn_print_dots "Graceful: telnet: ${seconds}"
|
|
done
|
|
# If telnet shutdown fails tmux shutdown will be used, this risks loss of world save.
|
|
else
|
|
if [ "${refused}" ]; then
|
|
fn_print_error "Graceful: telnet: "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_error "Graceful: telnet: ${telnetip}:${telnetport} : FAIL"
|
|
else
|
|
fn_print_error_nl "Graceful: telnet: Unknown error"
|
|
fn_script_log_error "Graceful: telnet: Unknown error"
|
|
fi
|
|
echo -en "\n" | tee -a "${lgsmlog}"
|
|
echo -en "Telnet output:" | tee -a "${lgsmlog}"
|
|
echo -en "\n ${sdtdtelnetshutdown}" | tee -a "${lgsmlog}"
|
|
echo -en "\n\n" | tee -a "${lgsmlog}"
|
|
fi
|
|
else
|
|
fn_print_warn "Graceful: telnet: expect not installed: "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_warn "Graceful: telnet: expect not installed: FAIL"
|
|
fi
|
|
}
|
|
|
|
# Attempts graceful shutdown of Soulmask using telnet.
|
|
fn_stop_graceful_sm() {
|
|
fn_print_dots "Graceful: telnet"
|
|
fn_script_log_info "Graceful: telnet"
|
|
if [ "${telnetenabled}" == "false" ]; then
|
|
fn_print_info_nl "Graceful: telnet: DISABLED: Enable in ${servercfg}"
|
|
elif [ "$(command -v expect 2> /dev/null)" ]; then
|
|
# Tries to shutdown with both localhost and server IP.
|
|
for telnetip in 127.0.0.1 ${ip}; do
|
|
fn_print_dots "Graceful: telnet: ${telnetip}:${telnetport}"
|
|
fn_script_log_info "Graceful: telnet: ${telnetip}:${telnetport}"
|
|
fn_stop_graceful_sm_telnet
|
|
completed=$(echo -en "\n ${smtelnetshutdown}" | grep "Completed.")
|
|
refused=$(echo -en "\n ${smtelnetshutdown}" | grep "Timeout or EOF")
|
|
if [ "${refused}" ]; then
|
|
fn_print_error "Graceful: telnet: ${telnetip}:${telnetport} : "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_error "Graceful: telnet: ${telnetip}:${telnetport} : FAIL"
|
|
elif [ "${completed}" ]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
# If telnet shutdown was successful will use telnet again to check
|
|
# the connection has closed, confirming that the tmux session can now be killed.
|
|
if [ "${completed}" ]; then
|
|
for seconds in {1..30}; do
|
|
fn_stop_graceful_sm_telnet
|
|
refused=$(echo -en "\n ${smtelnetshutdown}" | grep "Timeout or EOF")
|
|
if [ "${refused}" ]; then
|
|
fn_print_ok "Graceful: telnet: ${telnetip}:${telnetport} : "
|
|
fn_print_ok_eol_nl
|
|
fn_script_log_pass "Graceful: telnet: ${telnetip}:${telnetport} : ${seconds} seconds"
|
|
if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
|
|
alert="stopped"
|
|
alert.sh
|
|
fi
|
|
break
|
|
fi
|
|
fn_sleep_time_1
|
|
fn_print_dots "Graceful: telnet: ${seconds}"
|
|
done
|
|
# If telnet shutdown fails, show it and stop
|
|
else
|
|
if [ "${refused}" ]; then
|
|
fn_print_error "Graceful: telnet: "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_error "Graceful: telnet: ${telnetip}:${telnetport} : FAIL"
|
|
else
|
|
fn_print_error_nl "Graceful: telnet: Unknown error"
|
|
fn_script_log_error "Graceful: telnet: Unknown error"
|
|
fi
|
|
echo -en "\n" | tee -a "${lgsmlog}"
|
|
echo -en "Telnet output:" | tee -a "${lgsmlog}"
|
|
echo -en "\n ${smtelnetshutdown}" | tee -a "${lgsmlog}"
|
|
echo -en "\n\n" | tee -a "${lgsmlog}"
|
|
fi
|
|
else
|
|
fn_print_warn "Graceful: telnet: expect not installed: "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_warn "Graceful: telnet: expect not installed: FAIL"
|
|
fi
|
|
}
|
|
|
|
# telnet command for soulmask graceful shutdown.
|
|
fn_stop_graceful_sm_telnet() {
|
|
smtelnetshutdown=$(expect -c '
|
|
proc abort {} {
|
|
puts "Timeout or EOF\n"
|
|
exit 1
|
|
}
|
|
spawn telnet '"${telnetip}"' '"${telnetport}"'
|
|
expect {
|
|
"Hello:" { send "saveworld 1\r" }
|
|
default abort
|
|
}
|
|
expect {
|
|
"the world is saved." { send "quit 1\r" }
|
|
default abort
|
|
}
|
|
expect {
|
|
"World is closing..." {}
|
|
default abort
|
|
}
|
|
expect { eof }
|
|
puts "Completed.\n"
|
|
')
|
|
}
|
|
|
|
# Attempts graceful shutdown by sending /save /stop.
|
|
fn_stop_graceful_avorion() {
|
|
fn_print_dots "Graceful: /save /stop"
|
|
fn_script_log_info "Graceful: /save /stop"
|
|
# Sends /save.
|
|
TERM=screen tmux -L "${socketname}" send-keys -t "${sessionname}" /save ENTER > /dev/null 2>&1
|
|
fn_sleep_time_5
|
|
# Sends /quit.
|
|
TERM=screen tmux -L "${socketname}" send-keys -t "${sessionname}" /stop ENTER > /dev/null 2>&1
|
|
# Waits up to 30 seconds giving the server time to shutdown gracefully.
|
|
for seconds in {1..30}; do
|
|
check_status.sh
|
|
if [ "${status}" == "0" ]; then
|
|
fn_print_ok "Graceful: /save /stop: ${seconds}"
|
|
fn_print_ok_eol_nl
|
|
fn_script_log_pass "Graceful: /save /stop: OK: ${seconds} seconds"
|
|
if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
|
|
alert="stopped"
|
|
alert.sh
|
|
fi
|
|
break
|
|
fi
|
|
fn_sleep_time_1
|
|
fn_print_dots "Graceful: /save /stop: ${seconds}"
|
|
done
|
|
check_status.sh
|
|
if [ "${status}" != "0" ]; then
|
|
fn_print_error "Graceful: /save /stop: "
|
|
fn_print_fail_eol_nl
|
|
fn_script_log_error "Graceful: /save /stop: FAIL"
|
|
fi
|
|
}
|
|
|
|
fn_stop_graceful_select() {
|
|
if [ "${stopmode}" == "1" ]; then
|
|
fn_stop_tmux
|
|
elif [ "${stopmode}" == "2" ]; then
|
|
fn_stop_graceful_ctrlc
|
|
elif [ "${stopmode}" == "3" ]; then
|
|
fn_stop_graceful_cmd "quit" 30
|
|
elif [ "${stopmode}" == "4" ]; then
|
|
fn_stop_graceful_cmd "quit" 120
|
|
elif [ "${stopmode}" == "5" ]; then
|
|
fn_stop_graceful_cmd "stop" 30
|
|
elif [ "${stopmode}" == "6" ]; then
|
|
fn_stop_graceful_cmd "q" 30
|
|
elif [ "${stopmode}" == "7" ]; then
|
|
fn_stop_graceful_cmd "exit" 30
|
|
elif [ "${stopmode}" == "8" ]; then
|
|
fn_stop_graceful_sdtd
|
|
elif [ "${stopmode}" == "9" ]; then
|
|
fn_stop_graceful_goldsrc
|
|
elif [ "${stopmode}" == "10" ]; then
|
|
fn_stop_graceful_avorion
|
|
elif [ "${stopmode}" == "11" ]; then
|
|
fn_stop_graceful_cmd "end" 30
|
|
elif [ "${stopmode}" == "12" ]; then
|
|
fn_stop_graceful_cmd "shutdown" 30
|
|
elif [ "${stopmode}" == "13" ]; then
|
|
fn_stop_graceful_sm
|
|
fi
|
|
}
|
|
|
|
fn_stop_tmux() {
|
|
fn_print_dots "${servername}"
|
|
fn_script_log_info "tmux kill-session: ${sessionname}: ${servername}"
|
|
# Kill tmux session.
|
|
TERM=screen tmux -L "${socketname}" kill-session -t "${sessionname}" > /dev/null 2>&1
|
|
fn_sleep_time_1
|
|
check_status.sh
|
|
if [ "${status}" == "0" ]; then
|
|
fn_print_ok_nl "${servername}"
|
|
fn_script_log_pass "Stopped ${servername}"
|
|
if [ "${statusalert}" == "on" ] && [ "${firstcommandname}" == "STOP" ]; then
|
|
alert="stopped"
|
|
alert.sh
|
|
fi
|
|
else
|
|
fn_print_fail_nl "Unable to stop ${servername}"
|
|
fn_script_log_fail "Unable to stop ${servername}"
|
|
fi
|
|
}
|
|
|
|
# Checks if the server is already stopped.
|
|
fn_stop_pre_check() {
|
|
if [ "${status}" == "0" ]; then
|
|
fn_print_skip_nl "${servername} is already stopped"
|
|
fn_script_log_info "${servername} is already stopped"
|
|
else
|
|
fn_stop_players_online
|
|
# Select graceful shutdown.
|
|
fn_stop_graceful_select
|
|
# Check status again, a kill tmux session if graceful shutdown failed.
|
|
check_status.sh
|
|
if [ "${status}" != "0" ]; then
|
|
fn_stop_tmux
|
|
fi
|
|
fi
|
|
}
|
|
|
|
fn_print_dots ""
|
|
check.sh
|
|
|
|
# Create a stopping lockfile that only exists while the stop command is running.
|
|
date '+%s' > "${lockdir:?}/${selfname}-stopping.lock"
|
|
|
|
fn_print_dots "${servername}"
|
|
|
|
info_game.sh
|
|
fn_stop_pre_check
|
|
|
|
# Remove started lockfile.
|
|
rm -f "${lockdir:?}/${selfname}-started.lock"
|
|
|
|
# If user ran the stop command monitor will become disabled.
|
|
if [ "${firstcommandname}" == "STOP" ]; then
|
|
rm -f "${lockdir:?}/${selfname}-monitoring.lock"
|
|
fi
|
|
|
|
# Remove stopping lockfile.
|
|
rm -f "${lockdir:?}/${selfname}-stopping.lock"
|
|
|
|
if [ -z "${exitbypass}" ]; then
|
|
core_exit.sh
|
|
fi
|