2016-08-07 15:37:52 +10:00

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