Ben Peddell d2e8f30e3c Wait for SteamCMD to download mod
When `workshop_download_item` is executed, the Steam API will
download not just the specified mod, but also every other
mod that has ever been installed by that SteamCMD instance.
Wait for those to download and update when downloading the mod.
2015-12-09 04:40:31 +10:00

1433 lines
40 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.4"
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?" -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
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}"
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
#---------------------
# 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 [ ! -f "$arkserverroot/$arkserverexec" ] ; then
echo -e "[" "$YELLOW" "WARN" "$NORMAL" "]" "\tYour ARK server exec could not be found."
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() {
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);
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);
print $rcvbody, "\n";
' "$(getRconPort)" "${ark_MultiHome:-127.0.0.1}" "$(getAdminPassword)" "$1"
}
#
# Save world
#
doSaveWorld() {
rconcmd saveworld
}
#
# Exit cleanly
#
doExitServer() {
rconcmd doexit
}
#
# Broadcast message
#
doBroadcast(){
rconcmd "broadcast $1" >/dev/null
}
#
# Broadcast message with echo
#
doBroadcastWithEcho(){
echo "$1"
doBroadcast "$1"
}
#
# 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=`$steamcmdroot/$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +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(){
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
}
#
# run function
#
doRun() {
cd "$arkserverroot"
arkserveropts="$serverMap"
if [ -n "$serverMapModId" ]; then
arkserveropts="-MapModID=$serverMapModId"
fi
if [ -z "$arkserveropts" ]; then
arkserveropts="TheIsland"
fi
arkextraopts=( )
# 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
arkserveropts="${arkserveropts}?listen"
# run the server in background
echo "`timestamp`: start"
# set max open files limit before we start the server
ulimit -n $maxOpenFiles
serverpid=0
restartserver=1
# Shutdown the server when we are terminated
shutdown_server(){
restartserver=0
rm "$arkserverroot/$arkautorestartfile"
if [ "$serverpid" -ne 0 ]; then
kill -INT $serverpid
fi
}
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 "`timestamp`: Server PID: $serverpid"
# Disable auto-restart so we don't get caught in a restart loop
rm -f "$arkserverroot/$arkautorestartfile"
restartserver=0
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
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 up"
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
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
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 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"
# install the server
./$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +force_install_dir "$arkserverroot" +app_update $appid validate +quit
# the current version should be the last version. We set our version
getCurrentVersion
}
#
# Waits for a configurable number of minutes before updating the server
#
doUpdateWarn(){
cd "$arkserverroot"
local pid=`getServerPID`
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 1 )
for warninterval in "${warnintervals[@]}"; do
if [ "`getServerPID`" != "$pid" ]; then
echo "Server has stopped. Aborting update"
return 1
fi
if (( arkwarnminutes > warninterval )); then
if [ -n "$msgWarnUpdateMinutes" ]; then
warnmsg="$(printf "$msgWarnUpdateMinutes" "$warnminutes")"
else
warnmsg="$(printf "This ARK server will shutdown for an update in %d minutes" "$warnminutes")"
fi
doBroadcastWithEcho "$warnmsg"
sleep $(( warnminutes - warninterval ))m
warnminutes=$warninterval
fi
done
local warnseconds=90
warnintervals=( 60 45 30 20 15 10 5 0 )
for warninterval in "${warnintervals[@]}"; do
if [ "`getServerPID`" != "$pid" ]; then
echo "Server has stopped. Aborting update"
return 1
fi
if [ -n "$msgWarnUpdateSeconds" ]; then
warnmsg="$(printf "$msgWarnUpdateSeconds" "$warnseconds")"
else
warnmsg="$(printf "This ARK server will shutdown for an update in %d seconds" "$warnseconds")"
fi
doBroadcastWithEcho "$warnmsg"
sleep $(( warnseconds - warninterval ))s
warnseconds=$warninterval
done
fi
if [ "`getServerPID`" != "$pid" ]; then
echo "Server has stopped. Aborting update"
return 1
fi
return 0
}
#
# Stop the server, update it and then start it back.
#
doUpdate() {
local appupdate=
local updatetype=normal
local validate=
local modupdate=
local saveworld=
for arg in "$@"; do
if [ "$arg" == "--force" ]; then
appupdate=1
elif [ "$arg" == "--safe" ]; then
updatetype=safe
elif [ "$arg" == "--warn" ]; then
updatetype=warn
elif [ "$arg" == "--validate" ]; then
validate=validate
appupdate=1
elif [ "$arg" == "--saveworld" ]; then
saveworld=1
elif [ "$arg" == "--update-mods" ]; then
modupdate=1
elif [ "$arg" == "--backup" ]; then
arkBackupPreUpdate=true
fi
done
echo "$$" >"${arkserverroot}/.ark-update.lock.$$"
while true; do
if ! ln "${arkserverroot}/.ark-update.lock.$$" "${arkserverroot}/.ark-update.lock"; then
local lockpid="$(<"${arkserverroot}/.ark-update.lock")"
if [ -n "$lockpid" ] && [ "$lockpid" != "$$" ] && kill -0 "$lockpid"; then
echo "Update already in progress (PID: $lockpid)"
rm -f "${arkserverroot}/.ark-update.lock.$$"
return 1
fi
rm -f "${arkserverroot}/.ark-update.lock"
else
break
fi
done
rm -f "${arkserverroot}/.ark-update.lock.$$"
if [ -n "$modupdate" ]; then
if ! doDownloadAllMods; then
modupdate=
fi
if ! isAnyModUpdateNeeded; then
modupdate=
fi
fi
cd "$arkserverroot"
if isUpdateNeeded; then
appupdate=1
fi
if [ -n "$appupdate" -o -n "$modupdate" ]; then
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 ! doUpdateWarn; then
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
# If user wants to back-up, we do it here.
if [ "$arkBackupPreUpdate" == "true" ]; then
doBackup
fi
if [ -n "$appupdate" ]; then
cd "$steamcmdroot"
./$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +force_install_dir "$arkserverroot" +app_update $appid $validate +quit
# 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" ]; then
for modid in $(getModIds); do
if isModUpdateNeeded $modid; then
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
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'
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 modsrcdir="$steamcmdroot/steamapps/workshop/content/$mod_appid/$modid"
local moddldir="$steamcmdroot/steamapps/workshop/downloads/$mod_appid/$modid"
local dlsize=0
cd "$steamcmdroot"
while true; do
./$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +workshop_download_item $mod_appid $modid +quit
if [ ! -d "$moddldir" ]; then break; fi
local newsize="`du -s "$moddldir/.." | cut -f1`"
if [ $newsize -eq $dlsize ]; then break; fi
dlsize=$newsize
done
if [ -f "$modsrcdir/mod.info" ]; then
echo "Mod $modid downloaded"
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:-Linux}"
for varname in "${!mod_branch_@}"; do
if [ "mod_branch_$modid" == "$varname" ]; then
modbranch="${!varname}"
fi
done
if [ \( ! -f "$moddestdir/.modbranch" \) ] || [ "$(<"$moddestdir/.modbranch")" != "$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
}
#
# Checks if any installed or requested mods need to be updated
#
isAnyModUpdateNeeded(){
for modid in $(getModIds); do
if isModUpdateNeeded $modid; then
return 0
fi
done
return 1
}
#
# 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}"
for varname in "${!mod_branch_@}"; do
if [ "mod_branch_$modid" == "$varname" ]; then
modbranch="${!varname}"
fi
done
if [ \( ! -f "$moddestdir/.modbranch" \) ] || [ "$(<"$moddestdir/.modbranch")" != "$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 "$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
perl -e '
my $data;
{ local $/; $data = <STDIN>; }
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);
print pack("L< L< L< Z8 L< C L< L<", $ARGV[0], 0, 8, "ModName", 1, 0, 1, $mapfilelen);
print $mapfile;
print "\x33\xFF\x22\xFF\x02\x00\x00\x00\x01";
' $modid <"$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/.modbranch"
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
}
#
# Copies server state to a backup directory
#
doBackup(){
local datestamp=`date +"%Y-%m-%d_%H.%M.%S"`
local backupdir="${arkbackupdir}/${datestamp}"
local savedir="SavedArks"
mkdir -p "$backupdir"
# 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
# ARK server uses Write-Unlink-Rename
echo -ne "${NORMAL} Copying ARK world file "
cp -p "${arkserverroot}/ShooterGame/Saved/${savedir}/${serverMap##*/}.ark" "${backupdir}/${serverMap##*/}.ark"
if [ ! -f "${backupdir}/${serverMap##*/}.ark" ]; then
sleep 2
cp -p "${arkserverroot}/ShooterGame/Saved/${savedir}/${serverMap##*/}.ark" "${backupdir}/${serverMap##*/}.ark"
fi
# If both attempts fail, server may have
# crashed between unlink and rename
if [ ! -f "${backupdir}/${serverMap##*/}.ark" ]; then
cp -p "${arkserverroot}/ShooterGame/Saved/${savedir}/${serverMap##*/}.tmp" "${backupdir##*/}/${serverMap##*/}.ark"
fi
if [ -f "${backupdir}/${serverMap##*/}.ark" ]; then
echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]"
else
echo -e "${NORMAL}[ ${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 "${arkserverroot}/ShooterGame/Saved/${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}[ ${GREEN}OK${NORMAL} ]"
else
echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]"
fi
done
# ARK server uses Lock-Truncate-Write-Unlock
echo -e "${NORMAL} Copying ARK tribe files "
for f in "${arkserverroot}/ShooterGame/Saved/${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}[ ${GREEN}OK${NORMAL} ]"
else
echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]"
fi
done
# ARK server uses Lock-Truncate-Write-Unlock
echo -ne "${NORMAL} Copying GameUserSettings.ini "
cp -p "${arkserverroot}/ShooterGame/Saved/Config/LinuxServer/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}[ ${GREEN}OK${NORMAL} ]"
else
echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]"
fi
}
#
# 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";
' "$(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 version: " "$GREEN" $instver "$NORMAL"
}
useConfig() {
if [ "$1" == "main" ]; then
return
fi
for varname in "${!configfile_@}"; do
if [ "configfile_$1" == "$varname" ]; then
source "${!varname}"
return
fi
done
source "$1"
}
#---------------------
# Main program
#---------------------
# check the configuration and throw errors or warnings if needed
checkConfig
while true; do
case "$1" in
run)
doRun
;;
start)
if [ "$2" == "--all" ]; then
doStartAll
shift
else
doStart
fi
;;
stop)
if [ "$2" == "--all" ]; then
doStopAll
shift
else
doStop
fi
;;
restart)
if [ "$2" == "--all" ]; then
doStopAll
else
doStop
fi
echo "`timestamp`: stop" >> "$logdir/$arkmanagerLog"
sleep 1
if [ "$2" == "--all" ]; then
doStartAll
shift
else
doStart
fi
echo "`timestamp`: start" >> "$logdir/$arkmanagerLog"
echo "`timestamp`: restart" >> "$logdir/$arkmanagerLog"
;;
install)
doInstall
;;
update)
args=()
while [[ "$2" =~ ^-- ]]; do
args=( "${args[@]}" "$2" )
shift
done
doUpdate "${args[@]}"
;;
checkupdate)
checkForUpdate
;;
installmod)
doInstallMod "$2"
shift
;;
backup)
doBackup
;;
broadcast)
doBroadcast "$2"
shift
;;
saveworld)
doSaveWorld
;;
rconcmd)
rconcmd "$2"
shift
;;
status)
printStatus
;;
upgrade-tools)
doUpgradeTools
;;
uninstall-tools)
doUninstallTools
;;
useconfig)
useConfig "$2"
shift
;;
--version)
echo "Version: ${arkstVersion}"
echo "Channel: ${arkstChannel}"
if [ -n "${arkstCommit}" ]; then
echo "Commit: ${arkstCommit:0:7}"
fi
exit 1
;;
-h|--help)
echo -e "Usage: arkmanager [OPTION]\n"
echo "Option 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 "restart Stops the server and then starts it"
echo "restart --all Restarts all servers specified in configfile_xxxxx"
echo "run Runs the server without daemonizing"
echo "start Starts the server"
echo "start --all Starts all servers specified in configfile_xxxxx"
echo "stop Stops the server"
echo "stop --all Stops all servers specified in configfile_xxxxx"
echo "status Returns the status of the current ARK server instance"
echo "update [OPTION ...] Check for a new ARK server version, if needed, stops the server, updates it, and starts it again"
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> Use the configuration overrides in the specified config name or file"
echo "--help Show this help"
echo "--version Show the version info of ARK Server Tools"
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"
exit 1
;;
*)
echo -n "arkmanager v${arkstVersion}: "
if [ $# -eq 0 ]; then
echo "no command specified"
else
echo "unknown command '$1' specified"
fi
echo "Try 'arkmanager -h' or 'arkmanager --help' for more information."
exit 1
;;
esac
status=$?
shift
if [ $# -eq 0 ]; then
break
fi
done
exit $status