mirror of
https://github.com/eliasstepanik/ark-ac-server-tools.git
synced 2026-01-11 18:48:26 +00:00
2622 lines
75 KiB
Bash
Executable File
2622 lines
75 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ARK: survival evolved manager
|
|
#
|
|
# Original author: LeXaT
|
|
# Maintainer: FezVrasta
|
|
# Contributors: Sispheor, Atriusftw, klightspeed, lexat, puseidr
|
|
|
|
# Script version
|
|
arkstVersion='1.6'
|
|
arkstCommit=''
|
|
|
|
doUpgradeTools() {
|
|
local sudo=sudo
|
|
if [ "$UID" == 0 -o "$steamcmd_user" == "--me" ]; then
|
|
sudo=
|
|
fi
|
|
echo "arkmanager v${arkstVersion}: Checking for updates..."
|
|
arkstLatestVersion=`curl -s https://raw.githubusercontent.com/FezVrasta/ark-server-tools/${arkstChannel}/.version`
|
|
arkstLatestCommit=`curl -s https://api.github.com/repos/FezVrasta/ark-server-tools/git/refs/heads/${arkstChannel} | sed -n 's/^ *"sha": "\(.*\)",.*/\1/p'`
|
|
|
|
if [ "$arkstLatestVersion" == "Not Found" ]; then
|
|
echo "Channel ${arkstChannel} does not exist"
|
|
echo
|
|
echo "Available channels:"
|
|
curl -s https://api.github.com/repos/FezVrasta/ark-server-tools/git/refs/heads | sed -n 's|^ *"ref": "refs/heads/\(.*\)",|\1|p'
|
|
echo
|
|
return
|
|
fi
|
|
|
|
reinstall_args=()
|
|
if [ -n "$install_bindir" ]; then
|
|
reinstall_args=( "${reinstall_args[@]}" "--bindir" "$install_bindir" )
|
|
fi
|
|
if [ -n "$install_libexecdir" ]; then
|
|
reinstall_args=( "${reinstall_args[@]}" "--libexecdir" "$install_libexecdir" )
|
|
fi
|
|
if [ -n "$install_datadir" ]; then
|
|
reinstall_args=( "${reinstall_args[@]}" "--datadir" "$install_datadir" )
|
|
fi
|
|
if [[ $arkstLatestVersion > $arkstVersion ]]; then
|
|
read -p "A new version was found! Do you want to upgrade ARK Server Tools to v${arkstLatestVersion}?" -n 1 -r
|
|
echo -en "\n"
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
curl -s https://raw.githubusercontent.com/FezVrasta/ark-server-tools/${arkstChannel}/netinstall.sh | $sudo bash -s -- ${steamcmd_user} ${arkstChannel} "${reinstall_args[@]}"
|
|
exit 0
|
|
fi
|
|
elif [[ $arkstLatestVersion == $arkstVersion && "$arkstLatestCommit" != "$arkstCommit" ]]; then
|
|
read -p "A hotfix is available for v${arkstLatestVersion}. Do you wish to install it?" -n 1 -r
|
|
echo -en "\n"
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
curl -s https://raw.githubusercontent.com/FezVrasta/ark-server-tools/${arkstChannel}/netinstall.sh | $sudo bash -s -- ${steamcmd_user} ${arkstChannel} "${reinstall_args[@]}"
|
|
exit 0
|
|
fi
|
|
else
|
|
echo "Your ARK server tools are already up to date"
|
|
fi
|
|
}
|
|
|
|
doUninstallTools() {
|
|
local sudo=sudo
|
|
if [ "$UID" == 0 -o "$steamcmd_user" == "--me" ]; then
|
|
sudo=
|
|
fi
|
|
|
|
read -p "Are you sure you want to uninstall the ARK Server Tools? [y/N]" -n 1 -r
|
|
|
|
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
|
|
if [ -n "${install_datadir}" -a -x "${install_datadir}/arkmanager-uninstall.sh" ]; then
|
|
$sudo "${install_datadir}/arkmanager-uninstall.sh"
|
|
exit 0
|
|
elif [ -n "${install_libexecdir}" -a -x "${install_libexecdir}/arkmanager-uninstall.sh" ]; then
|
|
$sudo "${install_libexecdir}/arkmanager-uninstall.sh"
|
|
exit 0
|
|
fi
|
|
fi
|
|
}
|
|
|
|
runAsRoot(){
|
|
getConfigVar(){
|
|
val="$(echo -ne "$(sed -n "/^$1=/{s|^[^=]*=||;s|[[:space:]]*\\(#.*\\)*\$||;s|^\"\\(.*\\)\"\$|\\1|;s|^'\\(.*\\)'\$|\\1|;p}" <"/etc/arkmanager/arkmanager.cfg" | tail -n1)")"
|
|
if [ -n "$val" ]; then
|
|
echo "$val"
|
|
else
|
|
echo "$2"
|
|
fi
|
|
}
|
|
|
|
arkstChannel="$(getConfigVar arkstChannel "master")"
|
|
install_bindir="$(getConfigVar install_bindir "${0%/*}")"
|
|
install_libexecdir="$(getConfigVar install_libexecdir "${install_bindir%/*}/libexec/arkmanager")"
|
|
install_datadir="$(getConfigVar install_datadir "${install_bindir%/*}/share/arkmanager")"
|
|
steamcmd_user="$(getConfigVar steamcmd_user "steam")"
|
|
|
|
if ! getent passwd "$steamcmd_user" >/dev/null 2>&1; then
|
|
echo "Invalid steamcmd_user in config file"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$1" == "upgrade-tools" ]; then
|
|
doUpgradeTools
|
|
elif [ "$1" == "uninstall-tools" ]; then
|
|
doUninstallTools
|
|
else
|
|
su "$steamcmd_user" -c "$(printf "%q" "$0")$(printf " %q" "$@")"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Check the user is not currently running this script as root
|
|
if [ "$(id -u)" == "0" ]; then
|
|
runAsRoot "$@"
|
|
exit 0
|
|
fi
|
|
|
|
#---------------------
|
|
# Variables
|
|
#---------------------
|
|
|
|
# Global variables
|
|
if [ -f "/etc/arkmanager/arkmanager.cfg" ]; then
|
|
source /etc/arkmanager/arkmanager.cfg
|
|
fi
|
|
|
|
if [ -f "${HOME}/.arkmanager.cfg" ]; then
|
|
source "${HOME}/.arkmanager.cfg"
|
|
fi
|
|
|
|
cd "$HOME"
|
|
|
|
lsof=lsof
|
|
if [ -x /usr/sbin/lsof ]; then
|
|
lsof=/usr/sbin/lsof
|
|
fi
|
|
|
|
# Local variables
|
|
instver=""
|
|
bnumber=""
|
|
GREEN="\\033[1;32m"
|
|
RED="\\033[1;31m"
|
|
YELLOW="\\e[0;33m"
|
|
NORMAL="\\033[0;39m"
|
|
maxOpenFiles=100000
|
|
|
|
# Set TERM to "dumb" if TERM is not set
|
|
export TERM=${TERM:-dumb}
|
|
|
|
arkmanagerLog="arkmanager.log" # here are logged the actions performed by arkmanager
|
|
arkserverLog="arkserver.log" # here is logged the output of ShooterGameServer
|
|
|
|
appid="${appid:-376030}"
|
|
mod_appid="${mod_appid:-346110}"
|
|
arkautorestartfile="${arkautorestartfile:-ShooterGame/Saved/.autorestart}"
|
|
arkserverpidfile="${arkserverpidfile:-ShooterGame/Saved/.arkserver.pid}"
|
|
arkmanagerpidfile="${arkmanagerpidfile:-ShooterGame/Saved/.arkmanager.pid}"
|
|
install_bindir="${install_bindir:-${0%/*}}"
|
|
install_libexecdir="${install_libexecdir:-${install_bindir%/*}/libexec/arkmanager}"
|
|
|
|
if [ "$steamcmd_user" == "--me" ]; then
|
|
install_datadir="${install_datadir:-${HOME}/.share/local/arkmanager}"
|
|
else
|
|
install_datadir="${install_datadir:-${install_bindir%/*}/share/arkmanager}"
|
|
fi
|
|
|
|
declare -A modsrcdirs
|
|
|
|
#---------------------
|
|
# functions
|
|
#---------------------
|
|
|
|
#
|
|
# timestamp
|
|
#
|
|
timestamp() {
|
|
date +%T
|
|
}
|
|
|
|
#
|
|
# check configuration and report errors
|
|
#
|
|
checkConfig() {
|
|
# SteamCMD configuration
|
|
# steamcmdroot
|
|
if [ ! -d "$steamcmdroot" ] ; then
|
|
echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tYour SteamCMD root seems not valid."
|
|
fi
|
|
# steamcmdexec
|
|
if [ ! -f "$steamcmdroot/$steamcmdexec" ] ; then
|
|
echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tYour SteamCMD exec could not be found."
|
|
fi
|
|
# steamcmd_user
|
|
if [ "$steamcmd_user" != "--me" ]; then
|
|
if ! getent passwd $steamcmd_user > /dev/null 2>&1 ; then
|
|
echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tYour SteamCMD user is not valid."
|
|
fi
|
|
fi
|
|
|
|
# Environment configuration
|
|
# arkserverexec
|
|
if [ -n "$arkserverroot" ] && [ ! -f "$arkserverroot/$arkserverexec" ] ; then
|
|
echo -e "[" "$YELLOW" "WARN" "$NORMAL" "]" "\tYour ARK server exec could not be found."
|
|
fi
|
|
|
|
# SavedArks directory
|
|
if [ -n "$arkserverroot" ]; then
|
|
local savedarksdir="${arkserverroot}/ShooterGame/Saved/${ark_AltSaveDirectoryName:-SavedArks}"
|
|
mkdir -p "${savedarksdir}"
|
|
if [ ! -w "${savedarksdir}" ]; then
|
|
echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tThe ARK SavedArks directory is not writable, and saveworld will fail"
|
|
fi
|
|
fi
|
|
|
|
# Warn if any mods are requested but not installed
|
|
if [ -n "$arkserverroot" -a -d "${arkserverroot}/ShooterGame/Content/Mods" ]; then
|
|
for modid in $(getModIds); do
|
|
if [ ! -f "${arkserverroot}/ShooterGame/Content/Mods/${modid}/mod.info" ]; then
|
|
echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tMod ${modid} is requested but not installed. Run 'arkmanager installmod ${modid}' to install this mod."
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Warn if mod_branch=Linux
|
|
if [ "$mod_branch" == "Linux" -a -z "$nowarnmodbranch" ]; then
|
|
echo -e "[" "$YELLOW" "WARN" "$NORMAL" "]" "\tmod_branch is set to Linux. Linux mods are known to cause the server to crash. It is suggested you set mod_branch to Windows."
|
|
fi
|
|
|
|
# Service configuration
|
|
# logdir
|
|
if [ ! -w "$logdir" ] ; then
|
|
echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tYou have not rights to write in the log directory."
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Get setting from config or from ini file
|
|
# $1 is the setting name
|
|
# $2 is the default
|
|
#
|
|
getArkServerSetting() {
|
|
local varname="ark_$1"
|
|
if [ -n "${!varname}" ]; then
|
|
echo "${!varname}"
|
|
else
|
|
local val="$(tr -d '\0\376\377' <"${arkserverroot}/ShooterGame/Saved/Config/LinuxServer/GameUserSettings.ini" | sed -n '/^\[ServerSettings\]/,/^\[.*\]/{s/^'"$1"'[[:space:]]*=[[:space:]]*//p;}' )"
|
|
if [ -n "$val" ]; then
|
|
echo "$val"
|
|
else
|
|
echo "$2"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Get server admin password
|
|
#
|
|
getAdminPassword() {
|
|
getArkServerSetting "ServerAdminPassword" ""
|
|
}
|
|
|
|
#
|
|
# Get server RCON Port
|
|
#
|
|
getRconPort() {
|
|
getArkServerSetting "RCONPort" "32330"
|
|
}
|
|
|
|
#
|
|
# Get server Game Port
|
|
#
|
|
getGamePort() {
|
|
echo "${ark_Port:-7778}"
|
|
}
|
|
|
|
#
|
|
# Get server Query Port
|
|
#
|
|
getQueryPort(){
|
|
echo "${ark_QueryPort:-27015}"
|
|
}
|
|
|
|
#
|
|
# Execute RCON command
|
|
#
|
|
rconcmd() {
|
|
local adminpass="$(getAdminPassword)"
|
|
if [ -z "$adminpass" ]; then
|
|
echo "ServerAdminPassword is empty - unable to execute RCON command"
|
|
return 1
|
|
elif [[ "$adminpass" =~ [?\177-\377] ]]; then
|
|
echo "ServerAdminPassword contains invalid characters"
|
|
return 1
|
|
fi
|
|
perl -MSocket -e '
|
|
sub sendpkt {
|
|
my ($sock, $reqid, $reqtype, $body) = @_;
|
|
my $packet = pack("VVV", length($body) + 10, $reqid, $reqtype) . $body . "\0\0";
|
|
send($sock, $packet, 0) or die "Error sending command to server: $!";
|
|
}
|
|
|
|
sub recvpkt {
|
|
my ($sock) = @_;
|
|
my $data = "";
|
|
recv($sock, $data, 12, 0);
|
|
die "Empty response" if length($data) == 0;
|
|
my ($pktlen, $resid, $restype) = unpack("VVV", $data);
|
|
recv($sock, $data, $pktlen - 8, 0);
|
|
return ($resid, $restype, substr($data, 0, $pktlen - 10));
|
|
}
|
|
|
|
sub auth {
|
|
my ($sock, $password) = @_;
|
|
my $reqid = 1;
|
|
sendpkt($sock, $reqid, 3, $password);
|
|
my ($resid, $restype, $rcvbody) = recvpkt($sock);
|
|
die "Authentication failed" if $resid == -1;
|
|
}
|
|
|
|
my $port = $ARGV[0];
|
|
my $ipaddr = $ARGV[1];
|
|
my $password = $ARGV[2];
|
|
my $command = $ARGV[3];
|
|
socket(my $socket, PF_INET, SOCK_STREAM, 0);
|
|
setsockopt($socket, SOL_SOCKET, SO_RCVTIMEO, pack("i4", 30, 0, 0, 0));
|
|
my $sockaddr = pack_sockaddr_in($port, inet_aton($ipaddr));
|
|
connect($socket, $sockaddr) or die "Error connecting to server: $!";
|
|
auth($socket, $password);
|
|
sendpkt($socket, 2, 2, $command);
|
|
my ($resid, $restype, $rcvbody) = recvpkt($socket);
|
|
if ($rcvbody eq "Server received, But no response!! \n ") {
|
|
print "Command processed\n";
|
|
} else {
|
|
print "\"", $rcvbody, "\"\n";
|
|
}
|
|
' "$(getRconPort)" "${ark_MultiHome:-127.0.0.1}" "$adminpass" "$1"
|
|
}
|
|
|
|
#
|
|
# Save world
|
|
#
|
|
doSaveWorld() {
|
|
rconcmd saveworld
|
|
}
|
|
|
|
#
|
|
# Exit cleanly
|
|
#
|
|
doExitServer() {
|
|
rconcmd doexit
|
|
}
|
|
|
|
#
|
|
# Broadcast message
|
|
#
|
|
doBroadcast(){
|
|
rconcmd "broadcast $1"
|
|
}
|
|
|
|
#
|
|
# Broadcast message with echo
|
|
#
|
|
doBroadcastWithEcho(){
|
|
echo "$1"
|
|
doBroadcast "$1"
|
|
}
|
|
|
|
#
|
|
# SteamCMD helper function
|
|
#
|
|
function runSteamCMD(){
|
|
"$steamcmdroot/$steamcmdexec" +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} "$@" +quit
|
|
}
|
|
|
|
function runSteamCMDspinner(){
|
|
if [ -n "$verbose" ]; then
|
|
printf "Executing"
|
|
printf " %q" "$steamcmdroot/$steamcmdexec" +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} "$@" +quit
|
|
printf "\n"
|
|
if (command >&3) 2>/dev/null; then
|
|
runSteamCMD "$@" > >(tee /dev/fd/3)
|
|
else
|
|
runSteamCMD "$@"
|
|
fi
|
|
return $?
|
|
else
|
|
if [ -z "$progressDisplayType" ]; then
|
|
if stty <&2 >/dev/null 2>&1; then
|
|
progressDisplayType=spinner
|
|
else
|
|
progressDisplayType=dots
|
|
fi
|
|
fi
|
|
if (command >&3) 2>/dev/null; then
|
|
runSteamCMD "$@" >&3 &
|
|
else
|
|
runSteamCMD "$@" >/dev/null &
|
|
fi
|
|
local scpid=$!
|
|
local pos=0
|
|
local spinner=( '\b-' '\b/' '\b|' '\b\\' )
|
|
if [ "$progressDisplayType" == "dots" ]; then
|
|
spinner=( '.' )
|
|
fi
|
|
echo -n ' ... '
|
|
while kill -0 $scpid 2>/dev/null; do
|
|
echo -ne "${spinner[$pos]}"
|
|
(( pos = (pos + 1) % ${#spinner[*]} ))
|
|
sleep 0.5
|
|
done
|
|
echo -ne '\b \b'
|
|
wait $scpid
|
|
return $?
|
|
fi
|
|
}
|
|
|
|
function runSteamCMDspinnerSubst(){
|
|
local fd="$1"
|
|
shift
|
|
runSteamCMDspinner "$@" 3>&1 >/dev/fd/${fd}
|
|
}
|
|
|
|
#
|
|
# Check if a new version is available but not apply it
|
|
#
|
|
function checkForUpdate(){
|
|
tput sc
|
|
echo "Querying Steam database for latest version..."
|
|
|
|
if isUpdateNeeded; then
|
|
tput rc; tput ed;
|
|
echo -e "Current version:" "$RED" $instver "$NORMAL"
|
|
echo -e "Available version:" "$GREEN" $bnumber "$NORMAL"
|
|
echo -e "Your server needs to be restarted in order to receive the latest update."
|
|
echo -e "Run \"arkmanager update\" to do so"
|
|
return 1
|
|
else
|
|
tput rc; tput ed;
|
|
echo -e "Current version:" "$GREEN" $instver "$NORMAL"
|
|
echo -e "Available version:" "$GREEN" $bnumber "$NORMAL"
|
|
echo "Your server is up to date!"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Check if the server need to be updated
|
|
# Return 0 if update is needed, else return 1
|
|
#
|
|
function isUpdateNeeded(){
|
|
getCurrentVersion
|
|
getAvailableVersion
|
|
if [[ "$bnumber" == "Unknown" || "$bnumber" -eq "$instver" ]]; then
|
|
return 1 # no update needed
|
|
else
|
|
return 0 # update needed
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Parse an ACF structure
|
|
# $1 is the desired path
|
|
# $2 is the desired property
|
|
# $3 is the current path
|
|
#
|
|
function parseSteamACF(){
|
|
local sname
|
|
while read name val; do
|
|
name="${name#\"}"
|
|
name="${name%\"}"
|
|
val="${val#\"}"
|
|
val="${val%\"}"
|
|
if [ "$name" = "}" ]; then
|
|
break
|
|
elif [ "$name" == "{" ]; then
|
|
parseSteamACF "$1" "$2" "${3}.${sname}"
|
|
else
|
|
if [ "$3" == "$1" -a "$name" == "$2" ]; then
|
|
echo "$val"
|
|
break
|
|
fi
|
|
sname="${name}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
#
|
|
# Return the current version number
|
|
#
|
|
function getCurrentVersion(){
|
|
if [ -f "${arkserverroot}/steamapps/appmanifest_${appid}.acf" ]; then
|
|
instver=`while read name val; do if [ "${name}" == "{" ]; then parseSteamACF "" "buildid"; break; fi; done <"${arkserverroot}/steamapps/appmanifest_${appid}.acf"`
|
|
echo $instver > "$arkserverroot/arkversion"
|
|
else
|
|
instver=""
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Get the current available server version on steamdb
|
|
#
|
|
function getAvailableVersion(){
|
|
rm -f "$steamcmd_appinfocache"
|
|
bnumber=`runSteamCMD +app_info_update 1 +app_info_print "$appid" +quit | while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".depots.branches.public" "buildid"; break; fi; done`
|
|
if [ -z "$bnumber" ]; then
|
|
bnumber="Unknown"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Get the PID of the server process
|
|
#
|
|
function getServerPID(){
|
|
if [ -f "${arkserverroot}/${arkserverpidfile}" ]; then
|
|
serverpid="$(<"${arkserverroot}/${arkserverpidfile}")"
|
|
if kill -0 "$serverpid" >/dev/null 2>&1; then
|
|
echo $serverpid
|
|
return
|
|
fi
|
|
fi
|
|
ps -ef | grep "$arkserverroot/$arkserverexec" | grep -v grep | awk '{print $2}'
|
|
}
|
|
|
|
#
|
|
# Check id the server process is alive
|
|
#
|
|
function isTheServerRunning(){
|
|
if [ -n "`getServerPID`" ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Check if the server is up
|
|
#
|
|
#
|
|
function isTheServerUp(){
|
|
$lsof -i "${ark_MultiHome:+udp@}${ark_MultiHome}:$(getGamePort)" > /dev/null
|
|
result=$?
|
|
if [ $result -ne 0 ]; then
|
|
perl -MSocket -MFcntl -e '
|
|
my $port = int($ARGV[0]);
|
|
socket(my $socket, PF_INET, SOCK_DGRAM, 0);
|
|
setsockopt($socket, SOL_SOCKET, SO_RCVTIMEO, pack("i4", 1, 0, 0, 0));
|
|
my $sockaddr = pack_sockaddr_in($port, inet_aton($ARGV[1]));
|
|
send($socket, "\xff\xff\xff\xffTSource Engine Query\x00", 0, $sockaddr);
|
|
my $flags = fcntl($socket, F_GETFL, 0) or exit(1);
|
|
fcntl($socket, F_SETFL, $flags | O_NONBLOCK) or exit(1);
|
|
my $data = "";
|
|
my $rin = "";
|
|
vec($rin, fileno($socket), 1) = 1;
|
|
if (select($rin, undef, undef, 0.25) >= 0) {
|
|
recv($socket, $data, 1400, 0) or exit(1);
|
|
my ($servername, $mapname, $game, $fullname, $rest) = split(/\x00/, substr($data, 6), 5);
|
|
my $maxplayers = ord(substr($rest, 3, 1));
|
|
if ($maxplayers == 0) { exit(1); }
|
|
exit(0);
|
|
} else {
|
|
exit(1);
|
|
}
|
|
' "$(getQueryPort)" "${ark_MultiHome:-127.0.0.1}"
|
|
result=$?
|
|
fi
|
|
# In this case, the result is:
|
|
# 1 if the command fail. The port is not listenning
|
|
# 0 if the command succeed. The port is listenning
|
|
if [ $result -eq 0 ];then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Check if the server is visible in the steam server list
|
|
#
|
|
function isTheServerOnline(){
|
|
if [ -n "$ark_MultiHome" ]; then
|
|
publicip="$(curl --interface "${ark_MultiHome}" -s https://api.ipify.org/)"
|
|
else
|
|
publicip="$(curl -s https://api.ipify.org/)"
|
|
fi
|
|
local serverresp
|
|
|
|
if [[ "$publicip" =~ [1-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]* ]]; then
|
|
serverresp="$(curl -s "http://api.steampowered.com/ISteamApps/GetServersAtAddress/v0001?addr=${publicip}:$(getQueryPort)")"
|
|
fi
|
|
|
|
# If the Steam server response contains "addr": "$ip:$port",
|
|
# then the server has registered with the Steam master server
|
|
if [[ "$serverresp" =~ "\"addr\": \""([^\"]*):([0-9]*)"\"" ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Check if anybody is connected to the server
|
|
#
|
|
function numPlayersConnected(){
|
|
if [ -n "$arkUsePlayerList" ]; then
|
|
perl -MSocket -e '
|
|
my $port = int($ARGV[0]);
|
|
socket(my $socket, PF_INET, SOCK_DGRAM, 0);
|
|
setsockopt($socket, SOL_SOCKET, SO_RCVTIMEO, pack("i4", 1, 0, 0, 0));
|
|
my $sockaddr = pack_sockaddr_in($port, inet_aton($ARGV[1]));
|
|
send($socket, "\xff\xff\xff\xff\x55\xff\xff\xff\xff", 0, $sockaddr);
|
|
my $data = "";
|
|
recv($socket, $data, 1400, 0) or (print "-1" and exit(1));
|
|
if (ord(substr($data, 4, 1)) == 0x41) {
|
|
my $chal = substr($data, 5);
|
|
send($socket, "\xff\xff\xff\xff\x55" . $chal, 0, $sockaddr);
|
|
$data = "";
|
|
recv($socket, $data, 1400, 0) or (print "-1" and exit(1));
|
|
}
|
|
ord(substr($data, 4, 1)) != 0x44 and (print "-1" and exit(1));
|
|
my $players = ord(substr($data, 5, 1));
|
|
my $active = 0;
|
|
my $pdata = substr($data, 6);
|
|
for my $i (0 .. $players) {
|
|
my $idx = ord(substr($pdata, 0, 1));
|
|
my ($name, $rest) = split(/\x00/, substr($pdata, 1), 2);
|
|
$pdata = substr($rest, 8);
|
|
if ($name ne "") {
|
|
$active = $active + 1;
|
|
}
|
|
}
|
|
print "$active\n";
|
|
' "$(getQueryPort)" "${ark_MultiHome:-127.0.0.1}"
|
|
else
|
|
perl -MSocket -e '
|
|
my $port = int($ARGV[0]);
|
|
socket(my $socket, PF_INET, SOCK_DGRAM, 0);
|
|
setsockopt($socket, SOL_SOCKET, SO_RCVTIMEO, pack("i4", 1, 0, 0, 0));
|
|
my $sockaddr = pack_sockaddr_in($port, inet_aton($ARGV[1]));
|
|
send($socket, "\xff\xff\xff\xffTSource Engine Query\x00", 0, $sockaddr);
|
|
my $data = "";
|
|
recv($socket, $data, 1400, 0) or (print "-1" and exit(1));
|
|
my ($servername, $mapname, $game, $fullname, $rest) = split(/\x00/, substr($data, 6), 5);
|
|
my $players = ord(substr($rest, 2, 1));
|
|
print "$players\n";
|
|
' "$(getQueryPort)" "${ark_MultiHome:-127.0.0.1}"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# run function
|
|
#
|
|
doRun() {
|
|
cd "${arkserverroot}/${arkserverexec%/*}"
|
|
|
|
if isTheServerRunning; then
|
|
echo "Error: another server instance is running from the same directory"
|
|
echo "Aborting - two servers MUST NOT run from the same directory"
|
|
exit 1
|
|
fi
|
|
|
|
echo "$$" >"${arkserverroot}/${arkmanagerpidfile}"
|
|
|
|
arkserveropts="$serverMap"
|
|
|
|
while read varname; do
|
|
val="${!varname}"
|
|
modid="${varname#arkmod_}"
|
|
case "$val" in
|
|
game*|enabled)
|
|
ark_GameModIds="${ark_GameModIds}${ark_GameModIds:+,}${modid}"
|
|
;;
|
|
map*)
|
|
serverMapModId="${modid}"
|
|
;;
|
|
tc|total*)
|
|
ark_TotalConversionMod="${modid}"
|
|
;;
|
|
esac
|
|
done < <(sed -n 's/^\(arkmod_[^= ]*\)=.*/\1/p' <"$configfile")
|
|
|
|
if [ -n "$serverMapModId" ]; then
|
|
serverMap="$(perl -e '
|
|
my $data;
|
|
{ local $/; $data = <>; }
|
|
my $mapnamelen = unpack("@0 L<", $data);
|
|
my $mapname = substr($data, 4, $mapnamelen - 1);
|
|
$mapnamelen += 4;
|
|
my $mapfilelen = unpack("@" . ($mapnamelen + 4) . " L<", $data);
|
|
my $mapfile = substr($data, $mapnamelen + 8, $mapfilelen - 1);
|
|
print $mapfile;
|
|
' <"${arkserverroot}/ShooterGame/Content/Mods/${serverMapModId}/mod.info")"
|
|
arkserveropts="${serverMap}?MapModID=${serverMapModId}"
|
|
fi
|
|
|
|
if [ -z "$arkserveropts" ]; then
|
|
arkserveropts="TheIsland"
|
|
fi
|
|
|
|
arkextraopts=( )
|
|
|
|
while read varname; do
|
|
val="${!varname}"
|
|
case "$varname" in
|
|
ark_*)
|
|
name="${varname#ark_}"
|
|
|
|
# Port is actually one higher than specified
|
|
# i.e. specifying port 7777 will have the server
|
|
# use port 7778
|
|
if [ "$name" == "Port" ]; then
|
|
(( val = val - 1 ))
|
|
fi
|
|
|
|
if [ -n "$val" ]; then
|
|
arkserveropts="${arkserveropts}?${name}=${val}"
|
|
else
|
|
arkserveropts="${arkserveropts}?${name}"
|
|
fi
|
|
;;
|
|
arkopt_*)
|
|
name="${varname#arkopt_}"
|
|
val="${!varname}"
|
|
|
|
if [ -n "$val" ]; then
|
|
arkextraopts=( "${arkextraopts[@]}" "-${name}=${val}" )
|
|
fi
|
|
;;
|
|
arkflag_*)
|
|
name="${varname#arkflag_}"
|
|
|
|
arkextraopts=( "${arkextraopts[@]}" "-${name}" )
|
|
;;
|
|
esac
|
|
unset $varname
|
|
done < <(sed -n 's/^\(ark\(\|opt\|flag\)_[^= ]*\)=.*/\1/p' <"$configfile")
|
|
|
|
# bring in ark_... options
|
|
for varname in "${!ark_@}"; do
|
|
name="${varname#ark_}"
|
|
val="${!varname}"
|
|
|
|
# Port is actually one higher than specified
|
|
# i.e. specifying port 7777 will have the server
|
|
# use port 7778
|
|
if [ "$name" == "Port" ]; then
|
|
(( val = val - 1 ))
|
|
fi
|
|
|
|
if [ -n "$val" ]; then
|
|
arkserveropts="${arkserveropts}?${name}=${val}"
|
|
else
|
|
arkserveropts="${arkserveropts}?${name}"
|
|
fi
|
|
done
|
|
|
|
# bring in arkflag_... flags
|
|
for varname in "${!arkflag_@}"; do
|
|
name="${varname#arkflag_}"
|
|
|
|
arkextraopts=( "${arkextraopts[@]}" "-${name}" )
|
|
done
|
|
|
|
# bring in arkopt_... options
|
|
for varname in "${!arkopt_@}"; do
|
|
name="${varname#arkopt_}"
|
|
val="${!varname}"
|
|
|
|
if [ -n "$val" ]; then
|
|
arkextraopts=( "${arkextraopts[@]}" "-${name}=${val}" )
|
|
fi
|
|
done
|
|
|
|
if [[ " ${arkextraopts[*]} " =~ " -automanagedmods " ]]; then
|
|
if [ ! -f "${arkserverroot}/Engine/Binaries/ThirdParty/SteamCMD/Linux/steamcmd.sh" ]; then
|
|
mkdir -p "${arkserverroot}/Engine/Binaries/ThirdParty/SteamCMD/Linux"
|
|
curl -s "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" -o "${arkserverroot}/Engine/Binaries/ThirdParty/SteamCMD/Linux/steamcmd_linux.tar.gz"
|
|
tar -xzf "${arkserverroot}/Engine/Binaries/ThirdParty/SteamCMD/Linux/steamcmd_linux.tar.gz" -C "${arkserverroot}/Engine/Binaries/ThirdParty/SteamCMD/Linux"
|
|
fi
|
|
fi
|
|
|
|
arkserveropts="${arkserveropts}?listen"
|
|
# run the server in background
|
|
echo "`timestamp`: start"
|
|
|
|
serverpid=0
|
|
restartserver=1
|
|
|
|
# Shutdown the server when we are terminated
|
|
shutdown_server(){
|
|
restartserver=0
|
|
rm -f "$arkserverroot/$arkautorestartfile"
|
|
if [ "$serverpid" -ne 0 ]; then
|
|
kill -INT $serverpid >/dev/null 2>&1
|
|
fi
|
|
exit 0
|
|
}
|
|
|
|
trap shutdown_server INT TERM
|
|
|
|
# Auto-restart loop
|
|
while [ $restartserver -ne 0 ]; do
|
|
echo -n "`timestamp`: Running"
|
|
printf " %q" "$arkserverroot/$arkserverexec" "$arkserveropts" "${arkextraopts[@]}"
|
|
echo
|
|
# Put the server process into the background so we can monitor it
|
|
"$arkserverroot/$arkserverexec" "$arkserveropts" "${arkextraopts[@]}" &
|
|
# Grab the server PID
|
|
serverpid=$!
|
|
echo "$serverpid" >"${arkserverroot}/${arkserverpidfile}"
|
|
echo "`timestamp`: Server PID: $serverpid"
|
|
# Disable auto-restart so we don't get caught in a restart loop
|
|
rm -f "$arkserverroot/$arkautorestartfile"
|
|
restartserver=0
|
|
if [ -n "$arkAlwaysRestartOnCrash" ]; then
|
|
restartserver=1
|
|
touch "$arkserverroot/$arkautorestartfile"
|
|
fi
|
|
|
|
sleep 5
|
|
|
|
while true; do
|
|
# Grab the current server PID
|
|
local pid="`getServerPID`"
|
|
if [ "$pid" == "$serverpid" ]; then
|
|
if [ "$restartserver" -eq 0 ]; then
|
|
# Check if the server has fully started
|
|
if ! isTheServerUp; then
|
|
# Enable auto-restart if the server is up
|
|
echo "`timestamp`: server is up"
|
|
touch "$arkserverroot/$arkautorestartfile"
|
|
restartserver=1
|
|
fi
|
|
fi
|
|
else
|
|
echo "`timestamp`: Bad PID '$pid'; expected '$serverpid'"
|
|
if [ "$pid" != "" ]; then
|
|
# Another instance must be running - disable autorestart
|
|
restartserver=0
|
|
fi
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
# Wait on the now-dead process to reap it and get its return status
|
|
wait $serverpid
|
|
echo "`timestamp`: exited with status $?"
|
|
|
|
# doStop will remove the autorestart file
|
|
if [ ! -f "$arkserverroot/$arkautorestartfile" ]; then
|
|
restartserver=0
|
|
|
|
fi
|
|
|
|
if [ "$restartserver" -ne 0 ]; then
|
|
echo "`timestamp`: restarting server"
|
|
fi
|
|
done
|
|
}
|
|
|
|
#
|
|
# start function
|
|
#
|
|
doStart() {
|
|
if isTheServerRunning; then
|
|
echo "The server is already running"
|
|
else
|
|
if [ "$arkAutoUpdateOnStart" == "true" ]; then
|
|
if ! [[ " $* " =~ " --noautoupdate " ]]; then
|
|
echo "Updating server"
|
|
doUpdate --update-mods
|
|
fi
|
|
fi
|
|
|
|
if [[ " $* " =~ " --alwaysrestart " ]]; then
|
|
arkAlwaysRestartOnCrash=true
|
|
fi
|
|
tput sc
|
|
echo "The server is starting..."
|
|
|
|
doRun </dev/null >>"$logdir/$arkserverLog" 2>&1 & # output of this command is logged
|
|
echo "`timestamp`: start" >> "$logdir/$arkmanagerLog"
|
|
tput rc; tput ed;
|
|
echo "The server is now running, and should be up within 10 minutes"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# starts all servers specified by configfile_xxxxx in config file
|
|
#
|
|
doStartAll(){
|
|
doStart
|
|
for cfg in "${!configfile_@}"; do
|
|
if [ -f "${!cfg}" ]; then
|
|
(
|
|
source "${!cfg}"
|
|
doStart
|
|
)
|
|
fi
|
|
done
|
|
}
|
|
|
|
#
|
|
# stop the ARK server
|
|
#
|
|
doStop() {
|
|
if isTheServerRunning; then
|
|
local stopreason="$1"
|
|
local dowarn=
|
|
local warnreason=
|
|
local dosave=
|
|
shift
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--warn) dowarn=1; ;;
|
|
--warnreason=*) warnreason="${arg#--warnreason=}"; dowarn=1; ;;
|
|
--saveworld) dosave=1; ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -n "$dowarn" ]]; then
|
|
if ! doWarn "$stopreason" "$warnreason"; then
|
|
return 1
|
|
fi
|
|
fi
|
|
if [[ -n "$dosave" ]]; then
|
|
doSaveWorld
|
|
fi
|
|
tput sc
|
|
echo "Stopping server..."
|
|
echo "`timestamp`: stopping" >> "$logdir/$arkmanagerLog"
|
|
rm -f "$arkserverroot/$arkautorestartfile"
|
|
# kill the server with the PID
|
|
PID=`getServerPID`
|
|
kill -INT $PID
|
|
|
|
for (( i = 0; i < 20; i++ )); do
|
|
sleep 1
|
|
if ! isTheServerRunning; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
if isTheServerRunning; then
|
|
tput rc
|
|
echo "Killing server..."
|
|
kill -KILL $PID
|
|
fi
|
|
|
|
if [ -f "${arkserverroot}/${arkmanagerpidfile}" ]; then
|
|
PID="$(<"${arkserverroot}/${arkmanagerpidfile}")"
|
|
if [ -n "$PID" ]; then
|
|
kill $PID
|
|
fi
|
|
fi
|
|
|
|
tput rc; tput ed;
|
|
echo "The server has been stopped"
|
|
echo "`timestamp`: stopped" >> "$logdir/$arkmanagerLog"
|
|
else
|
|
echo "The server is already stopped"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# stops all servers specified by configfile_xxxxx in config file
|
|
#
|
|
doStopAll(){
|
|
doStop
|
|
for cfg in "${!configfile_@}"; do
|
|
if [ -f "${!cfg}" ]; then
|
|
(
|
|
source "${!cfg}"
|
|
doStop
|
|
)
|
|
fi
|
|
done
|
|
}
|
|
|
|
#
|
|
# install / update / download update
|
|
#
|
|
runSteamCMDAppUpdate(){
|
|
runSteamCMDspinner +force_install_dir "$1" +app_update $appid $2
|
|
}
|
|
|
|
#
|
|
# install of ARK server
|
|
#
|
|
doInstall() {
|
|
# Check if arkserverroot already exists
|
|
if [ ! -d "$arkserverroot" ]; then
|
|
# If it does not exist, try create it
|
|
echo -e "Creating the ARK server root directory ($arkserverroot)"
|
|
mkdir -p "$arkserverroot"
|
|
if [ ! $? ] ; then
|
|
echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tFailed to create the defined ARK server root directory ($arkserverroot)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
cd "$steamcmdroot"
|
|
echo -n "Installing ARK server"
|
|
# install the server
|
|
runSteamCMDAppUpdate "$arkserverroot" validate
|
|
# the current version should be the last version. We set our version
|
|
getCurrentVersion
|
|
}
|
|
|
|
|
|
#
|
|
# Cancels a pending shutdown
|
|
#
|
|
doCancelShutdown(){
|
|
if [ -f "${arkserverroot}/.ark-warn.lock" ]; then
|
|
local lockpid="$(<"${arkserverroot}/.ark-warn.lock")"
|
|
if [ -n "$lockpid" ]; then
|
|
kill "$lockpid"
|
|
rm -f "${arkserverroot}/.ark-warn.lock"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Formats a warning message based on replacement strings
|
|
#
|
|
printWarnMessage(){
|
|
local msg
|
|
if [ -n "$msgWarnReason" ]; then
|
|
local reason
|
|
local msgtime
|
|
if [ "$3" == "minutes" ]; then
|
|
if [ -n "$msgTimeMinutes" ]; then
|
|
msgtime="${msgTimeMinutes//\{minutes\}/$4}"
|
|
else
|
|
msgtime="$4 minutes"
|
|
fi
|
|
else
|
|
if [ -n "$msgTimeSeconds" ]; then
|
|
msgtime="${msgTimeSeconds//\{seconds\}/$4}"
|
|
else
|
|
msgtime="$4 seconds"
|
|
fi
|
|
fi
|
|
msg="${msgWarnReason//\{time\}/$msgtime}"
|
|
if [ -n "$warnreason" ]; then
|
|
local v="warnreason_$warnreason"
|
|
reason="${!v}"
|
|
if [ -z "$reason" ]; then
|
|
reason="$warnreason"
|
|
fi
|
|
elif [ "$1" == "update" ]; then
|
|
if [ -n "$appupdate" ]; then
|
|
if [ -n "$modupdate" ]; then
|
|
if [ -n "$msgReasonUpdateAppMod" ]; then
|
|
reason="$msgReasonUpdateMod"
|
|
else
|
|
reason="an update to the game and an update to mod(s) {modnamesupdated}"
|
|
fi
|
|
else
|
|
if [ -n "$msgReasonUpdateApp" ]; then
|
|
reason="$msgReasonUpdateApp"
|
|
else
|
|
reason="an update to the game"
|
|
fi
|
|
fi
|
|
elif [ -n "$modupdate" ]; then
|
|
if [ -n "$msgReasonUpdateMod" ]; then
|
|
reason="$msgReasonUpdateMod"
|
|
else
|
|
reason="an update to mod(s) {modnamesupdated}"
|
|
fi
|
|
fi
|
|
elif [ -n "$shutdownreason" ]; then
|
|
reason="$shutdownreason"
|
|
elif [ "$1" == "restart" ]; then
|
|
if [ -n "$msgReasonRestart" ]; then
|
|
reason="$msgReasonRestart"
|
|
else
|
|
reason="a restart"
|
|
fi
|
|
else
|
|
if [ -n "$msgReasonShutdown" ]; then
|
|
reason="$msgReasonShutdown"
|
|
else
|
|
reason="maintenance"
|
|
fi
|
|
fi
|
|
reason="${reason//\{time\}/${msgtime}}"
|
|
reason="${reason//\{modnamesupdated\}/${modnamesupdated}}"
|
|
reason="${reason//\{version\}/${arkversion}}"
|
|
msg="${msg//\{reason\}/${reason}}"
|
|
else
|
|
if [ "$1" == "update" ]; then
|
|
if [ "$3" == "minutes" ]; then
|
|
if [ -n "$msgWarnUpdateMinutes" ]; then
|
|
msg="${msgWarnUpdateMinutes//%d/$4}"
|
|
else
|
|
msg="This ARK server will shutdown for an update in $4 minutes"
|
|
fi
|
|
else
|
|
if [ -n "$msgWarnUpdateSeconds" ]; then
|
|
msg="${msgWarnUpdateSeconds//%d/$4}"
|
|
else
|
|
msg="This ARK server will shutdown for an update in $4 seconds"
|
|
fi
|
|
fi
|
|
elif [ "$1" == "restart" ]; then
|
|
if [ "$3" == "minutes" ]; then
|
|
if [ -n "$msgWarnRestartMinutes" ]; then
|
|
msg="${msgWarnRestartMinutes//%d/$4}"
|
|
else
|
|
msg="This ARK server will shutdown for a restart in $4 minutes"
|
|
fi
|
|
else
|
|
if [ -n "$msgWarnRestartSeconds" ]; then
|
|
msg="${msgWarnRestartSeconds//%d/$4}"
|
|
else
|
|
msg="This ARK server will shutdown for a restart in $4 seconds"
|
|
fi
|
|
fi
|
|
else
|
|
if [ "$3" == "minutes" ]; then
|
|
if [ -n "$msgWarnShutdownMinutes" ]; then
|
|
msg="${msgWarnShutdownMinutes//%d/$4}"
|
|
else
|
|
msg="This ARK server will shutdown in $4 minutes"
|
|
fi
|
|
else
|
|
if [ -n "$msgWarnShutdownSeconds" ]; then
|
|
msg="${msgWarnShutdownSeconds//%d/$4}"
|
|
else
|
|
msg="This ARK server will shutdown in $4 seconds"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
doBroadcastWithEcho "$msg"
|
|
}
|
|
|
|
#
|
|
# Checks if a player has requested an update cancel in the last 5 minutes
|
|
#
|
|
isUpdateCancelRequested(){
|
|
if [ -n "$chatCommandRestartCancel" ]; then
|
|
local canceltime="$(
|
|
find ~/ARK/server1/ShooterGame/Saved/Logs -name 'ServerGame.*.log' -mmin -5 -print0 |
|
|
xargs -0 grep -F -e "${chatCommandRestartCancel}" |
|
|
sed 's@^[[]\(....\)\.\(..\)\.\(..\)-\(..\)\.\(..\)\.\(..\):.*@\1-\2-\3 \4:\5:\6 UTC@' |
|
|
head -n1)"
|
|
if [ -n canceltime ]; then
|
|
canceltime="$(date +%s --date="${canceltime}")"
|
|
local timenow="$(date +%s --date="now - 5 minutes")"
|
|
if (( canceltime > timenow )); then
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
#
|
|
# Waits for a configurable number of minutes before updating the server
|
|
#
|
|
doWarn(){
|
|
cd "$arkserverroot"
|
|
|
|
(
|
|
echo "$$" >"${arkserverroot}/.ark-warn.lock.$$" 2>/dev/null
|
|
while true; do
|
|
if ! ln "${arkserverroot}/.ark-warn.lock.$$" "${arkserverroot}/.ark-warn.lock" 2>/dev/null; then
|
|
local lockpid="$(<"${arkserverroot}/.ark-warn.lock")"
|
|
if [ -n "$lockpid" ] && [ "$lockpid" != "$$" ] && kill -0 "$lockpid" 2>/dev/null; then
|
|
echo "Shutdown warning already in progress (PID: $lockpid)"
|
|
rm -f "${arkserverroot}/.ark-warn.lock.$$" 2>/dev/null
|
|
exit 1
|
|
fi
|
|
rm -f "${arkserverroot}/.ark-warn.lock"
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
rm -f "${arkserverroot}/.ark-warn.lock.$$"
|
|
|
|
update_cancelled(){
|
|
if [ -n "$msgUpdateCancelled" ]; then
|
|
msg="${msgUpdateCancelled//%s/$1}"
|
|
else
|
|
msg="Shutdown cancelled by operator ($1)"
|
|
fi
|
|
doBroadcastWithEcho "${msg}"
|
|
exit 1
|
|
}
|
|
|
|
trap "update_cancelled 'Ctrl+C'" SIGINT
|
|
trap "update_cancelled 'Terminated'" SIGTERM
|
|
trap "update_cancelled 'Connection Closed'" SIGHUP
|
|
trap "update_cancelled 'Quit'" SIGQUIT
|
|
|
|
local pid=`getServerPID`
|
|
local sleeppid
|
|
if [ -n "$pid" ]; then
|
|
local warnmsg
|
|
local warnminutes=$(( arkwarnminutes ))
|
|
if (( warnminutes == 0 )); then
|
|
warnminutes=60
|
|
fi
|
|
|
|
local warnintervals=( 90 60 45 30 20 15 10 5 4 3 2 )
|
|
|
|
for warninterval in "${warnintervals[@]}"; do
|
|
if [ "`getServerPID`" != "$pid" ]; then
|
|
echo "Server has stopped. Aborting $1"
|
|
rm -f "${arkserverroot}/.ark-warn.lock"
|
|
return 1
|
|
fi
|
|
if (( warnminutes >= warninterval )); then
|
|
sleep 1m &
|
|
sleeppid=$!
|
|
printWarnMessage "$1" "$2" "minutes" "$warnminutes"
|
|
for (( min = warnminutes; min >= warninterval; min-- )); do
|
|
numplayers=$(numPlayersConnected)
|
|
echo "There are ${numplayers} players connected"
|
|
if [[ "numplayers" == "-1" ]]; then
|
|
echo "Server is not running. Shutting down immediately"
|
|
return 0
|
|
elif (( (numplayers + 0) == 0 )); then
|
|
doBroadcastWithEcho "Nobody is connected. Shutting down immediately"
|
|
rm -f "${arkserverroot}/.ark-warn.lock"
|
|
return 0
|
|
fi
|
|
if isUpdateCancelRequested; then
|
|
doBroadcastWithEcho "Restart cancelled by player request"
|
|
return 1
|
|
fi
|
|
wait $sleeppid
|
|
if (( $min > $warninterval )); then
|
|
sleep 1m &
|
|
sleeppid=$!
|
|
fi
|
|
done
|
|
warnminutes=$(( warninterval - 1 ))
|
|
fi
|
|
done
|
|
|
|
local warnseconds=120
|
|
warnintervals=( 90 60 45 30 20 15 10 5 0 )
|
|
for warninterval in "${warnintervals[@]}"; do
|
|
sleep $(( warnseconds - warninterval ))s &
|
|
sleeppid=$!
|
|
if [ "`getServerPID`" != "$pid" ]; then
|
|
echo "Server has stopped. Aborting update"
|
|
rm -f "${arkserverroot}/.ark-warn.lock"
|
|
return 1
|
|
fi
|
|
printWarnMessage "$1" "$2" "seconds" "$warnseconds"
|
|
if (( warnseconds >= 20 )); then
|
|
numplayers=$(numPlayersConnected)
|
|
echo "There are ${numplayers} players connected"
|
|
if [[ "numplayers" == "-1" ]]; then
|
|
echo "Server is not running. Shutting down immediately"
|
|
return 0
|
|
elif (( (numplayers + 0) == 0 )); then
|
|
doBroadcastWithEcho "Nobody is connected. Shutting down immediately"
|
|
rm -f "${arkserverroot}/.ark-warn.lock"
|
|
return 0
|
|
fi
|
|
if isUpdateCancelRequested; then
|
|
doBroadcastWithEcho "Restart cancelled by player request"
|
|
return 1
|
|
fi
|
|
fi
|
|
wait $sleeppid
|
|
warnseconds=$warninterval
|
|
done
|
|
fi
|
|
|
|
rm -f "${arkserverroot}/.ark-warn.lock"
|
|
|
|
if [ "`getServerPID`" != "$pid" ]; then
|
|
echo "Server has stopped. Aborting $1"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
)
|
|
|
|
return $?
|
|
}
|
|
|
|
#
|
|
# Stop the server, update it and then start it back.
|
|
#
|
|
doUpdate() {
|
|
local appupdate=
|
|
local updatetype=normal
|
|
local validate=
|
|
local modupdate=
|
|
local saveworld=
|
|
local downloadonly=
|
|
local nodownload=
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--force) appupdate=1; ;;
|
|
--safe) updatetype=safe; ;;
|
|
--warn) updatetype=warn; ;;
|
|
--ifempty) updatetype=ifempty; ;;
|
|
--warnreason=*) warnreason="${arg#--warnreason=}"; updatetype=warn; ;;
|
|
--validate) validate=validate; appupdate=1; ;;
|
|
--saveworld) saveworld=1; ;;
|
|
--update-mods) modupdate=1; ;;
|
|
--backup) arkBackupPreUpdate=true; ;;
|
|
--stagingdir=*) arkStagingDir="${arg#--stagingdir=}"; ;;
|
|
--downloadonly) downloadonly=1; ;;
|
|
--no-download) nodownload=1; ;;
|
|
*)
|
|
echo "Unrecognized option $arg"
|
|
echo "Try 'arkmanager -h' or 'arkmanager --help' for more information."
|
|
exit 1
|
|
esac
|
|
done
|
|
|
|
echo "$$" >"${arkserverroot}/.ark-update.lock.$$" 2>/dev/null
|
|
while true; do
|
|
if ! ln "${arkserverroot}/.ark-update.lock.$$" "${arkserverroot}/.ark-update.lock" 2>/dev/null; then
|
|
local lockpid="$(<"${arkserverroot}/.ark-update.lock")"
|
|
if [ -n "$lockpid" ] && [ "$lockpid" != "$$" ] && kill -0 "$lockpid" 2>/dev/null; then
|
|
echo "Update already in progress (PID: $lockpid)"
|
|
rm -f "${arkserverroot}/.ark-update.lock.$$" 2>/dev/null
|
|
return 1
|
|
fi
|
|
rm -f "${arkserverroot}/.ark-update.lock"
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
rm -f "${arkserverroot}/.ark-update.lock.$$"
|
|
|
|
if [ -n "$modupdate" ]; then
|
|
if [ -z "$nodownload" ]; then
|
|
if ! doDownloadAllMods; then
|
|
modupdate=
|
|
fi
|
|
fi
|
|
if ! isAnyModUpdateNeeded; then
|
|
modupdate=
|
|
fi
|
|
fi
|
|
|
|
cd "$arkserverroot"
|
|
|
|
if [ -n "$appupdate" ] || isUpdateNeeded; then
|
|
appupdate=1
|
|
|
|
if [ -n "${arkStagingDir}" -a "${arkStagingDir}" != "${arkserverroot}" ]; then
|
|
if [ ! -d "$arkStagingDir/ShooterGame" ]; then
|
|
echo "Copying to staging directory"
|
|
mkdir -p "$arkStagingDir"
|
|
if [ "$(stat -c "%d" "$arkserverroot")" == "$(stat -c "%d" "$arkStagingDir")" ]; then
|
|
cp -al "$arkserverroot/ShooterGame/." "$arkStagingDir/ShooterGame"
|
|
cp -al "$arkserverroot/Engine/." "$arkStagingDir/Engine"
|
|
cp -al "$arkserverroot/linux64/." "$arkStagingDir/linux64"
|
|
cp -al "$arkserverroot/PackageInfo.bin" "$arkStagingDir/PackageInfo.bin"
|
|
cp -al "$arkserverroot/steamclient.so" "$arkStagingDir/steamclient.so"
|
|
cp -al "$arkserverroot/version.txt" "$arkStagingDir/version.txt"
|
|
cp -a "$arkserverroot/steamapps/." "$arkStagingDir/steamapps"
|
|
else
|
|
rsync -a "$arkserverroot/." "$arkStagingDir/."
|
|
fi
|
|
rm -rf "$arkStagingDir/ShooterGame/Content/Mods/"*
|
|
rm -rf "$arkStagingDir/ShooterGame/Saved/"*
|
|
rm -rf "$arkStagingDir/Engine/Binaries/ThirdParty/SteamCMD/Linux/steamapps"
|
|
fi
|
|
|
|
if [ -z "$nodownload" ]; then
|
|
echo -n "Downloading ARK update"
|
|
cd "$steamcmdroot"
|
|
if runSteamCMDAppUpdate "$arkStagingDir" $validate; then
|
|
rm -rf "${arkStagingDir}/steamapps/downloading/${appid}"
|
|
fi
|
|
|
|
if [ -d "${arkStagingDir}/steamapps/downloading/${appid}" ]; then
|
|
echo "Update download interrupted"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$downloadonly" ]; then
|
|
if [ -n "$appupdate" -a -n "$arkStagingDir" -a "$arkStagingDir" != "$arkserverroot" ]; then
|
|
echo "Server update downloaded"
|
|
fi
|
|
if [ -n "$modupdate" ]; then
|
|
echo "Mod update downloaded"
|
|
fi
|
|
echo "Not applying update - download-only enabled"
|
|
elif [ -n "$appupdate" -o -n "$modupdate" ]; then
|
|
arkversion="$(<"$arkserverroot/version.txt")"
|
|
if isTheServerRunning; then
|
|
if [ "$updatetype" == "safe" ]; then
|
|
while [ ! `find $arkserverroot/ShooterGame/Saved/SavedArks -mmin -1 -name ${serverMap##*/}.ark` ]; do
|
|
echo "`timestamp`: Save file older than 1 minute. Delaying update." >> "$logdir/update.log"
|
|
sleep 30s
|
|
done
|
|
echo "`timestamp`: Save file newer than 1 minute. Performing an update." >> "$logdir/update.log"
|
|
elif [ "$updatetype" == "warn" ]; then
|
|
if ! doWarn update; then
|
|
return 1
|
|
fi
|
|
elif [ "$updatetype" == "ifempty" ]; then
|
|
numplayers=$(( $(numPlayersConnected) + 0 ))
|
|
if (( numplayers != 0 )); then
|
|
echo "${numplayers} players are still connected"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# check if the server was alive before the update so we can launch it back after the update
|
|
serverWasAlive=0
|
|
if isTheServerRunning ;then
|
|
serverWasAlive=1
|
|
fi
|
|
|
|
if [ -n "$saveworld" ]; then
|
|
echo "Saving world"
|
|
doSaveWorld
|
|
fi
|
|
|
|
doStop update
|
|
|
|
# If user wants to back-up, we do it here.
|
|
|
|
if [ "$arkBackupPreUpdate" == "true" ]; then
|
|
doBackup
|
|
fi
|
|
|
|
if [ -n "$appupdate" ]; then
|
|
if [ -d "${arkStagingDir}" -a "${arkStagingDir}" != "${arkserverroot}" ]; then
|
|
echo "Applying update from staging directory"
|
|
if [ "$(stat -c "%d" "$arkserverroot")" == "$(stat -c "%d" "$arkStagingDir")" ]; then
|
|
cp -alu --remove-destination "$arkStagingDir/ShooterGame/." "$arkserverroot/ShooterGame"
|
|
cp -alu --remove-destination "$arkStagingDir/Engine/." "$arkserverroot/Engine"
|
|
cp -alu --remove-destination "$arkStagingDir/linux64/." "$arkserverroot/linux64"
|
|
cp -alu --remove-destination "$arkStagingDir/PackageInfo.bin" "$arkserverroot/PackageInfo.bin"
|
|
cp -alu --remove-destination "$arkStagingDir/steamclient.so" "$arkserverroot/steamclient.so"
|
|
cp -alu --remove-destination "$arkStagingDir/version.txt" "$arkserverroot/version.txt"
|
|
cp -au --remove-destination "$arkStagingDir/steamapps/." "$arkserverroot/steamapps"
|
|
else
|
|
rsync -a "$arkStagingDir/." "$arkserverroot"
|
|
fi
|
|
cd "$arkserverroot"
|
|
find Engine ShooterGame linux64 -depth -print |
|
|
grep -v '^ShooterGame/\(Saved\|Content/Mods\)' |
|
|
while read f; do
|
|
if [ ! -e "${arkStagingDir}/${f}" ]; then
|
|
if [ -f "$f" ]; then
|
|
rm "${f}"
|
|
else
|
|
rmdir "${f}"
|
|
fi
|
|
fi
|
|
done
|
|
else
|
|
echo -n "Performing ARK update"
|
|
cd "$steamcmdroot"
|
|
runSteamCMDAppUpdate "$arkserverroot" $validate
|
|
fi
|
|
# the current version should be the last version. We set our version
|
|
getCurrentVersion
|
|
echo "`timestamp`: update to $instver complete" >> "$logdir/update.log"
|
|
fi
|
|
|
|
if [ -n "$modupdate" ] && [ -z "$arkflag_automanagedmods" ]; then
|
|
for modid in $(getModIds); do
|
|
if isModUpdateNeeded $modid; then
|
|
echo "Updating mod $modid"
|
|
doExtractMod $modid
|
|
echo "`timestamp`: Mod $modid updated" >> "$logdir/update.log"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# we restart the server only if it was started before the update
|
|
if [ $serverWasAlive -eq 1 ]; then
|
|
doStart --noautoupdate
|
|
fi
|
|
else
|
|
echo "Your server is already up to date! The most recent version is ${bnumber}."
|
|
echo "`timestamp`: No update needed." >> "$logdir/update.log"
|
|
fi;
|
|
|
|
rm -f "${arkserverroot}/.ark-update.lock"
|
|
}
|
|
|
|
#
|
|
# Get the Mod IDs of the installed mods and the requested mods
|
|
#
|
|
getModIds(){
|
|
(
|
|
echo "${serverMapModId}"
|
|
echo "${ark_TotalConversionMod}"
|
|
echo "${ark_GameModIds}" | tr ',' '\n'
|
|
for v in "${!arkmod_@}"; do
|
|
if [ "${!v}" != "disabled" ]; then
|
|
echo "${v#arkmod_}"
|
|
fi
|
|
done
|
|
find "${arkserverroot}/ShooterGame/Content/Mods" -maxdepth 1 -type d -printf "%P\n"
|
|
) | sort | uniq | grep '^[1-9][0-9]*$'
|
|
}
|
|
|
|
#
|
|
# Downloads a mod from the Steam workshop
|
|
#
|
|
doDownloadMod(){
|
|
local modid=$1
|
|
local steamcmdroot="$steamcmdroot"
|
|
if [ -n "$arkflag_automanagedmods" ]; then
|
|
steamcmdroot="$arkserverroot/Engine/Binaries/ThirdParty/SteamCMD/Linux"
|
|
fi
|
|
local modsrcdir="$steamcmdroot/steamapps/workshop/content/$mod_appid/$modid"
|
|
local moddldir="$steamcmdroot/steamapps/workshop/downloads/$mod_appid"
|
|
cd "$steamcmdroot"
|
|
retries=10
|
|
|
|
# Bypass the 111111111 modid used by Primitive+
|
|
if [ "$modid" = "111111111" ]; then
|
|
return 0
|
|
fi
|
|
|
|
while true; do
|
|
echo -n "Downloading mod $modid"
|
|
local output
|
|
output=$(runSteamCMDspinnerSubst 5 +workshop_download_item $mod_appid $modid)
|
|
result=$?
|
|
if [ $result -eq 0 ]; then
|
|
modsrcdir="$(echo "$output" | sed -n 's@^Success. Downloaded item [0-9][0-9]* to "\([^"]*\)" .*@\1@p')"
|
|
break
|
|
else
|
|
echo
|
|
if [ ! -d "$moddldir" ]; then
|
|
echo "Mod $modid download failed"
|
|
break
|
|
fi
|
|
(( retries = retries - 1 ))
|
|
if (( retries <= 0 )); then
|
|
echo "Retries exhausted"
|
|
fi
|
|
echo "Mod $modid not fully downloaded - retrying"
|
|
fi
|
|
done 5> >(cat)
|
|
|
|
if [ -f "$modsrcdir/mod.info" ]; then
|
|
echo "Mod $modid downloaded"
|
|
modsrcdirs[$modid]="$modsrcdir"
|
|
return 0
|
|
else
|
|
echo "Mod $modid was not successfully downloaded"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Downloads all installed and requested mods from the Steam workshop
|
|
#
|
|
doDownloadAllMods(){
|
|
for modid in $(getModIds); do
|
|
doDownloadMod $modid || return 1
|
|
done
|
|
}
|
|
|
|
#
|
|
# Checks if the files a mod owns need to be updated
|
|
#
|
|
isModUpdateNeeded(){
|
|
local modid=$1
|
|
local modsrcdir="$steamcmdroot/steamapps/workshop/content/$mod_appid/$modid"
|
|
local moddestdir="$arkserverroot/ShooterGame/Content/Mods/$modid"
|
|
local modbranch="${mod_branch:-Windows}"
|
|
|
|
# Bypass the 111111111 modid used by Primitive+
|
|
if [ "$modid" = "111111111" ]; then
|
|
return 1
|
|
fi
|
|
|
|
if [ -n "${modsrcdirs[$modid]}" ]; then
|
|
modsrcdir="${modsrcdirs[$modid]}"
|
|
fi
|
|
|
|
for varname in "${!mod_branch_@}"; do
|
|
if [ "mod_branch_$modid" == "$varname" ]; then
|
|
modbranch="${!varname}"
|
|
fi
|
|
done
|
|
|
|
if [ -f "$moddestdir/.modbranch" ]; then
|
|
mv "$moddestdir/.modbranch" "$moddestdir/__arkmanager_modbranch__.info"
|
|
fi
|
|
|
|
if [ \( ! -f "$moddestdir/__arkmanager_modbranch__.info" \) ] || [ "$(<"$moddestdir/__arkmanager_modbranch__.info")" != "$modbranch" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if [ -f "$modsrcdir/mod.info" ]; then
|
|
if [ -f "$modsrcdir/${modbranch}NoEditor/mod.info" ]; then
|
|
modsrcdir="$modsrcdir/${modbranch}NoEditor"
|
|
fi
|
|
|
|
while read f; do
|
|
if [ \( ! -f "$moddestdir/${f%.z}" \) -o "$modsrcdir/$f" -nt "$moddestdir/${f%.z}" ]; then
|
|
return 0
|
|
fi
|
|
done < <(find "$modsrcdir" -type f ! -name "*.z.uncompressed_size" -printf "%P\n")
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
#
|
|
# Get the name of the specified mod
|
|
#
|
|
getModName(){
|
|
local modid=$1
|
|
local modsrcdir="$steamcmdroot/steamapps/workshop/content/$mod_appid/$modid"
|
|
|
|
if [ -n "${modsrcdirs[$modid]}" ]; then
|
|
modsrcdir="${modsrcdirs[$modid]}"
|
|
fi
|
|
|
|
modname="$(curl -s "http://steamcommunity.com/sharedfiles/filedetails/?id=${modid}" | sed -n 's|^.*<div class="workshopItemTitle">\([^<]*\)</div>.*|\1|p')"
|
|
|
|
if [ -n "$modname" ]; then
|
|
echo "$modname"
|
|
else
|
|
perl -e '
|
|
my $data;
|
|
{ local $/; $data = <STDIN>; }
|
|
my $mapnamelen = unpack("@0 L<", $data);
|
|
my $mapname = substr($data, 4, $mapnamelen - 1);
|
|
print $mapname
|
|
' <"${modsrcdir}/mod.info"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Checks if any installed or requested mods need to be updated
|
|
#
|
|
isAnyModUpdateNeeded(){
|
|
modnamesupdated=""
|
|
local ismodupdateneeded=1
|
|
for modid in $(getModIds); do
|
|
if isModUpdateNeeded $modid; then
|
|
ismodupdateneeded=0
|
|
if [ -n "$modnamesupdated" ]; then
|
|
modnamesupdated="${modnamesupdated}, "
|
|
fi
|
|
modnamesupdated="${modnamesupdated}$(getModName "$modid")"
|
|
fi
|
|
done
|
|
|
|
return $ismodupdateneeded
|
|
}
|
|
|
|
#
|
|
# Extracts a mod into the ARK Mods directory
|
|
#
|
|
doExtractMod(){
|
|
local modid=$1
|
|
local modsrcdir="$steamcmdroot/steamapps/workshop/content/$mod_appid/$modid"
|
|
local moddestdir="$arkserverroot/ShooterGame/Content/Mods/$modid"
|
|
local modbranch="${mod_branch:-Windows}"
|
|
|
|
# Bypass the 111111111 modid used by Primitive+
|
|
if [ "$modid" = "111111111" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "${modsrcdirs[$modid]}" ]; then
|
|
modsrcdir="${modsrcdirs[$modid]}"
|
|
fi
|
|
|
|
for varname in "${!mod_branch_@}"; do
|
|
if [ "mod_branch_$modid" == "$varname" ]; then
|
|
modbranch="${!varname}"
|
|
fi
|
|
done
|
|
|
|
if [ -f "$moddestdir/.modbranch" ]; then
|
|
mv "$moddestdir/.modbranch" "$moddestdir/__arkmanager_modbranch__.info"
|
|
fi
|
|
|
|
if [ \( ! -f "$moddestdir/__arkmanager_modbranch__.info" \) ] || [ "$(<"$moddestdir/__arkmanager_modbranch__.info")" != "$modbranch" ]; then
|
|
rm -rf "$moddestdir"
|
|
fi
|
|
|
|
if [ -f "$modsrcdir/mod.info" ]; then
|
|
echo "Copying files to $moddestdir"
|
|
|
|
if [ -f "$modsrcdir/${modbranch}NoEditor/mod.info" ]; then
|
|
modsrcdir="$modsrcdir/${modbranch}NoEditor"
|
|
fi
|
|
|
|
find "$modsrcdir" -type d -printf "$moddestdir/%P\0" | xargs -0 -r mkdir -p
|
|
|
|
find "$moddestdir" -type f ! -name '.*' -printf "%P\n" | while read f; do
|
|
if [ \( ! -f "$modsrcdir/$f" \) -a \( ! -f "$modsrcdir/${f}.z" \) ]; then
|
|
rm "$moddestdir/$f"
|
|
fi
|
|
done
|
|
|
|
find "$moddestdir" -depth -type d -printf "%P\n" | while read d; do
|
|
if [ ! -d "$modsrcdir/$d" ]; then
|
|
rmdir "$moddestdir/$d"
|
|
fi
|
|
done
|
|
|
|
find "$modsrcdir" -type f ! \( -name '*.z' -or -name '*.z.uncompressed_size' \) -printf "%P\n" | while read f; do
|
|
if [ \( ! -f "$moddestdir/$f" \) -o "$modsrcdir/$f" -nt "$moddestdir/$f" ]; then
|
|
printf "%10d %s " "`stat -c '%s' "$modsrcdir/$f"`" "$f"
|
|
cp "$modsrcdir/$f" "$moddestdir/$f"
|
|
echo -ne "\r\\033[K"
|
|
fi
|
|
done
|
|
|
|
find "$modsrcdir" -type f -name '*.z' -printf "%P\n" | while read f; do
|
|
if [ \( ! -f "$moddestdir/${f%.z}" \) -o "$modsrcdir/$f" -nt "$moddestdir/${f%.z}" ]; then
|
|
printf "%10d %s " "`stat -c '%s' "$modsrcdir/$f"`" "${f%.z}"
|
|
perl -M'Compress::Raw::Zlib' -e '
|
|
my $sig;
|
|
read(STDIN, $sig, 8) or die "Unable to read compressed file: $!";
|
|
if ($sig != "\xC1\x83\x2A\x9E\x00\x00\x00\x00"){
|
|
die "Bad file magic";
|
|
}
|
|
my $data;
|
|
read(STDIN, $data, 24) or die "Unable to read compressed file: $!";
|
|
my ($chunksizelo, $chunksizehi,
|
|
$comprtotlo, $comprtothi,
|
|
$uncomtotlo, $uncomtothi) = unpack("(LLLLLL)<", $data);
|
|
my @chunks = ();
|
|
my $comprused = 0;
|
|
while ($comprused < $comprtotlo) {
|
|
read(STDIN, $data, 16) or die "Unable to read compressed file: $!";
|
|
my ($comprsizelo, $comprsizehi,
|
|
$uncomsizelo, $uncomsizehi) = unpack("(LLLL)<", $data);
|
|
push @chunks, $comprsizelo;
|
|
$comprused += $comprsizelo;
|
|
}
|
|
foreach my $comprsize (@chunks) {
|
|
read(STDIN, $data, $comprsize) or die "File read failed: $!";
|
|
my ($inflate, $status) = new Compress::Raw::Zlib::Inflate();
|
|
my $output;
|
|
$status = $inflate->inflate($data, $output, 1);
|
|
if ($status != Z_STREAM_END) {
|
|
die "Bad compressed stream; status: " . ($status);
|
|
}
|
|
if (length($data) != 0) {
|
|
die "Unconsumed data in input"
|
|
}
|
|
print $output;
|
|
}
|
|
' <"$modsrcdir/$f" >"$moddestdir/${f%.z}"
|
|
touch -c -r "$modsrcdir/$f" "$moddestdir/${f%.z}"
|
|
echo -ne "\r\\033[K"
|
|
fi
|
|
done
|
|
|
|
modname="$(curl -s "http://steamcommunity.com/sharedfiles/filedetails/?id=${modid}" | sed -n 's|^.*<div class="workshopItemTitle">\([^<]*\)</div>.*|\1|p')"
|
|
|
|
if [ -f "${moddestdir}/.mod" ]; then
|
|
rm "${moddestdir}/.mod"
|
|
fi
|
|
|
|
perl -e '
|
|
my $data;
|
|
{ local $/; $data = <STDIN>; }
|
|
my $mapnamelen = unpack("@0 L<", $data);
|
|
my $mapname = substr($data, 4, $mapnamelen - 1);
|
|
my $nummaps = unpack("@" . ($mapnamelen + 4) . " L<", $data);
|
|
my $pos = $mapnamelen + 8;
|
|
my $modname = ($ARGV[1] || $mapname) . "\x00";
|
|
my $modnamelen = length($modname);
|
|
my $modpath = "../../../ShooterGame/Content/Mods/" . $ARGV[0] . "\x00";
|
|
my $modpathlen = length($modpath);
|
|
print pack("L< L< L< Z$modnamelen L< Z$modpathlen L<",
|
|
$ARGV[0], 0, $modnamelen, $modname, $modpathlen, $modpath,
|
|
$nummaps);
|
|
for (my $mapnum = 0; $mapnum < $nummaps; $mapnum++){
|
|
my $mapfilelen = unpack("@" . ($pos) . " L<", $data);
|
|
my $mapfile = substr($data, $mapnamelen + 12, $mapfilelen);
|
|
print pack("L< Z$mapfilelen", $mapfilelen, $mapfile);
|
|
$pos = $pos + 4 + $mapfilelen;
|
|
}
|
|
print "\x33\xFF\x22\xFF\x02\x00\x00\x00\x01";
|
|
' $modid "$modname" <"$moddestdir/mod.info" >"${moddestdir}.mod"
|
|
|
|
if [ -f "$moddestdir/modmeta.info" ]; then
|
|
cat "$moddestdir/modmeta.info" >>"${moddestdir}.mod"
|
|
else
|
|
echo -ne '\x01\x00\x00\x00\x08\x00\x00\x00ModType\x00\x02\x00\x00\x001\x00' >>"${moddestdir}.mod"
|
|
fi
|
|
|
|
echo "$modbranch" >"$moddestdir/__arkmanager_modbranch__.info"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Downloads mod and installs it into mods directory
|
|
#
|
|
doInstallMod(){
|
|
local modid=$1
|
|
|
|
if [ -f "$steamcmdroot/steamapps/workshop/appworkshop_${mod_appid}.acf" ]; then
|
|
sed -i "/^\\t\\t\"${modid}\"/,/^\\t\\t}/d" "$steamcmdroot/steamapps/workshop/appworkshop_${mod_appid}.acf"
|
|
fi
|
|
|
|
if doDownloadMod $modid; then
|
|
doExtractMod $modid
|
|
echo "Mod $modid installed"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Downloads and installs all requested mods
|
|
#
|
|
doInstallAllMods(){
|
|
for modid in $(getModIds); do
|
|
doInstallMod "$modid"
|
|
done
|
|
}
|
|
|
|
#
|
|
# Removes mod from mods directory
|
|
#
|
|
doUninstallMod(){
|
|
local modid=$1
|
|
local moddir="$arkserverroot/ShooterGame/Content/Mods/$modid"
|
|
if [ -d "${moddir}" ]; then
|
|
rm -rf "${moddir}"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Enables a mod in the config
|
|
#
|
|
doEnableMod(){
|
|
local modid="${1%=*}"
|
|
local modtype="${1#*=}"
|
|
if [ "$modtype" = "$1" ]; then
|
|
modtype=game
|
|
fi
|
|
local modvar="arkmod_${modid}"
|
|
if [ -n "${!modvar}" ]; then
|
|
sed -i "s|^\(${modvar}\)=[^ ]*|\1=${modtype}|" "$configfile"
|
|
else
|
|
echo "${modvar}=${modtype}" >>"$configfile"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Disable a mod in the config
|
|
#
|
|
doDisableMod(){
|
|
local modid="$1"
|
|
local modvar="arkmod_$modid"
|
|
if [ "$ark_GameModIds" = *"$modid"* ]; then
|
|
sed -i "s|^\(ark_GameModIds=\(\|[\"']\)\(\|[^\"' ]*,\)\)${modid},*|\1|" "$configfile"
|
|
fi
|
|
if [ -n "$modvar" ]; then
|
|
sed -i "s|^\(arkmod_${modid}\)=[^ ]*|\1=disabled|" "$configfile"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Removes mod from steamcmd workshop directory
|
|
#
|
|
doRemoveMods(){
|
|
local modid
|
|
for modid in ${1//,/ }; do
|
|
if [ -f "$steamcmdroot/steamapps/workshop/appworkshop_${mod_appid}.acf" ]; then
|
|
sed -i "/^\\t\\t\"${modid}\"/,/^\\t\\t}/d" "$steamcmdroot/steamapps/workshop/appworkshop_${mod_appid}.acf"
|
|
fi
|
|
|
|
if [ -d "$steamcmdroot/steamapps/workshop/content/${mod_appid}/${modid}" ]; then
|
|
rm -rf "$steamcmdroot/steamapps/workshop/content/${mod_appid}/${modid}"
|
|
fi
|
|
|
|
if [ -d "$steamcmdroot/steamapps/workshop/downloads/${mod_appid}/${modid}" ]; then
|
|
rm -rf "$steamcmdroot/steamapps/workshop/downloads/${mod_appid}/${modid}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
#
|
|
# Copies server state to a backup directory
|
|
#
|
|
doBackup(){
|
|
local datestamp=`date +"%Y-%m-%d_%H.%M.%S"`
|
|
local daystamp=`date +"%Y-%m-%d"`
|
|
local backupdir="${arkbackupdir}/${datestamp}"
|
|
local backupdirdaily="${arkbackupdir}/${daystamp}"
|
|
local savedir="SavedArks"
|
|
mkdir -p "$backupdir"
|
|
mkdir -p "$backupdirdaily"
|
|
|
|
# extract the map name from the active map mod
|
|
if [ -n "$serverMapModId" ]; then
|
|
serverMap="$(perl -e '
|
|
my $data;
|
|
{ local $/; $data = <>; }
|
|
my $mapnamelen = unpack("@0 L<", $data);
|
|
my $mapname = substr($data, 4, $mapnamelen - 1);
|
|
$mapnamelen += 4;
|
|
my $mapfilelen = unpack("@" . ($mapnamelen + 4) . " L<", $data);
|
|
my $mapfile = substr($data, $mapnamelen + 8, $mapfilelen - 1);
|
|
print $mapfile;
|
|
' <"${arkserverroot}/ShooterGame/Content/Mods/${serverMapModId}/mod.info")"
|
|
fi
|
|
|
|
# Get save directory name
|
|
if [ -n "${ark_AltSaveDirectoryName}" ]; then
|
|
savedir="${ark_AltSaveDirectoryName}"
|
|
fi
|
|
|
|
saverootdir="${arkserverroot}/ShooterGame/Saved"
|
|
savedcfgdir="${saverootdir}/Config/LinuxServer"
|
|
savedir="${saverootdir}/${savedir}"
|
|
|
|
# Check for the (unlikely) case that the case of the
|
|
# saved ark directory is screwed up
|
|
if [ ! -d "${savedir}" ]; then
|
|
cisavedir="$(find "${arkserverroot}" -ipath "${savedir}" | head -n1)"
|
|
|
|
if [ -n "$cisavedir" ]; then
|
|
echo -e " ${NORMAL}[ ${YELLOW}WARN${NORMAL} ] Saved arks directory capitalization is inconsistent"
|
|
savedir="${cisavedir}"
|
|
else
|
|
echo -e " ${NORMAL}[ ${RED}ERROR${NORMAL} ] Saved arks directory does not exist"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# ARK server uses Write-Unlink-Rename
|
|
echo -ne "${NORMAL} Copying ARK world file "
|
|
|
|
# Take into account screwed up casing of saved ark files
|
|
# in some environments
|
|
mapfile="$(find "${savedir}" -iname "${serverMap##*/}.ark" | head -n1)"
|
|
|
|
if [ -z "$mapfile" ]; then
|
|
sleep 2
|
|
mapfile="$(find "${savedir}" -iname "${serverMap##*/}.ark" | head -n1)"
|
|
fi
|
|
|
|
# If both attempts fail, server may have
|
|
# crashed between unlink and rename
|
|
if [ -z "$mapfile" ]; then
|
|
mapfile="$(find "${savedir}" -iname "${serverMap##*/}.tmp" | head -n1)"
|
|
fi
|
|
|
|
# If neither the ark nor the tmp file exists, then the
|
|
# map name may be incorrect. Try to get any ark or tmp
|
|
# file in the saved arks directory
|
|
if [ -z "$mapfile" ]; then
|
|
mapfile="$(find "${savedir}" -iname "*.ark" | head -n1)"
|
|
|
|
if [ -z "$mapfile" ]; then
|
|
mapfile="$(find "${savedir}" -iname "*.tmp" | head -n1)"
|
|
fi
|
|
fi
|
|
|
|
if [ -f "${mapfile}" ]; then
|
|
cp -p "${mapfile}" "${backupdir}/${serverMap##*/}.ark"
|
|
fi
|
|
|
|
if [ -f "${backupdir}/${serverMap##*/}.ark" ]; then
|
|
echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]"
|
|
else
|
|
echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]"
|
|
fi
|
|
|
|
# ARK server uses Lock-Truncate-Write-Unlock
|
|
# Unfortunately we can't lock the file, as
|
|
# ARK server uses a non-blocking lock and will
|
|
# fail to update the file if the lock fails.
|
|
echo -e "${NORMAL} Copying ARK profile files"
|
|
for f in "${savedir}/"*.arkprofile; do
|
|
echo -ne "${NORMAL} ${f##*/} "
|
|
cp -p "${f}" "${backupdir}/${f##*/}"
|
|
if [ ! -s "${backupdir}/${f##*/}" ]; then
|
|
sleep 2
|
|
cp -p "${f}" "${backupdir}/${f##*/}"
|
|
fi
|
|
# If both attempts fail, server may have
|
|
# crashed between truncate and write
|
|
if [ ! -s "${backupdir}/${f##*/}" ]; then
|
|
cp -p "${f%.arkprofile}.tmpprofile" "${backupdir}/${f##*/}"
|
|
fi
|
|
if [ -s "${backupdir}/${f##*/}" ]; then
|
|
echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]"
|
|
else
|
|
echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]"
|
|
fi
|
|
done
|
|
|
|
# ARK server uses Lock-Truncate-Write-Unlock
|
|
echo -e "${NORMAL} Copying ARK tribe files "
|
|
for f in "${savedir}/"*.arktribe; do
|
|
echo -ne "${NORMAL} ${f##*/} "
|
|
cp -p "${f}" "${backupdir}/${f##*/}"
|
|
if [ ! -s "${backupdir}/${f##*/}" ]; then
|
|
sleep 2
|
|
cp -p "${f}" "${backupdir}/${f##*/}"
|
|
fi
|
|
# If both attempts fail, server may have
|
|
# crashed between truncate and write
|
|
if [ ! -s "${backupdir}/${f##*/}" ]; then
|
|
cp -p "${f%.arktribe}.tmptribe" "${backupdir}/${f##*/}"
|
|
fi
|
|
if [ -s "${backupdir}/${f##*/}" ]; then
|
|
echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]"
|
|
else
|
|
echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]"
|
|
fi
|
|
done
|
|
|
|
# ARK server uses Lock-Truncate-Write-Unlock
|
|
echo -ne "${NORMAL} Copying GameUserSettings.ini "
|
|
cp -p "${savedcfgdir}/GameUserSettings.ini" "${backupdir}/GameUserSettings.ini"
|
|
if [ ! -s "${backupdir}/GameUserSettings.ini" ]; then
|
|
sleep 2
|
|
cp -p "${f}" "${backupdir}/${f##*/}"
|
|
fi
|
|
if [ -f "${backupdir}/GameUserSettings.ini" ]; then
|
|
echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]"
|
|
else
|
|
echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]"
|
|
fi
|
|
|
|
|
|
echo -ne "${NORMAL} Copying Game.ini "
|
|
|
|
cp -p "${savedcfgdir}/Game.ini" "${backupdir}/Game.ini"
|
|
if [ ! -s "${backupdir}/Game.ini" ]; then
|
|
sleep 2
|
|
cp -p "${f}" "${backupdir}/${f##*/}"
|
|
fi
|
|
if [ -f "${backupdir}/Game.ini" ]; then
|
|
echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]"
|
|
else
|
|
echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]"
|
|
fi
|
|
|
|
|
|
#Tar the files and remove the original Backup Directory. Saves about 50MB of disk space per backup
|
|
|
|
echo -ne "${NORMAL} Compressing Backup "
|
|
|
|
tar -jcf "${arkbackupdir}/${daystamp}/${datestamp}.tar.bz2" -C "${arkbackupdir}" "${datestamp}"
|
|
rm -rf ${backupdir}
|
|
|
|
if [ -f "${arkbackupdir}/${daystamp}/${datestamp}.tar.bz2" ]; then
|
|
echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]"
|
|
else
|
|
echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]"
|
|
fi
|
|
echo -e "${NORMAL} Created Backup: ${GREEN} ${datestamp}.tar.bz2${NORMAL}"
|
|
|
|
if [ -n "$arkMaxBackupSizeGB" ] && (( arkMaxBackupSizeGB >= 1 )); then
|
|
(( arkMaxBackupSizeMB = arkMaxBackupSizeGB * 1024 ))
|
|
fi
|
|
|
|
if [ -n "$arkMaxBackupSizeMB" ] && (( arkMaxBackupSizeMB > 64 )); then
|
|
find "${arkbackupdir}" -type f -printf "%T@\t%s\t%p\n" |
|
|
sort -n -r |
|
|
cut -f2-3 |
|
|
(sz=0; while read fsz f; do
|
|
if (( sz / 1048576 > arkMaxBackupSizeMB )); then
|
|
rm "$f"
|
|
fi
|
|
(( sz += fsz ))
|
|
done)
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Install a cron job to execute a particular command
|
|
#
|
|
doInstallCronJob(){
|
|
hour='*'
|
|
minute='0'
|
|
cmdopts="${arkCronExtraOpts}"
|
|
cmdargs=""
|
|
output=">/dev/null 2>&1"
|
|
arkmanagerpath="${0}"
|
|
command="$1"
|
|
shift
|
|
|
|
for opt in "$@"; do
|
|
case "$opt" in
|
|
--daily)
|
|
;;
|
|
--hourly)
|
|
hour='*'
|
|
;;
|
|
--hour=*)
|
|
hour="${opt#--hour=}"
|
|
;;
|
|
--minute=*)
|
|
minute="${opt#--minute=}"
|
|
;;
|
|
--enable-output)
|
|
output=
|
|
;;
|
|
--arg=*)
|
|
cmdargs="${cmdargs} $(printf "%q" "${opt#--arg=}")"
|
|
;;
|
|
--*)
|
|
cmdopts="${cmdopts} $(printf "%q" "${opt}")"
|
|
;;
|
|
*)
|
|
cmdargs="${args} $(printf "%q" "${opt}")"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
cronjob="${minute} ${hour} * * * ${arkmanagerpath} --cronjob ${command} @${instance} ${cmdopts} --args ${cmdargs} -- ${output}"
|
|
|
|
(crontab -l | \
|
|
sed -e "\\# [*] [*] [*] ${arkmanagerpath} --cronjob ${command} @${instance} #d";
|
|
echo "${cronjob}" ) | \
|
|
crontab -
|
|
}
|
|
|
|
#
|
|
# Removes an installed cron job
|
|
#
|
|
doRemoveCronJob(){
|
|
arkmanagerpath="${0}"
|
|
command="$1"
|
|
|
|
crontab -l | \
|
|
sed -e "\\# [*] [*] [*] ${arkmanagerpath} --cronjob ${command} @${instance} #d" | \
|
|
crontab -
|
|
}
|
|
|
|
#
|
|
# Print the status of the server (running? online? version?)
|
|
#
|
|
printStatus(){
|
|
if isTheServerRunning ;then
|
|
echo -e "$NORMAL" "Server running: " "$GREEN" "Yes" "$NORMAL"
|
|
else
|
|
echo -e "$NORMAL" "Server running: " "$RED" "No" "$NORMAL"
|
|
fi
|
|
|
|
if isTheServerUp ;then
|
|
echo -e "$NORMAL" "Server listening: " "$RED" "No" "$NORMAL"
|
|
else
|
|
echo -e "$NORMAL" "Server listening: " "$GREEN" "Yes" "$NORMAL"
|
|
perl -MSocket -e '
|
|
my $port = int($ARGV[0]);
|
|
socket(my $socket, PF_INET, SOCK_DGRAM, 0);
|
|
setsockopt($socket, SOL_SOCKET, SO_RCVTIMEO, pack("i4", 1, 0, 0, 0));
|
|
my $sockaddr = pack_sockaddr_in($port, inet_aton($ARGV[1]));
|
|
send($socket, "\xff\xff\xff\xffTSource Engine Query\x00", 0, $sockaddr);
|
|
my $data = "";
|
|
recv($socket, $data, 1400, 0) or (print "Unable to query server\n" and exit(1));
|
|
my ($servername, $mapname, $game, $fullname, $rest) = split(/\x00/, substr($data, 6), 5);
|
|
my $players = ord(substr($rest, 2, 1));
|
|
my $maxplayers = ord(substr($rest, 3, 1));
|
|
print "Server Name: $servername\n";
|
|
print "Players: $players / $maxplayers\n";
|
|
send($socket, "\xff\xff\xff\xff\x55\xff\xff\xff\xff", 0, $sockaddr);
|
|
$data = "";
|
|
recv($socket, $data, 1400, 0) or (print "Challenge request failed" and exit(1));
|
|
if (ord(substr($data, 4, 1)) == 0x41) {
|
|
my $chal = substr($data, 5);
|
|
send($socket, "\xff\xff\xff\xff\x55" . $chal, 0, $sockaddr);
|
|
$data = "";
|
|
recv($socket, $data, 1400, 0) or (print "A2S_PLAYERS request failed" and exit(1));
|
|
}
|
|
ord(substr($data, 4, 1)) != 0x44 and (print ("A2S_PLAYERS Response: : " . unpack("H*", $data)) and exit(1));
|
|
my $players = ord(substr($data, 5, 1));
|
|
my $active = 0;
|
|
my $pdata = substr($data, 6);
|
|
for my $i (0 .. $players) {
|
|
my $idx = ord(substr($pdata, 0, 1));
|
|
my ($name, $rest) = split(/\x00/, substr($pdata, 1), 2);
|
|
$pdata = substr($rest, 8);
|
|
if ($name ne "") {
|
|
$active = $active + 1;
|
|
}
|
|
}
|
|
print "Active Players: $active\n";
|
|
' "$(getQueryPort)" "${ark_MultiHome:-127.0.0.1}"
|
|
|
|
if isTheServerOnline; then
|
|
echo -e "$NORMAL" "Server online: " "$GREEN" "Yes" "$NORMAL"
|
|
echo -e "$NORMAL" "ARKServers link: " "$GREEN" "http://arkservers.net/server/${publicip}:$(getQueryPort)" "$NORMAL"
|
|
else
|
|
echo -e "$NORMAL" "Server online: " "$RED" "No" "$NORMAL"
|
|
fi
|
|
fi
|
|
|
|
getCurrentVersion
|
|
echo -e "$NORMAL" "Server build ID: " "$GREEN" $instver "$NORMAL"
|
|
echo -e "$NORMAL" "Server version: " "$GREEN" "$(<"$arkserverroot/version.txt")" "$NORMAL"
|
|
}
|
|
|
|
getAllInstanceNames(){
|
|
declare -A instancenames
|
|
for varname in "${!configfile_@}"; do
|
|
instancename="${varname#configfile_}"
|
|
instancenames[${instancename}]="${instancename}"
|
|
done
|
|
for f in /etc/arkmanager/instances/*.cfg; do
|
|
if [ -f "${f}" ]; then
|
|
instancename="${f##*/}"
|
|
instancename="${instancename%.cfg}"
|
|
instancenames[${instancename}]="${instancename}"
|
|
fi
|
|
done
|
|
for f in ${HOME}/.config/arkmanager/instances/*.cfg; do
|
|
if [ -f "${f}" ]; then
|
|
instancename="${f##*/}"
|
|
instancename="${instancename%.cfg}"
|
|
instancenames[${instancename}]="${instancename}"
|
|
fi
|
|
done
|
|
|
|
echo "${instancenames[@]}"
|
|
}
|
|
|
|
doListAllInstances(){
|
|
if [ "$1" == "--brief" ]; then
|
|
getAllInstanceNames
|
|
else
|
|
echo "The following instances are available:"
|
|
for n in $(getAllInstanceNames); do
|
|
(
|
|
echo -n " @${n}: "
|
|
useConfig "$n"
|
|
echo "${configfile} => ${arkserverroot}"
|
|
)
|
|
done
|
|
fi
|
|
}
|
|
|
|
doPrintConfig(){
|
|
declare -A vars
|
|
declare -A vals
|
|
for v in $(eval echo \$\{\!{a..z}\*\} \$\{\!{A..Z}\*\} \$\{\!_\*\}); do
|
|
vals["$v"]="${!v}"
|
|
done
|
|
for cfgfile in "$configfile" "$HOME/.arkmanager.cfg" "/etc/arkmanager/arkmanager.cfg"; do
|
|
while read v; do
|
|
val="$(source "$cfgfile"; echo "${!v}")"
|
|
if [[ "$val" = "${vals[$v]}" && -z "${vars[$v]}" ]]; then
|
|
vars["$v"]="$cfgfile"
|
|
echo "${cfgfile} => ${v}"
|
|
fi
|
|
done < <(sed -n 's/^[[:space:]]*\([A-Za-z_][A-Za-z0-9_]*\)=.*/\1/p' <"$cfgfile")
|
|
done
|
|
}
|
|
|
|
useConfig() {
|
|
configfile=
|
|
if [ -f "/etc/arkmanager/instances/${1}.cfg" ]; then
|
|
configfile="/etc/arkmanager/instances/${1}.cfg"
|
|
fi
|
|
if [ -f "${HOME}/.config/arkmanager/instances/${1}.cfg" ]; then
|
|
configfile="${HOME}/.config/arkmanager/instances/${1}.cfg"
|
|
fi
|
|
for varname in "${!configfile_@}"; do
|
|
if [ "configfile_$1" == "$varname" ]; then
|
|
configfile="${!varname}"
|
|
break
|
|
fi
|
|
done
|
|
if [ -z "$configfile" ]; then
|
|
echo "Error: no config files for instance $1"
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$configfile" ]; then
|
|
echo "Error: config file $configfile does not exist"
|
|
exit 1
|
|
fi
|
|
source "$configfile"
|
|
if [ -z "$arkserverroot" ]; then
|
|
echo "Error: arkserverroot not set"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
showUsage() {
|
|
echo -e "Usage: arkmanager [Commands]\n"
|
|
echo "Commands can be followed by one or more @instance arguments"
|
|
echo "The special '@all' instance selects all instances"
|
|
echo "Commands may also be followed by zero or more --options"
|
|
echo
|
|
echo "Commands that take no instances:"
|
|
echo "Command Description"
|
|
echo "upgrade-tools Check for a new ARK Server Tools version and upgrades it if needed"
|
|
echo "uninstall-tools Uninstall the ARK Server Tools"
|
|
echo "useconfig <name> Sets the default instance for the commands that follow"
|
|
echo "list-instances Lists all available instances"
|
|
echo "--help Show this help"
|
|
echo "--version Show the version info of ARK Server Tools"
|
|
echo
|
|
echo "Commands that take one or more instances:"
|
|
echo "Command Description"
|
|
echo "backup Saves a backup of your server inside the backup directory"
|
|
echo "broadcast <msg> Sends a message to all users connected to server"
|
|
echo "saveworld Saves the game world to disk"
|
|
echo "rconcmd <cmd> Execute RCON command on server"
|
|
echo "checkupdate Check for a new ARK server version"
|
|
echo "install Install the ARK server files from steamcmd"
|
|
echo "installmod <modid> Installs a mod from the Steam workshop"
|
|
echo "uninstallmod <modid> Removes the mod from the Mods directory"
|
|
echo "reinstallmod <modid> Removes and re-installs a mod in the Mods directory"
|
|
echo "install-cronjob <cmd> Adds a cron job using the specified command"
|
|
echo "remove-cronjob <cmd> Removes a cron job that used the specified command"
|
|
echo "restart Stops the server and then starts it"
|
|
echo "run Runs the server without daemonizing"
|
|
echo "start Starts the server"
|
|
echo "stop Stops the server"
|
|
echo "status Returns the status of the current ARK server instance"
|
|
echo "update Check for a new ARK server version, if needed, stops the server, updates it, and starts it again"
|
|
echo
|
|
echo "Update command takes the below options:"
|
|
echo " --force Apply update without checking the current version"
|
|
echo " --safe Wait for server to perform world save and update."
|
|
echo " --warn Warn players before updating server"
|
|
echo " --validate Validates all ARK server files"
|
|
echo " --saveworld Saves world before update"
|
|
echo " --update-mods Updates installed and requested mods"
|
|
echo " --backup Takes a backup of the save files before updating"
|
|
echo " --downloadonly Download the mod and/or server update without applying it"
|
|
echo " Requires arkStagingDir be set to a staging directory on the same filesystem as the server"
|
|
}
|
|
|
|
#---------------------
|
|
# Main program
|
|
#---------------------
|
|
|
|
main(){
|
|
# check the configuration and throw errors or warnings if needed
|
|
checkConfig
|
|
|
|
while true; do
|
|
options=( )
|
|
allinstances=no
|
|
instances=( )
|
|
args=( )
|
|
command="$1"
|
|
shift
|
|
nrarg=0
|
|
|
|
# Handle global options
|
|
case "$command" in
|
|
--verbose)
|
|
verbose=1
|
|
continue
|
|
;;
|
|
--dots)
|
|
progressDisplayType=dots
|
|
continue
|
|
;;
|
|
--spinner)
|
|
progressDisplayType=spinner
|
|
continue
|
|
;;
|
|
--cronjob)
|
|
inCronJob=true
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
# get the number of arguments for commands that take arguments
|
|
case "$command" in
|
|
installmod) nrarg=1; ;;
|
|
uninstallmod) nrarg=1; ;;
|
|
reinstallmod) nrarg=1; ;;
|
|
broadcast) nrarg=1; ;;
|
|
rconcmd) nrarg=1; ;;
|
|
useconfig) nrarg=1; ;;
|
|
install-cronjob) nrarg=1; ;;
|
|
remove-cronjob) nrarg=1; ;;
|
|
remove-mods) nrarg=1; ;;
|
|
esac
|
|
|
|
# Enumerate the options and arguments
|
|
while [ $# -ne 0 ]; do
|
|
case "$1" in
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
--args)
|
|
nrarg=$#
|
|
;;
|
|
--verbose)
|
|
verbose=1
|
|
;;
|
|
--dots)
|
|
progressDisplayType=dots
|
|
;;
|
|
--spinner)
|
|
progressDisplayType=spinner
|
|
;;
|
|
--*)
|
|
options+=( "$1" )
|
|
;;
|
|
@all)
|
|
allinstances=yes
|
|
;;
|
|
@*)
|
|
instances+=( "${1#@}" )
|
|
;;
|
|
*)
|
|
if [ $nrarg -gt 0 ]; then
|
|
args+=( "$1" )
|
|
(( nrarg-- ))
|
|
else
|
|
break
|
|
fi
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# handle non-instance separately
|
|
case "$command" in
|
|
upgrade-tools)
|
|
doUpgradeTools
|
|
exit
|
|
;;
|
|
uninstall-tools)
|
|
doUninstallTools
|
|
exit
|
|
;;
|
|
useconfig)
|
|
defaultinstance="${args[0]}"
|
|
continue
|
|
;;
|
|
remove-mods)
|
|
doRemoveMods "${args[0]}"
|
|
if [ $# -eq 0 ]; then
|
|
exit 0
|
|
else
|
|
continue
|
|
fi
|
|
;;
|
|
list-instances)
|
|
doListAllInstances "${options[@]}"
|
|
exit
|
|
;;
|
|
--version)
|
|
echo "Version: ${arkstVersion}"
|
|
echo "Channel: ${arkstChannel}"
|
|
if [ -n "${arkstCommit}" ]; then
|
|
echo "Commit: ${arkstCommit:0:7}"
|
|
fi
|
|
blobsize="$(sed "s@^arkstCommit=.*@arkstCommit=''@" "$0" | wc -c)"
|
|
echo "Blob SHA: $( (echo -ne "blob ${blobsize}\0"; sed "s@^arkstCommit=.*@arkstCommit=''@" "$0") | sha1sum | cut -d' ' -f1)"
|
|
exit 1
|
|
;;
|
|
-h|--help)
|
|
showUsage
|
|
exit 1
|
|
;;
|
|
"")
|
|
echo "arkmanager v${arkstVersion}: no command specified"
|
|
showUsage
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Handle no instances being specified
|
|
if [[ "${#instances[@]}" == 0 && "$allinstances" == "no" ]]; then
|
|
if [ -n "$defaultinstance" ]; then
|
|
instances=( "$defaultinstance" )
|
|
else
|
|
echo "No instances supplied for command ${command} ${options[*]} ${args[*]}"
|
|
read -p "Do you wish to run this command for all instances?" -n 1 -r
|
|
echo
|
|
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
|
|
allinstances=yes
|
|
else
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Handle all instances being requested
|
|
if [[ "$allinstances" == "yes" ]]; then
|
|
instances=( $(getAllInstanceNames) )
|
|
fi
|
|
|
|
# Run the command for each instance requested
|
|
for instance in "${instances[@]}"; do
|
|
(
|
|
echo "Running command '${command}' for instance '${instance}'"
|
|
useConfig "$instance"
|
|
checkConfig
|
|
|
|
case "$command" in
|
|
run)
|
|
doRun
|
|
;;
|
|
start)
|
|
doStart "${options[@]}"
|
|
;;
|
|
stop)
|
|
doStop shutdown "${options[@]}"
|
|
;;
|
|
restart)
|
|
doStop restart "${options[@]}"
|
|
echo "`timestamp`: stop" >> "$logdir/$arkmanagerLog"
|
|
;;
|
|
cancelshutdown)
|
|
doCancelShutdown "${options[@]}"
|
|
;;
|
|
install)
|
|
doInstall
|
|
;;
|
|
update)
|
|
doUpdate "${options[@]}"
|
|
;;
|
|
checkupdate)
|
|
checkForUpdate
|
|
;;
|
|
installmod)
|
|
doInstallMod "${args[@]}"
|
|
;;
|
|
enablemod)
|
|
doEnableMod "${args[@]}"
|
|
;;
|
|
disablemod)
|
|
doDisableMod "${args[@]}"
|
|
;;
|
|
installmods)
|
|
doInstallAllMods
|
|
;;
|
|
uninstallmod)
|
|
doUninstallMod "${args[@]}"
|
|
;;
|
|
reinstallmod)
|
|
doUninstallMod "${args[@]}"
|
|
doInstallMod "${args[@]}"
|
|
;;
|
|
backup)
|
|
doBackup
|
|
;;
|
|
broadcast)
|
|
doBroadcast "${args[@]}"
|
|
;;
|
|
saveworld)
|
|
doSaveWorld
|
|
;;
|
|
rconcmd)
|
|
rconcmd "${args[@]}"
|
|
;;
|
|
printconfig)
|
|
doPrintConfig
|
|
;;
|
|
status)
|
|
printStatus
|
|
;;
|
|
install-cronjob)
|
|
doInstallCronJob "${args[@]}" "${options[@]}"
|
|
;;
|
|
remove-cronjob)
|
|
doRemoveCronJob "${args[@]}"
|
|
;;
|
|
*)
|
|
echo -n "arkmanager v${arkstVersion}: unknown command '$command' specified"
|
|
showUsage
|
|
exit 255
|
|
;;
|
|
esac
|
|
)
|
|
laststatus=$?
|
|
if [ $laststatus -eq 255 ]; then
|
|
exit 1
|
|
elif [ $laststatus -ne 0 ]; then
|
|
status=$laststatus
|
|
fi
|
|
done
|
|
|
|
# Perform the restart portion of the restart command
|
|
if [[ "$command" == "restart" ]]; then
|
|
sleep 1
|
|
for instance in "${instances[@]}"; do
|
|
(
|
|
useConfig "$instance"
|
|
doStart "${options[@]}"
|
|
echo "`timestamp`: start" >> "$logdir/$arkmanagerLog"
|
|
echo "`timestamp`: restart" >> "$logdir/$arkmanagerLog"
|
|
)
|
|
done
|
|
fi
|
|
|
|
if [ $# -eq 0 ]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
exit $status
|
|
}
|
|
|
|
# Only execute main function if script is not being sourced
|
|
# by another script
|
|
if [[ "$(caller)" =~ ^0 ]]; then
|
|
main "$@"
|
|
fi
|
|
|