2020-02-22 20:40:55 +10:00

3839 lines
115 KiB
Bash
Executable File

#!/bin/bash
# ARK: survival evolved manager
#
# Original author: LeXaT
# Maintainer: FezVrasta
# Contributors: Sispheor, Atriusftw, klightspeed, lexat, puseidr
#disable exportall so options are no exported under Debian et al.
set +o allexport
# Script version
arkstVersion='1.6'
arkstTag=''
arkstCommit=''
arkstGithubRepo="FezVrasta/ark-server-tools"
arkstRootUseEnv=''
arkstGlobalCfgFile='/etc/arkmanager/arkmanager.cfg'
arkstUserCfgFile='.arkmanager.cfg'
doUpgradeTools() {
local sudo=sudo
if [ $(id -u) == 0 -o "$steamcmd_user" == "--me" ]; then
sudo=
fi
local 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
echo "arkmanager v${arkstVersion}: Checking for updates..."
if [ -n "$arkstUnstable" ] || [ "$arkstChannel" != "master" ]; then
doUpgradeToolsFromBranch
else
doUpgradeToolsFromRelease
fi
}
doUpgradeToolsFromCommit(){
local sudo=sudo
if [ $(id -u) == 0 -o "$steamcmd_user" == "--me" ]; then
sudo=
fi
local commit="$1"
tmpdir="$(mktemp -d "ark-server-tools-XXXXXXXX")"
if [ -z "$tmpdir" ]; then echo "Unable to create temporary directory"; exit 1; fi
cd "$tmpdir"
echo "Downloading installer"
curl -s -L "https://github.com/${arkstGithubRepo}/archive/${commit}.tar.gz" | tar -xz
cd "ark-server-tools-${commit}/tools"
if [ ! -f "install.sh" ]; then echo "install.sh not found in $PWD"; exit 1; fi
sed -i -e "s|^arkstCommit='.*'|arkstCommit='${commit}'|" \
-e "s|^arkstTag='.*'|arkstTag='${tagname}'|" \
-e "s|^arkstRootUseEnv='.*'|arkstRootUseEnv='${arkstRootUseEnv}'|" \
arkmanager
echo "Running install.sh"
$sudo bash install.sh "$steamcmd_user" "${reinstall_args[@]}"
result=$?
cd /
rm -rf "$tmpdir"
if [ "$result" = 0 ] || [ "$result" = 2 ]; then
echo "ARK Server Tools successfully upgraded"
"$0" --version
else
echo "ARK Server Tools upgrade failed"
fi
exit $result
}
doUpgradeToolsFromBranch(){
arkstLatestVersion=`curl -s "https://raw.githubusercontent.com/${arkstGithubRepo}/${arkstChannel}/.version"`
arkstLatestCommit=`curl -s "https://api.github.com/repos/${arkstGithubRepo}/git/refs/heads/${arkstChannel}" | sed -n 's/^ *"sha": "\(.*\)",.*/\1/p'`
if [[ "$arkstLatestVersion" == "404: Not Found" ]]; then
echo "Channel '${arkstChannel}' does not exist"
echo
echo "Available channels:"
curl -s "https://api.github.com/repos/${arkstGithubRepo}/git/refs/heads" | sed -n 's|^ *"ref": "refs/heads/\(.*\)",|\1|p'
echo
return
fi
REPLY=
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
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
else
echo "Your ARK server tools are already up to date"
fi
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
doUpgradeToolsFromCommit "$arkstLatestCommit"
fi
}
doUpgradeToolsFromRelease(){
local tagname=
local desc=
echo "Getting latest release..."
# Read the variables from github
while IFS=$'\t' read -r n v; do
case "${n}" in
tag_name) tagname="${v}"; ;;
body) desc="${v}"
esac
done < <(curl -s "https://api.github.com/repos/${arkstGithubRepo}/releases/latest" | sed -n 's/^ "\([^"]*\)": "*\([^"]*\)"*,*/\1\t\2/p')
if [ -n "$tagname" ]; then
if [ "$tagname" != "$arkstTag" ]; then
echo "A new version has been released: ${tagname}"
echo -e "$desc"
read -p "Do you want to upgrade to ${tagname}? [Y/N] " -n 1 -r
echo
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
echo "Getting commit for latest release..."
local commit="$(curl -s "https://api.github.com/repos/${arkstGithubRepo}/git/refs/tags/${tagname}" | sed -n 's/^ *"sha": "\(.*\)",.*/\1/p')"
doUpgradeToolsFromCommit "$commit"
fi
else
echo "Your ARK server tools are already up to date"
fi
else
echo "Unable to get latest release"
fi
}
doUninstallTools() {
local sudo=sudo
if [ $(id -u) == 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}" <"${arkstGlobalCfgFile}" | tail -n1)")"
if [ -n "$arkstRootUseEnv" ]; then
val="$(eval printf "%s" "$(printf "%q" "${val}" | sed 's|\\[$]\\[{]\([A-Za-z][A-Za-z0-9_]*\)\\[}]|${\1}|g;s|\\[$]\([A-Za-z][A-Za-z0-9_]*\)|${\1}|g')")"
fi
if [ -n "$val" ]; then
echo "$val"
else
echo "$2"
fi
}
cd /
arkstChannel="$(getConfigVar arkstChannel "master")"
arkstUnstable="$(getConfigVar arkstUnstable "")"
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")"
steamcmd_user_shellexec="$(getConfigVar steamcmd_user_shellexec "${BASH:-/bin/bash}")"
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
exec /sbin/runuser "$steamcmd_user" -s "$steamcmd_user_shellexec" -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 "${arkstGlobalCfgFile}" ]; then
source "${arkstGlobalCfgFile}"
fi
if [ -f "${HOME}/${arkstUserCfgFile}" ]; then
source "${HOME}/${arkstUserCfgFile}"
fi
if [[ -n "${serverbasedir}" && -d "${serverbasedir}" ]]; then
cd "${serverbasedir}"
elif [ -d "${HOME}" ]; then
cd "${HOME}"
elif [ -d "${steamcmdroot}" ]; then
cd "${steamcmdroot}"
elif [[ ! -d "${PWD}" || ! -r "${PWD}" || ! -x "${PWD}" ]]; then
cd /
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}"
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 +"%Y-%m-%d %H:%M:%S"
}
#
# Log a message to arkmanager.log, and exho it to the console
#
logprint(){
printf "%s\n" "$*"
printf "%s: [%s] %s\n" "$(timestamp)" "${instance}" "$*" >>"${logdir}/${arkmanagerLog}"
}
#
# 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
if [ "$1" != "install" ] && [ -n "$instance" ]; then
# 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}/${arkserverdir:-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
if [ "$1" != "installmod" ] && [ "$1" != "installmods" ]; then
# Warn if any mods are requested but not installed
if [ -n "$arkserverroot" -a -d "${arkserverroot}/${arkserverdir:-ShooterGame}/Content/Mods" ]; then
for modid in $(getModIds); do
if [ ! -f "${arkserverroot}/${arkserverdir:-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
fi
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 lcname="$(tr 'A-Z' 'a-z' <<<"${1}")"
local varname
for varname in "${!ark_@}"; do
if [ "$(tr 'A-Z' 'a-z' <<<"$varname")" == "ark_${lcname}" ]; then
echo "${!varname}"
return
fi
done
for varname in "${!arkopt_@}"; do
if [ "$(tr 'A-Z' 'a-z' <<<"$varname")" == "arkopt_${lcname}" ]; then
echo "${!varname}"
return
fi
done
local val="$(tr -d '\0\376\377' <"${arkserverroot}/${arkserverdir}/Saved/Config/LinuxServer/GameUserSettings.ini" | sed -n '/^\[ServerSettings\]/,/^\[.*\]/{s/^'"$1"'[[:space:]]*=[[:space:]]*//ip;}' )"
if [ -n "$val" ]; then
echo "$val"
else
echo "$2"
fi
}
#
# Get server admin password
#
getAdminPassword() {
getArkServerSetting "ServerAdminPassword" ""
}
#
# Get server RCON Port
#
getRconPort() {
getArkServerSetting "RCONPort" "32330"
}
#
# Gets server bind IP
#
getMultiHome() {
getArkServerSetting "MultiHome" "${1}"
}
getMultiHomeIP() {
getArkServerSetting "MultiHome" "${1:-127.0.0.1}"
}
#
# Get server Game Port
#
getGamePort() {
echo "${ark_Port:-7778}"
}
#
# Get server Query Port
#
getQueryPort(){
echo "${ark_QueryPort:-27015}"
}
#
# Determine SteamCMD data directory
#
getSteamWorkshopDir(){
if [[ -z "${steamcmdhome}" || ! -d "${steamcmdhome}" ]]; then
steamcmdhome="${HOME}"
fi
for d in "$steamworkshopdir" \
"$steamcmdhome/.steam/SteamApps/workshop" \
"$steamcmdhome/.steam/steamapps/workshop" \
"$steamcmdhome/Steam/SteamApps/workshop" \
"$steamcmdhome/Steam/steamapps/workshop" \
"${steamdataroot:-$steamcmdroot}/steamapps/workshop"; do
if [[ -d "${d}" && -f "${d}/appworkshop_${mod_appid}.acf" ]]; then
echo "$d"
return
fi
done
# default
echo "$steamcmdhome/Steam/steamapps/workshop"
}
#
# Determine SteamCMD data directory
#
getSteamAppInfoCache(){
if [[ -z "${steamcmdhome}" || ! -d "${steamcmdhome}" ]]; then
steamcmdhome="${HOME}"
fi
for d in "${steamcmd_appinfocache}" \
"$steamcmdhome/.steam/appcache/appinfo.vdf" \
"$steamcmdhome/Steam/appcache/appinfo.vdf" \
"${steamdataroot:-$steamcmdroot}/appcache/appinfo.vdf"; do
if [[ -f "${d}" ]]; then
echo "$d"
return
fi
done
# default
echo "${steamcmd_appinfocache:-$steamcmdhome/Steam/appcache/appinfo.vdf}"
}
#
# Determine SteamCMD data directory
#
getSteamWorkshopLog(){
if [[ -z "${steamcmdhome}" || ! -d "${steamcmdhome}" ]]; then
steamcmdhome="${HOME}"
fi
for d in "${steamcmd_workshoplog}" \
"$steamcmdhome/.steam/logs/workshop_log.txt" \
"$steamcmdhome/Steam/logs/workshop_log.txt" \
"${steamdataroot:-$steamcmdroot}/logs/workshop_log.txt"; do
if [[ -f "${d}" ]]; then
echo "$d"
return
fi
done
# default
echo "${steamcmd_workshoplog:-$steamcmdhome/logs/workshop_log.txt}"
}
#
# 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 or $resid == 0xFFFFFFFF;
}
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)" "$(getMultiHomeIP)" "$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"
}
#
# Discord Webhook notifier
#
function notifyDiscord(){
if [ -n "$discordWebhookURL" ]; then
local msg="$(echo -n "${msg}" | tr '\n' '\001' | sed 's/\001/\\n/g;s/["\\]/\\\0/g')"
local json="{\"content\":\"${msg}\"}"
curl -s -H "Content-Type: application/json" -d "${json}" "$discordWebhookURL" >/dev/null
fi
}
#
# Overridable notifier
#
function notifyOverride(){
:
}
#
# Notification wrapper
#
function notify(){
if [[ -n "$1" && "$1" != "-" ]]; then
local msg
local notifymsg="$1"
msg="${notifyTemplate:-Message from instance \`{instance\}\` on server \`{server\}\`: {msg\}}"
msg="${msg//\{msg\}/${1}}"
msg="${msg//\{instance\}/${instance}}"
msg="${msg//\{server\}/${HOSTNAME}}"
for v in "${!notifyvar_@}"; do
vn="${v#notifyvar_}"
msg="${msg//\{${vn}\}/${!v}}"
done
notifyDiscord "$msg"
notifyOverride "$msg"
if [ -n "${notifyCommand}" ]; then
eval " ${notifyCommand}"
fi
fi
}
#
# Download SteamCMD
#
function doDownloadSteamCMD(){
if [ ! -f "${steamcmdroot}/${steamcmdexec}" ]; then
mkdir -p "${steamcmdroot}"
curl -s "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" -o "${steamcmdroot}/steamcmd_linux.tar.gz"
tar -xzf "${steamcmdroot}/steamcmd_linux.tar.gz" -C "${steamcmdroot}"
fi
}
#
# SteamCMD helper function
#
function runSteamCMD(){
if [[ -z "${steamcmdhome}" || ! -d "${steamcmdhome}" ]]; then
steamcmdhome="${HOME}"
fi
HOME="${steamcmdhome}" "$steamcmdroot/$steamcmdexec" +@NoPromptForPassword 1 ${steamcmd_cmds_prelogin} +login ${steamlogin:-anonymous} ${steamcmd_cmds_postlogin} "$@" +quit
}
function runSteamCMDspinner(){
if [ -n "$verbose" ]; then
printf "Executing"
printf " %q" "$steamcmdroot/$steamcmdexec" +@NoPromptForPassword 1 ${steamcmd_cmds_prelogin} +login ${steamlogin:-anonymous} ${steamcmd_cmds_postlogin} "$@" +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(){
instver="$(getCurrentVersion)"
bnumber="$(getAvailableVersion)"
if [[ -z "$bnumber" || "$bnumber" -eq "$instver" ]]; then
return 1 # no update needed
elif checkUpdateManifests; then
echo "Build ID changed but manifests have not changed"
return 1
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
while read name val; do if [ "${name}" == "{" ]; then parseSteamACF "" "buildid"; break; fi; done <"${arkserverroot}/steamapps/appmanifest_${appid}.acf"
fi
}
#
# Return the version from the staging directory
#
function getStagingVersion(){
if [ -f "${arkStagingDir}/steamapps/appmanifest_${appid}.acf" ]; then
while read name val; do if [ "${name}" == "{" ]; then parseSteamACF "" "buildid"; break; fi; done <"${arkStagingDir}/steamapps/appmanifest_${appid}.acf"
fi
}
#
# Return the installed beta / branch
#
function getCurrentBranch(){
if [ -f "${arkserverroot}/steamapps/appmanifest_${appid}.acf" ]; then
while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".UserConfig" "betakey"; break; fi; done <"${arkserverroot}/steamapps/appmanifest_${appid}.acf"
fi
}
#
# Return the installed beta / branch in staging directory
#
function getCurrentBranch(){
if [ -f "${arkStagingDir}/steamapps/appmanifest_${appid}.acf" ]; then
while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".UserConfig" "betakey"; break; fi; done <"${arkStagingDir}/steamapps/appmanifest_${appid}.acf"
fi
}
#
# Get the current available server version on steamdb
#
function getAvailableVersion(){
rm -f "$(getSteamAppInfoCache)"
if [ -z "$appbranch" ]; then
appbranch="$(getCurrentBranch)"
fi
runSteamCMD +app_info_update 1 +app_info_print "$appid" | while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".depots.branches.${appbranch:-public}" "buildid"; break; fi; done
}
#
# Gets the server map name
#
function getServerMapName(){
local mapname="${serverMap}"
# extract the map name from the active map mod
if [[ ( -z "$mapname" || "$mapname" == "TheIsland" ) && -n "$serverMapModId" ]]; then
mapname="$(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}/${arkserverdir}/Content/Mods/${serverMapModId}/mod.info")"
fi
echo "${mapname##*/}"
}
#
# Gets the saved worlds directory
#
function getSavedArksDirectory(){
local savedir="SavedArks"
local saverootdir="$1"
# Get save directory name
if [ -n "${ark_AltSaveDirectoryName}" ]; then
savedir="${ark_AltSaveDirectoryName}"
fi
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" >&2
savedir="${cisavedir}"
else
echo -e " ${NORMAL}[ ${RED}ERROR${NORMAL} ] Saved arks directory does not exist" >&2
echo ""
return 1
fi
fi
echo "${savedir}"
}
#
# Check if the update manifest matches the current manifest
#
function checkUpdateManifests(){
local appinfo="$(runSteamCMD +app_info_print "$appid")"
if [ -z "$appbranch" ]; then
appbranch="$(getCurrentBranch)"
fi
local hasmanifest=
while read depot manifest <&3; do
hasmanifest=1
depot="${depot//\"/}"
manifest="${manifest//\"/}"
newmanifest="$(echo "${appinfo}" | while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".depots.${depot}.manifests" "${appbranch:-public}"; break; fi; done)"
if [[ -z "${newmanifest}" && "${appbranch:-public}" != "public" ]]; then
newmanifest="$(echo "${appinfo}" | while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".depots.${depot}.manifests" "public"; break; fi; done)"
fi
if [ "${newmanifest}" != "${manifest}" ]; then
return 1
fi
done 3< <(sed -n '/^[{]$/,/^[}]$/{/^\t"MountedDepots"$/,/^\t[}]$/{/^\t\t/p}}' "${arkserverroot}/steamapps/appmanifest_${appid}.acf")
if [ -z "$hasmanifest" ]; then
return 1
else
return 0
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
if [ -f "${arkserverroot}/${arkserveroldpidfile}" ]; then
serverpid="$(<"${arkserverroot}/${arkserveroldpidfile}")"
if kill -0 "$serverpid" >/dev/null 2>&1; then
echo $serverpid
return
fi
fi
}
#
# 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(){
local ip="$(getMultiHome)"
result=1
if [ -x "$lsof" ]; then
"$lsof" -w -i "${ip:+udp@}${ip}:$(getGamePort)" > /dev/null
result=$?
fi
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)" "$(getMultiHomeIP)"
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 "$(getMultiHome)" ]; then
publicip="$(curl --interface "$(getMultiHomeIP)" -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\":[[:space:]]*\"([^\"]*):([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)" "$(getMultiHomeIP)"
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)" "$(getMultiHomeIP)"
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
# $$ returns the main process, $BASHPID returns the current process
echo "$BASHPID" >"${arkserverroot}/${arkmanagerpidfile}"
if [ -f "${arkserverroot}/${arkupdatelockfile}" ]; then
local updatepid="$(<"${arkserverroot}/${arkupdatelockfile}")"
if kill -0 "$updatepid" >/dev/null 2>&1; then
echo "An update is currently in progress. Start aborted"
return 1
fi
fi
if [[ " $* " = *" --wait "* ]]; then
# This requires bash 4+
# $$ returns the main process, $BASHPID returns the current process
kill -STOP $BASHPID # wait for caller to renice us
fi
arkserveropts="$serverMap"
declare -A usedoptions
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
usedoptions[${varname}]="${val}"
done < <(sed -n 's/^\(arkmod_[^= ]*\)=.*/\1/p' <"$configfile")
if [[ ( -z "$serverMap" || "$serverMap" == "TheIsland" ) && -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}/${arkserverdir}/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
usedoptions[$varname]="${val}"
done < <(sed -n 's/^\(ark\(\|opt\|flag\)_[^= ]*\)=.*/\1/p' <"$configfile")
# bring in ark_... options
for varname in "${!ark_@}"; do
if [ -z "${usedoptions[${varname}]}" ]; then
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
usedoptions[${varname}]="${val}"
fi
done
# bring in arkflag_... flags
for varname in "${!arkflag_@}"; do
if [ -z "${usedoptions[${varname}]}" ]; then
name="${varname#arkflag_}"
arkextraopts=( "${arkextraopts[@]}" "-${name}" )
usedoptions[${varname}]="${val}"
fi
done
# bring in arkopt_... options
for varname in "${!arkopt_@}"; do
if [ -z "${usedoptions[${varname}]}" ]; then
name="${varname#arkopt_}"
val="${!varname}"
if [ -n "$val" ]; then
arkextraopts=( "${arkextraopts[@]}" "-${name}=${val}" )
fi
usedoptions[${varname}]="${val}"
fi
done
if [[ " ${arkextraopts[*]} " =~ " -automanagedmods " ]]; then
steamcmdroot="${arkserverroot}/Engine/Binaries/ThirdParty/SteamCMD/Linux"
steamcmdexec="steamcmd.sh"
doDownloadSteamCMD
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
notify "${notifyMsgShuttingDown:-Shutting down}"
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"
notify "${notifyMsgStarting:-Starting}"
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
serveronline=0
if [ -n "$arkAlwaysRestartOnCrash" ]; then
restartserver=1
touch "$arkserverroot/$arkautorestartfile"
fi
# Retries for checking it the server comes back up after going down
serverdowntries=0
sleep 5
while true; do
# Grab the current server PID
local pid="`getServerPID`"
if [ "$pid" == "$serverpid" ]; then
serverdowntries=0
if [ "$serveronline" -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"
notify "${notifyMsgServerUp:-Server is up}"
if [ "$restartserver" -eq 0 ]; then
touch "$arkserverroot/$arkautorestartfile"
restartserver=1
fi
serveronline=1
fi
elif isTheServerUp; then
(( serverdowntries++ ))
if (( serverdowntries > 12 )); then
# Server has not been listening for 60 seconds, so restart it.
echo "`timestamp`: The server has stopped listening"
echo "`timestamp`: Restarting server"
notify "${notifyMsgStoppedListening:-Server has stopped listening - restarting}"
for (( i = 0; i < 5; i++ )); do
if ! kill -0 "$serverpid"; then
break
fi
kill -INT "$serverpid"
sleep 5
done
if kill -0 "$serverpid"; then
echo "`timestamp`: Graceful restart failed - killing server"
kill -KILL "$serverpid"
fi
# Exit the server check loop
break
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
notify "${notifyMsgServerTerminated:-Server exited - restarting}"
echo "`timestamp`: restarting server"
fi
done
}
doRunBG(){
for fd in $(ls /proc/$BASHPID/fd/); do
[[ $fd -gt 2 && $fd != 255 ]] && exec {fd}<&-
done
doRun "$@" > >(while read -r l; do printf "%s: [%s] %s\n" "$(timestamp)" "${instance}" "${l}"; done) 2>&1
}
#
# start function
#
doStart() {
touch "${arkserverroot}/.startAfterUpdate-${instance}"
if [ -f "${arkserverroot}/${arkupdatelockfile}" ]; then
local updatepid="$(<"${arkserverroot}/${arkupdatelockfile}")"
if kill -0 "$updatepid" >/dev/null 2>&1; then
logprint "Start aborted due to running update - pid: $updatepid"
return 1
fi
fi
serverpid="$(getServerPID)"
if [ -n "$serverpid" ] && kill -0 "$serverpid"; then
logprint "Start aborted due to server already running - pid: $serverpid"
else
local prestart="${configfile%.cfg}.start"
if [ -n "${arkPreStart}" ]; then
prestart="${arkPreStart}"
fi
if [[ -n "${prestart}" && -f "${prestart}" ]]; then
if [ -x "${prestart}" ]; then
"${prestart}" "$@"
else
/bin/bash "${prestart}" "$@"
fi
fi
if [ "$arkAutoUpdateOnStart" == "true" ]; then
if ! [[ " $* " =~ " --noautoupdate " ]]; then
logprint "Checking for updates before starting"
doUpdate --update-mods --no-autostart
fi
fi
if [[ " $* " =~ " --alwaysrestart " ]]; then
arkAlwaysRestartOnCrash=true
fi
nobackground=
if [[ " $* " =~ " --no-background " ]]; then
nobackground=1
fi
tput sc
logprint "The server is starting..."
local pid=$!
if [[ -n "$nobackground" ]]; then
echo
doRun "$@"
return
elif [[ -n "$arkPriorityBoost" || -n "$arkCpuAffinity" ]]; then
doRunBG --wait </dev/null >>"$logdir/$arkserverLog" 2>&1 & # output of this command is logged
local pid="$!"
# Wait for monitor process to suspend itself
sleep 1
if [ -n "$arkPriorityBoost" ]; then
logprint "Boosting priority of ark server"
sudo renice -n "$arkPriorityBoost" "$pid"
fi
if [ -n "$arkCpuAffinity" ]; then
echo "Setting CPU affinity for ark server"
taskset -pc "$arkCpuAffinity" "$pid"
fi
kill -CONT "$pid"
else
doRunBG </dev/null >>"$logdir/$arkserverLog" 2>&1 & # output of this command is logged
fi
tput rc; tput ed;
logprint "The server is now running, and should be up within 10 minutes"
if [[ -n "$arkStartDelay" || -n "$defaultinstance_max" ]]; then
local delay="${arkStartDelay:-${defaultinstance_max}}"
if (( "${#instances[@]}" > 1 )); then
echo -n "Waiting up to ${delay} seconds before starting next instance "
while (( delay > 0 )); do
echo -n "."
if isTheServerOnline; then
break
fi
(( delay-- ))
done
echo
fi
fi
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 [ "$1" != "update" ]; then
rm -f "${arkserverroot}/.startAfterUpdate-${instance}"
fi
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
logprint "Stopping server; reason: $stopreason"
rm -f "$arkserverroot/$arkautorestartfile"
rm -f "$arkserverroot/$arkoldautorestartfile"
# kill the server with the PID
PID=`getServerPID`
kill -INT $PID >/dev/null 2>&1
for (( i = 0; i < 20; i++ )); do
sleep 1
if ! isTheServerRunning; then
break
fi
done
if isTheServerRunning; then
tput rc
logprint "Killing server"
kill -KILL $PID >/dev/null 2>&1
fi
if [ -f "${arkserverroot}/${arkmanagerpidfile}" ]; then
PID="$(<"${arkserverroot}/${arkmanagerpidfile}")"
if [ -n "$PID" ]; then
kill $PID >/dev/null 2>&1
fi
fi
rm -f "${arkserverroot}/${arkserverpidfile}"
rm -f "${arkserverroot}/${arkserveroldpidfile}"
rm -f "${arkserverroot}/${arkmanagerpidfile}"
tput rc; tput ed;
logprint "The server has been stopped"
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(){
local installdir="$1"
shift
runSteamCMDspinner +force_install_dir "$installdir" +app_update $appid $steamcmd_appextraopts "$@"
}
#
# 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
doDownloadSteamCMD
runSteamCMDAppUpdate "$arkserverroot" validate
if [ $? -eq 5 ]; then
echo "User ${steamlogin:-anonymous} login failed - please login to steamcmd manually"
elif [ $? -eq 8 ]; then
echo "Insufficient disk space to install the ARK server"
fi
# the current version should be the last version. We set our version
instver="$(getCurrentVersion)"
}
#
# Cancels a pending shutdown
#
doCancelShutdown(){
if [ -f "${arkserverroot}/${arkwarnlockfile}" ]; then
local lockpid="$(<"${arkserverroot}/${arkwarnlockfile}")"
if [ -n "$lockpid" ]; then
kill "$lockpid"
rm -f "${arkserverroot}/${arkwarnlockfile}"
fi
fi
}
#
# Formats a warning message based on replacement strings
#
printWarnMessage(){
local msg
local usenotify="$5"
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"
if [ -n "$usenotify" ]; then
notify "$msg"
fi
}
#
# Checks if a player has requested an update cancel in the last 5 minutes
#
isUpdateCancelRequested(){
if [ -n "$chatCommandRestartCancel" ]; then
local canceltime="$(
find "${arkserverroot}/${arkserverdir}/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 "${BASHPID}" >"${arkserverroot}/${arkwarnlockfile}.${BASHPID}" 2>/dev/null
while true; do
if ! ln "${arkserverroot}/${arkwarnlockfile}.${BASHPID}" "${arkserverroot}/${arkwarnlockfile}" 2>/dev/null; then
local lockpid="$(<"${arkserverroot}/${arkwarnlockfile}")"
if [ -n "$lockpid" ] && [ "$lockpid" != "${BASHPID}" ] && kill -0 "$lockpid" 2>/dev/null; then
echo "Shutdown warning already in progress (PID: $lockpid)"
rm -f "${arkserverroot}/${arkwarnlockfile}.${BASHPID}" 2>/dev/null
exit 1
fi
rm -f "${arkserverroot}/${arkwarnlockfile}"
else
break
fi
done
rm -f "${arkserverroot}/${arkwarnlockfile}.${BASHPID}"
update_cancelled(){
if [ -n "$msgUpdateCancelled" ]; then
msg="${msgUpdateCancelled//%s/$1}"
else
msg="Shutdown cancelled by operator ($1)"
fi
doBroadcastWithEcho "${msg}"
notify "${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
local usenotify=1
if [ -n "$noNotifyWarn" ]; then
usenotify=
fi
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 )
if (( warnminutes > 2 )); then
for warninterval in "${warnintervals[@]}"; do
if [ "`getServerPID`" != "$pid" ]; then
echo "Server has stopped. Aborting $1"
rm -f "${arkserverroot}/${arkwarnlockfile}"
return 1
fi
if (( warnminutes > warninterval )); then
sleep 1m &
sleeppid=$!
printWarnMessage "$1" "$2" "minutes" "$warnminutes" "$usenotify"
usenotify=
for (( min = warnminutes - 1; min >= warninterval; min-- )); do
numplayers=$(numPlayersConnected)
echo "There are ${numplayers} players connected"
if [[ "numplayers" == "-1" ]]; then
echo "Server is not running. Shutting down immediately"
notify "${notifyMsgServerNotRunning:-Server is not running. Shutting down immediately}"
return 0
elif (( (numplayers + 0) == 0 )); then
doBroadcastWithEcho "Nobody is connected. Shutting down immediately"
notify "${notifyMsgNobodyConnected:-Nobody is connected. Shutting down immediately}"
rm -f "${arkserverroot}/${arkwarnlockfile}"
return 0
fi
if isUpdateCancelRequested; then
doBroadcastWithEcho "Restart cancelled by player request"
notify "${notifyMsgRestartCancelled:-Restart cancelled by player request}"
return 1
fi
wait $sleeppid
if (( $min > $warninterval )); then
sleep 1m &
sleeppid=$!
fi
done
warnminutes=$warninterval
fi
done
fi
local warnseconds=120
warnintervals=( 90 60 45 30 20 15 10 5 0 )
if (( warnminutes == 1 )); then
warnseconds=60
warnintervals=( 45 30 20 15 10 5 0 )
fi
for warninterval in "${warnintervals[@]}"; do
sleep $(( warnseconds - warninterval ))s &
sleeppid=$!
if [ "`getServerPID`" != "$pid" ]; then
echo "Server has stopped. Aborting update"
rm -f "${arkserverroot}/${arkwarnlockfile}"
return 1
fi
printWarnMessage "$1" "$2" "seconds" "$warnseconds" "$usenotify"
usenotify=
if (( warnseconds >= 20 )); then
numplayers=$(numPlayersConnected)
echo "There are ${numplayers} players connected"
if [[ "numplayers" == "-1" ]]; then
echo "Server is not running. Shutting down immediately"
notify "${notifyMsgServerNotRunning:-Server is not running. Shutting down immediately}"
return 0
elif (( (numplayers + 0) == 0 )); then
doBroadcastWithEcho "Nobody is connected. Shutting down immediately"
notify "${notifyMsgNobodyConnected:-Nobody is connected. Shutting down immediately}"
rm -f "${arkserverroot}/${arkwarnlockfile}"
return 0
fi
if isUpdateCancelRequested; then
doBroadcastWithEcho "Restart cancelled by player request"
notify "${notifyMsgRestartCancelled:-Restart cancelled by player request}"
return 1
fi
fi
wait $sleeppid
warnseconds=$warninterval
done
fi
rm -f "${arkserverroot}/${arkwarnlockfile}"
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 bgupdate=
local updatetype=normal
local validate=
local modupdate=
local saveworld=
local downloadonly=
local nodownload=
local noautostart=
local use_systemd=
local use_service=
local use_upstart=
local force=
local safeupdate=
local appbeta=
local appbetapass=
local mapfile=
for arg in "$@"; do
case "$arg" in
--force) appupdate=1; force=1; ;;
--safe) safeupdate=1; ;;
--warn) updatetype=warn; ;;
--ifempty) updatetype=ifempty; ;;
--warnreason=*) warnreason="${arg#--warnreason=}"; updatetype=warn; ;;
--validate) validate=validate; appupdate=1; force=1; ;;
--saveworld) saveworld=1; ;;
--update-mods) modupdate=1; ;;
--backup) arkBackupPreUpdate=true; ;;
--no-autostart) noautostart=1; ;;
--stagingdir=*) arkStagingDir="${arg#--stagingdir=}"; ;;
--downloadonly) downloadonly=1; ;;
--no-download) nodownload=1; ;;
--systemd) use_systemd=1; ;;
--service) use_service=1; ;;
--upstart) use_upstart=1; ;;
--beta=*) appbeta="${arg#--beta=}"; ;;
--betapassword=*) appbetapass="${arg#--betapassword=}"; ;;
*)
echo "Unrecognized option $arg"
echo "Try 'arkmanager -h' or 'arkmanager --help' for more information."
exit 1
esac
done
# 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
echo "${BASHPID}" >"${arkserverroot}/${arkupdatelockfile}.${BASHPID}" 2>/dev/null
while true; do
if ! ln "${arkserverroot}/${arkupdatelockfile}.${BASHPID}" "${arkserverroot}/${arkupdatelockfile}" 2>/dev/null; then
local lockpid="$(<"${arkserverroot}/${arkupdatelockfile}")"
if [ -n "$lockpid" ] && [ "$lockpid" != "${BASHPID}" ] && kill -0 "$lockpid" 2>/dev/null; then
logprint "Update already in progress (PID: $lockpid)"
rm -f "${arkserverroot}/${arkupdatelockfile}.${BASHPID}" 2>/dev/null
return 1
fi
rm -f "${arkserverroot}/${arkupdatelockfile}"
else
break
fi
done
rm -f "${arkserverroot}/${arkupdatelockfile}.${BASHPID}"
logprint "Checking for update; PID: ${BASHPID}"
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/${arkserverdir}" ]; then
logprint "Copying to staging directory"
mkdir -p "$arkStagingDir"
if [ "$(stat -c "%d" "$arkserverroot")" == "$(stat -c "%d" "$arkStagingDir")" ]; then
if [ -n "$useRefLinks" ]; then
cp -a --reflink=always "$arkserverroot/${arkserverdir}/." "$arkStagingDir/${arkserverdir}"
cp -a --reflink=always "$arkserverroot/Engine/." "$arkStagingDir/Engine"
cp -a --reflink=always "$arkserverroot/linux64/." "$arkStagingDir/linux64"
cp -a --reflink=always "$arkserverroot/${arkserverdir}/Content/Mods/111111111/." "$arkStagingDir/${arkserverdir}/Content/Mods/111111111"
cp --reflink=always "$arkserverroot/${arkserverdir}/Content/Mods/111111111.mod" "$arkStagingDir/${arkserverdir}/Content/Mods/111111111.mod"
cp --reflink=always "$arkserverroot/"* "$arkStagingDir" >/dev/null 2>&1
cp -a --reflink=always "$arkserverroot/steamapps/." "$arkStagingDir/steamapps"
else
cp -al "$arkserverroot/${arkserverdir}/." "$arkStagingDir/${arkserverdir}"
cp -al "$arkserverroot/Engine/." "$arkStagingDir/Engine"
cp -al "$arkserverroot/linux64/." "$arkStagingDir/linux64"
cp -al "$arkserverroot/${arkserverdir}/Content/Mods/111111111/." "$arkStagingDir/${arkserverdir}/Content/Mods/111111111"
cp -l "$arkserverroot/${arkserverdir}/Content/Mods/111111111.mod" "$arkStagingDir/${arkserverdir}/Content/Mods/111111111.mod"
cp -l "$arkserverroot/"* "$arkStagingDir" >/dev/null 2>&1
cp -a "$arkserverroot/steamapps/." "$arkStagingDir/steamapps"
fi
else
rsync -a "$arkserverroot/." "$arkStagingDir/."
fi
rm -rf "$arkStagingDir/${arkserverdir}/Content/Mods/"*
rm -rf "$arkStagingDir/${arkserverdir}/Saved/"*
rm -rf "$arkStagingDir/Engine/Binaries/ThirdParty/SteamCMD/Linux/steamapps"
fi
rm -f "$arkStagingDir/${arkserverdir}/Saved/".[^.]*
rm -f "$arkStagingDir/${arkserverdir}/Binaries/Linux/"*.txt
if [ -z "$nodownload" ]; then
echo -n "Downloading ARK update"
logprint "Downloading ARK update" >/dev/null
doDownloadSteamCMD
cd "$steamcmdroot"
runSteamCMDAppUpdate "$arkStagingDir" ${appbeta:+-beta} $appbeta ${appbetapass:+-betapassword} $appbetapass $validate
if [ $? -eq 0 ]; then
rm -rf "${arkStagingDir}/steamapps/downloading/${appid}"
elif [ $? -eq 5 ]; then
echo "User ${steamlogin:-anonymous} login failed - please login to steamcmd manually"
fi
if [ -d "${arkStagingDir}/steamapps/downloading/${appid}" ]; then
logprint "Update download interrupted"
return 1
fi
local curver="$(getCurrentVersion)"
local newver="$(getStagingVersion)"
local nextver="$(getAvailableVersion)"
if [[ -z "${newver}" || "$curver" == "$newver" ]]; then
if [ -z "$force" ]; then
logprint "Update download unsuccessful"
return 1
elif [ "${newver}" != "${nextver}" ]; then
logprint "Warning: staging directory update was unsuccessful"
fi
fi
fi
fi
fi
if [[ -f "$arkserverroot/$arkmanagerpidfile" && "$arkserverroot/$arkmanagerpidfile" -ot "${arkserverroot}/${arkupdatetimefile}" ]]; then
local mgrpid="$(<"$arkserverroot/$arkmanagerpidfile")"
if (( mgrpid != 0 )); then
logprint "Server was updated while it was running"
bgupdate=1
fi
fi
if [ -n "$downloadonly" ]; then
if [ -n "$appupdate" -a -n "$arkStagingDir" -a "$arkStagingDir" != "$arkserverroot" ]; then
logprint "Server update downloaded"
fi
if [ -n "$modupdate" ]; then
logprint "Mod update downloaded"
fi
logprint "Not applying update - download-only requested"
elif [ -n "$appupdate" -o -n "$modupdate" -o -n "$bgupdate" ]; then
if false && [ -f "$arkserverroot/version.txt" ]; then
arkversion="$(<"$arkserverroot/version.txt")"
else
arkversion="$(getCurrentVersion)"
fi
if isTheServerRunning; then
if [ "$updatetype" == "warn" ]; then
if ! doWarn update; then
return 1
fi
elif [ "$updatetype" == "ifempty" ]; then
numplayers=$(( $(numPlayersConnected) + 0 ))
if (( numplayers != 0 )); then
logprint "${numplayers} players are still connected"
return 1
fi
fi
if [ -n "$safeupdate" ]; then
logprint "Saving world"
doSaveWorld
saverootdir="${arkserverroot}/${arkserverdir}/Saved"
savedir="$(getSavedArksDirectory "${saverootdir}")"
mapname="$(getServerMapName)"
maxwait=30
if [ -z "$savedir" ]; then
logprint "Unable to find saved arks directory"
else
mapfile="${savedir}/${mapname}.ark"
if [ ! -f "${mapfile}" ]; then
sleep 2
fi
if [ ! -f "${mapfile}" ]; then
if [ -f "${mapfile%.ark}.tmp" ]; then
logprint "Saved ark file doesn't exist, but temporary file does"
else
logprint "Unable to find saved ark file"
fi
else
for (( i = 0; i < maxwait; i++ )); do
if [ "$(find "${mapfile}" -mmin -1)" ]; then
break
fi
logprint "Save file older than 1 minute. Delaying update."
sleep 30s
done
logprint "Save file newer than 1 minute. Performing an update."
fi
fi
fi
fi
if [ -n "$saveworld" ]; then
logprint "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
logprint "Applying update from staging directory"
if [ "$(stat -c "%d" "$arkserverroot")" == "$(stat -c "%d" "$arkStagingDir")" ]; then
if [ -n "$useRefLinks" ]; then
cp -au --reflink=always --remove-destination "$arkStagingDir/${arkserverdir}/." "$arkserverroot/${arkserverdir}"
cp -au --reflink=always --remove-destination "$arkStagingDir/Engine/." "$arkserverroot/Engine"
cp -au --reflink=always --remove-destination "$arkStagingDir/linux64/." "$arkserverroot/linux64"
cp -u --reflink=always --remove-destination "$arkStagingDir/"* "$arkserverroot" >/dev/null 2>&1
cp -au --reflink=always --remove-destination "$arkStagingDir/steamapps/." "$arkserverroot/steamapps"
else
cp -alu --remove-destination "$arkStagingDir/${arkserverdir}/." "$arkserverroot/${arkserverdir}"
cp -alu --remove-destination "$arkStagingDir/Engine/." "$arkserverroot/Engine"
cp -alu --remove-destination "$arkStagingDir/linux64/." "$arkserverroot/linux64"
cp -lu --remove-destination "$arkStagingDir/"* "$arkserverroot" >/dev/null 2>&1
cp -au --remove-destination "$arkStagingDir/steamapps/." "$arkserverroot/steamapps"
fi
else
rsync -a "$arkStagingDir/." "$arkserverroot"
fi
cd "$arkserverroot"
find Engine ${arkserverdir} linux64 -depth -print |
grep -v '^\('"${arkserverdir}"'/\(Saved\|Content/Mods\|Binaries/Linux/.*\.txt\)\|Engine/Binaries/ThirdParty/SteamCMD/Linux/steamapps\)' |
while read f; do
if [ ! -e "${arkStagingDir}/${f}" ]; then
if [ -f "$f" ]; then
rm "${f}"
else
rmdir "${f}"
fi
fi
done
for f in *; do
if [[ -f "${f}" && ! -e "${arkStagingDir}/${f}" ]]; then
rm "${f}"
fi
done
else
echo -n "Performing ARK update"
logprint "Performing ARK update" >/dev/null
doDownloadSteamCMD
cd "$steamcmdroot"
runSteamCMDAppUpdate "$arkserverroot" ${appbeta:+-beta} $appbeta ${appbetapass:+-betapassword} $appbetapass $validate
if [ $? -eq 5 ]; then
echo "User ${steamlogin:-anonymous} login failed - please login to steamcmd manually"
fi
fi
# the current version should be the last version. We set our version
instver="$(getCurrentVersion)"
logprint "Update to $instver complete"
fi
if [ -n "$modupdate" ] && [ -z "$arkflag_automanagedmods" ]; then
for modid in $(getModIds); do
if isModUpdateNeeded $modid; then
logprint "Updating mod $modid"
doExtractMod $modid
logprint "Mod $modid updated"
fi
done
fi
if [ -z "$bgupdate" ]; then
touch "${arkserverroot}/${arkupdatetimefile}"
fi
else
logprint "Your server is already up to date! The most recent version is ${bnumber}."
fi;
rm -f "${arkserverroot}/${arkupdatelockfile}"
if ! isTheServerRunning; then
# we restart the server only if it was started before the update
if [ -z "$noautostart" ]; then
if [ $serverWasAlive -eq 1 ] || [ -f "${arkserverroot}/.startAfterUpdate-${instance}" ]; then
rm -f "${arkserverroot}/.startAfterUpdate-${instance}"
if [ -n "$use_systemd" ]; then
sudo systemctl start "arkmanager@$instance"
elif [ -n "$use_service" ]; then
if [ -f "/etc/init.d/arkmanager" ]; then
sudo "/etc/init.d/arkmanager" start "$instance"
elif [ -f "/etc/rc.d/init.d/arkmanager" ]; then
sudo "/etc/rc.d/init.d/arkmanager" start "$instance"
fi
elif [ -n "$use_upstart" ]; then
sudo start arkmanager "service=$instance"
else
doStart --noautoupdate
fi
fi
fi
fi
}
#
# Check if any mod update is available
#
checkForModUpdate(){
local updateavail=
local instmft=
local availmft=
local modname=
local steamworkshopdir="$(getSteamWorkshopDir)"
local cancheckmodavail=1
local modmissing=
local revstatcode=
local alluptodate=1
if [[ " $* " =~ " --revstatus " ]]; then
revstatcode=1
fi
if [ ! -d "${steamworkshopdir}" ]; then
echo "Error: ${steamworkshopdir} does not exist"
if [ -n "$revstatcode" ]; then return 4; else return 0; fi
fi
if [ ! -f "${steamworkshopdir}/appworkshop_${mod_appid}.acf" ]; then
echo "Error: appworkshop_${mod_appid}.acf not found at ${steamworkshopdir}"
cancheckmodavail=
fi
for modid in $(getModIds); do
availupd="$(getAvailModLastUpdated "$modid")"
instupd="$(getInstalledModLastUpdated "$modid")"
modname="$(getModName $modid)"
if [ -z "$availupd" ]; then
printf "Mod %d doesn't exist in the steam workshop\n" "$modid"
modmissing=1
elif [ "$instupd" != "$availupd" ]; then
alluptodate=
if [ -n "$cancheckmodavail" ]; then
localupd="$(getLocalModLastUpdated "$modid")"
if [ -z "$localupd" ]; then
printf "Mod %d [%s] has not been downloaded\n" "$modid" "$modname"
updateavail=1
elif [ "$availupd" != "$localupd" ]; then
printf "Mod %d [%s] has been updated on the Steam workshop\n" "$modid" "$modname"
printf "Local last updated: %s\nSteam last updated: %s\n" "$(date --date="@$localupd")" "$(date --date="@$availupd")"
updateavail=1
fi
fi
fi
if [ ! -d "$arkserverroot/${arkserverdir}/Content/Mods/$modid" ]; then
printf "Mod %d [%s] is not installed\n" "$modid" "$modname"
updateavail=1
elif [ "$instupd" != "$availupd" ]; then
if isModUpdateNeeded $modid; then
printf "Mod %d [%s] update needs to be applied\n" "$modid" "$modname"
updateavail=1
elif [ -z "$cancheckmodavail" ]; then
printf "Mod %d [%s] update needs to be downloaded and applied\n" "$modid" "$modname"
updateavail=1
fi
fi
done
if [ -n "$updateavail" ]; then
echo "One or more updates are available"
if [ -n "$revstatcode" ]; then return 1; else return 0; fi
elif [ -n "$alluptodate" ]; then
echo "All mods are up to date"
if [ -n "$revstatcode" ]; then return 0; else return 1; fi
elif [ -z "$cancheckmodavail" ]; then
echo "Cannot check if mods are up to date"
if [ -n "$revstatcode" ]; then return 3; else return 0; fi
elif [ -n "$modmissing" ]; then
echo "One or more mods are unavailable"
return 2
else
echo "All mods are up to date"
if [ -n "$revstatcode" ]; then return 0; else return 1; fi
fi
}
#
# 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
if [ -z "$ignoreInstalledMods" ]; then
find "${arkserverroot}/${arkserverdir}/Content/Mods" -maxdepth 1 -type d -printf "%P\n"
fi
) | sort | uniq | grep '^[1-9][0-9]*$' | grep -v '^111111111$'
}
#
# Get the Mod details of the installed mods and the requested mods
#
listMods(){
local modlist
local modid moddir moddesc
declare -A modlist
if [ -n "${serverMapModId}" ]; then
modlist[${serverMapModId}]="serverMapModId"
fi
if [ -n "${ark_TotalConversionMod}" ]; then
modlist[${ark_TotalConversionMod}]="ark_TotalConversionMod"
fi
if [ -n "${ark_GameModIds}" ]; then
for modid in ${ark_GameModIds//,/ }; do
modlist[${modid}]="ark_GameModIds"
done
fi
for modid in "${!arkmod_@}"; do
if [ "${!modid}" != "disabled" ]; then
modlist[${modid#arkmod_}]="${modid}"
fi
done
if [ -z "$ignoreInstalledMods" ]; then
for moddir in "${arkserverroot}/${arkserverdir}/Content/Mods"/*; do
modid="${moddir##*/}"
if [[ "${modid}" =~ ^[1-9][0-9]*$ && "${modid}" != "111111111" ]]; then
modlist[${moddir##*/}]="Content/Mods"
fi
done
fi
for modid in "${!modlist[@]}"; do
printf " %10d: %-20s %s\n" "$modid" "[${modlist[$modid]}]" "$(getModName $modid 2>/dev/null)"
done
}
#
# Gets installed mod last update timestamp
#
getInstalledModLastUpdated(){
local modid="$1"
if [ ! -f "${arkserverroot}/${arkserverdir}/Content/Mods/${modid}.mod" ]; then return 0; fi
if [ -f "${arkserverroot}/${arkserverdir}/Content/Mods/${modid}/__modversion__.info" ]; then
cat "${arkserverroot}/${arkserverdir}/Content/Mods/${modid}/__modversion__.info"
else
stat -c "%Y" "${arkserverroot}/${arkserverdir}/Content/Mods/${modid}.mod"
fi
}
#
# Gets local mod last update timestamp
#
getLocalModLastUpdated(){
local modid="$1"
local steamworkshopdir="$(getSteamWorkshopDir)"
if [ ! -f "${steamworkshopdir}/appworkshop_${mod_appid}.acf" ]; then return 0; fi
local instupd="$(sed -n '/^\t"WorkshopItemsInstalled"$/,/^\t[}]$/{/^\t\t"'"${modid}"'"$/,/^\t\t[}]$/{s|^\t\t\t"timeupdated"\t\t"\(.*\)"$|\1|p}}' <"${steamworkshopdir}/appworkshop_${mod_appid}.acf")"
echo "$instupd"
}
#
# Gets available mod last update timestamp
#
getAvailModLastUpdated(){
local modid="$1"
local serverresp="$(curl -s -d "itemcount=1&publishedfileids[0]=${modid}" "http://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1")"
local remupd=
if [[ "$serverresp" =~ \"time_updated\":[[:space:]]*([^,]*) ]]; then
remupd="${BASH_REMATCH[1]}"
fi
echo "$remupd"
}
#
# Checks if a mod update is available before trying to download it
isModUpdateAvailable(){
local modid="$1"
local localupd="$(getLocalModLastUpdated "$modid")"
local remupd="$(getAvailModLastUpdated "$modid")"
local instupd="$(getInstalledModLastUpdated "$modid")"
if [[ -n "$remupd" && "$instupd" == "$remupd" ]]; then
return 1
fi
local steamworkshopdir="$(getSteamWorkshopDir)"
if [ ! -f "${steamworkshopdir}/appworkshop_${mod_appid}.acf" ]; then return 0; fi
if [[ -n "${remupd}" && "${localupd}" != "${remupd}" ]]; then
return 0 # true
fi
return 1 # false
}
#
# 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
doDownloadSteamCMD
local steamworkshopdir="$(getSteamWorkshopDir)"
local steamcmd_workshoplog="$(getSteamWorkshopLog)"
local modsrcdir="${steamworkshopdir}/content/$mod_appid/$modid"
local moddldir="${steamworkshopdir}/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
elif [ $result -eq 5 ]; then
echo "User ${steamlogin:-anonymous} login failed - please login to steamcmd manually"
return 1
else
echo
failedmod="$(tail -n 20 "${steamcmd_workshoplog}" | sed -n 's|.* Download item \([0-9]*\) result : \(.*\)|\1\t\2|p' | grep -v $'\tOK' | tail -n1 | cut -f1)"
if [[ -n "$failedmod" && "$failedmod" != "$modid" ]]; then
echo "Mod $failedmod prevented mod update - removing failed mod"
doRemoveMods "$failedmod"
elif [ ! -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(){
local fail=0
local success=0
for modid in $(getModIds); do
if isModUpdateAvailable $modid; then
if doDownloadMod $modid; then
success=1
else
fail=1
fi
fi
done
[[ $success == 1 || $fail == 0 ]] && return 0 || return 1
}
#
# Checks if the files a mod owns need to be updated
#
isModUpdateNeeded(){
local modid=$1
local steamworkshopdir="$(getSteamWorkshopDir)"
local modsrcdir="${steamworkshopdir}/content/$mod_appid/$modid"
local moddestdir="$arkserverroot/${arkserverdir}/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 steamworkshopdir="$(getSteamWorkshopDir)"
local modsrcdir="${steamworkshopdir}/content/$mod_appid/$modid"
if [ -n "${modsrcdirs[$modid]}" ]; then
modsrcdir="${modsrcdirs[$modid]}"
fi
modname="$(curl -s "https://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 steamworkshopdir="$(getSteamWorkshopDir)"
local modsrcdir="${steamworkshopdir}/content/$mod_appid/$modid"
local moddestdir="$arkserverroot/${arkserverdir}/Content/Mods/$modid"
local modextractdir="$moddestdir"
local modbranch="${mod_branch:-Windows}"
if [[ -n "$arkStagingDir" && -d "$arkStagingDir" ]]; then
modextractdir="$arkStagingDir/Mods/$modid"
fi
# 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 "$modextractdir/.modbranch" ]; then
mv "$modextractdir/.modbranch" "$modextractdir/__arkmanager_modbranch__.info"
fi
if [ \( ! -f "$modextractdir/__arkmanager_modbranch__.info" \) ] || [ "$(<"$modextractdir/__arkmanager_modbranch__.info")" != "$modbranch" ]; then
rm -rf "$modextractdir"
fi
if [ -f "$modsrcdir/mod.info" ]; then
local modupdatetime="$(getLocalModLastUpdated "$modid")"
echo "Copying files to $modextractdir"
if [ -f "$modsrcdir/${modbranch}NoEditor/mod.info" ]; then
modsrcdir="$modsrcdir/${modbranch}NoEditor"
fi
find "$modsrcdir" -type d -printf "$modextractdir/%P\0" | xargs -0 -r mkdir -p
find "$modextractdir" -type f ! -name '.*' -printf "%P\n" | while read f; do
if [ \( ! -f "$modsrcdir/$f" \) -a \( ! -f "$modsrcdir/${f}.z" \) ]; then
rm "$modextractdir/$f"
fi
done
find "$modextractdir" -depth -type d -printf "%P\n" | while read d; do
if [ ! -d "$modsrcdir/$d" ]; then
rmdir "$modextractdir/$d"
fi
done
find "$modsrcdir" -type f ! \( -name '*.z' -or -name '*.z.uncompressed_size' \) -printf "%P\n" | while read f; do
if [ \( ! -f "$modextractdir/$f" \) -o "$modsrcdir/$f" -nt "$modextractdir/$f" ]; then
printf "%10d %s " "`stat -c '%s' "$modsrcdir/$f"`" "$f"
if [[ -n "$useRefLinks" && "$(stat -c "%d" "$modsrcdir")" == "$(stat -c "%d" "$modextractdir")" ]]; then
cp --reflink=auto "$modsrcdir/$f" "$modextractdir/$f"
else
cp "$modsrcdir/$f" "$modextractdir/$f"
fi
echo -ne "\r\\033[K"
fi
done
find "$modsrcdir" -type f -name '*.z' -printf "%P\n" | while read f; do
if [ \( ! -f "$modextractdir/${f%.z}" \) -o "$modsrcdir/$f" -nt "$modextractdir/${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" >"$modextractdir/${f%.z}"
touch -c -r "$modsrcdir/$f" "$modextractdir/${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 "${modextractdir}/.mod" ]; then
rm "${modextractdir}/.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[2] || $mapname) . "\x00";
my $modnamelen = length($modname);
my $modpath = "../../../" . $ARGV[0] . "/Content/Mods/" . $ARGV[1] . "\x00";
my $modpathlen = length($modpath);
print pack("L< L< L< Z$modnamelen L< Z$modpathlen L<",
$ARGV[1], 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";
' "$arkserverdir" "$modid" "$modname" <"$modextractdir/mod.info" >"${modextractdir}.mod"
if [ -f "$modextractdir/modmeta.info" ]; then
cat "$modextractdir/modmeta.info" >>"${modextractdir}.mod"
else
echo -ne '\x01\x00\x00\x00\x08\x00\x00\x00ModType\x00\x02\x00\x00\x001\x00' >>"${modextractdir}.mod"
fi
if [ -n "${modupdatetime}" ]; then
touch -c --date="@${modupdatetime}" -- "${modextractdir}.mod"
echo "${modupdatetime}" >"${modextractdir}/__modversion__.info"
fi
echo "$modbranch" >"$modextractdir/__arkmanager_modbranch__.info"
if [[ "$modextractdir" != "$moddestdir" ]]; then
if [ ! -d "${moddestdir}" ]; then
mkdir -p "${moddestdir}"
fi
if [ "$(stat -c "%d" "$modextractdir")" == "$(stat -c "%d" "$moddestdir")" ]; then
if [ -n "$useRefLinks" ]; then
cp -au --reflink=always --remove-destination "${modextractdir}/." "${moddestdir}"
else
cp -alu --remove-destination "${modextractdir}/." "${moddestdir}"
fi
else
cp -au --remove-destination "${modextractdir}/." "${moddestdir}"
fi
find "${moddestdir}" -type f ! -name '.*' -printf "%P\n" | while read f; do
if [ ! -f "${modextractdir}/${f}" ]; then
rm "${moddestdir}/${f}"
fi
done
find "$modextractdir" -depth -type d -printf "%P\n" | while read d; do
if [ ! -d "$modsrcdir/$d" ]; then
rmdir "$modextractdir/$d"
fi
done
if [[ -n "$useRefLinks" && "$(stat -c "%d" "$modextractdir")" == "$(stat -c "%d" "$moddestdir")" ]]; then
cp -u --reflink=always "${modextractdir}.mod" "${moddestdir}.mod"
else
cp -u "${modextractdir}.mod" "${moddestdir}.mod"
fi
fi
fi
}
#
# Downloads mod and installs it into mods directory
#
doInstallMod(){
local modid
local steamworkshopdir="$(getSteamWorkshopDir)"
for modid in ${1//,/ }; do
if [[ " $* " =~ *" --validate " ]]; then
if [ -f "${steamworkshopdir}/appworkshop_${mod_appid}.acf" ]; then
sed -i "/^\\t\\t\"${modid}\"/,/^\\t\\t}/d" "${steamworkshopdir}/appworkshop_${mod_appid}.acf"
fi
fi
if doDownloadMod $modid; then
doExtractMod $modid
echo "Mod $modid installed"
fi
done
}
#
# Downloads and installs all requested mods
#
doInstallAllMods(){
for modid in $(getModIds); do
doInstallMod "$modid" "$@"
done
}
#
# Removes all mods from the mods directory
#
doUninstallAllMods(){
for modid in $(getModIds); do
if [[ "$modid" != "111111111" && "$modid" != "TheCenter" ]]; then
doUninstallMod "$modid"
fi
done
}
#
# Removes mod from mods directory
#
doUninstallMod(){
local modid
for modid in ${1//,/ }; do
local moddir="$arkserverroot/${arkserverdir}/Content/Mods/$modid"
local modfile="$arkserverroot/${arkserverdir}/Content/Mods/${modid}.mod"
if [ -d "${moddir}" ]; then
rm -rf "${moddir}"
fi
if [ -f "${modfile}" ]; then
rm -f "$modfile"
fi
done
}
#
# 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
local steamworkshopdir="$(getSteamWorkshopDir)"
for modid in ${1//,/ }; do
if [ -f "${steamworkshopdir}/appworkshop_${mod_appid}.acf" ]; then
sed -i "/^\\t\\t\"${modid}\"/,/^\\t\\t}/d" "${steamworkshopdir}/appworkshop_${mod_appid}.acf"
fi
if [ -d "${steamworkshopdir}/content/${mod_appid}/${modid}" ]; then
rm -rf "${steamworkshopdir}/content/${mod_appid}/${modid}"
fi
if [ -d "${steamworkshopdir}/downloads/${mod_appid}/${modid}" ]; then
rm -rf "${steamworkshopdir}/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 saverootdir="${arkserverroot}/${arkserverdir}/Saved"
local savedcfgdir="${saverootdir}/Config/LinuxServer"
local savedir="$(getSavedArksDirectory "${saverootdir}")"
local mapname="$(getServerMapName)"
local backupfile
mkdir -p "$backupdir"
mkdir -p "$backupdirdaily"
for arg in "$@"; do
case "$arg" in
--allmaps) backupAllMaps=true; ;;
--no-allmaps) backupAllMaps=false; ;;
--autobackups) includeAutoBackups=true; ;;
--no-autobackups) includeAutoBackups=false; ;;
*)
echo "Unrecognized option $arg"
echo "Try 'arkmanager -h' or 'arkmanager --help' for more information."
exit 1
esac
done
if [[ -z "$savedir" ]]; then
return 1
fi
echo "${NORMAL} Saved arks directory is ${savedir}"
# ARK server uses Write-Unlink-Rename
echo -ne "${NORMAL} Copying ARK world file (${mapname}) "
local mapfile="${savedir}/${mapname}.ark"
cp -p "${mapfile}" "${backupdir}/${mapname}.ark"
if [ ! -f "${backupdir}/${mapname}.ark" ]; then
sleep 2
cp -p "${mapfile}" "${backupdir}/${mapname}.ark"
fi
if [ ! -f "${backupdir}/${mapname}.ark" ]; then
cp -p "${mapfile%.ark}.tmp" "${backupdir}/${mapname}.ark"
if [ -f "${backupdir}/${mapname}.ark" ]; then
echo "${NORMAL}\e[68G[ ${YELLOW}WARN${NORMAL} ]"
logprint "Saved ark file not found, but temporary file was"
else
echo "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]"
cimapfile="$(find "${savedir}" -maxdepth 1 -iname "${mapname}.ark" -or -iname "${mapname}.tmp" | head -n1)"
if [ -n "${mapfile}" ]; then
logprint "Inconsistent casing in map name - ${mapname}.ark does not exist, but ${cimapfile##*/} does"
else
logprint "Saved ark file could not be found"
logprint "Candidates:"
for f in "${savedir}"/*.ark; do
if [ "${f}" == "${f%_??.??.????_??.??.??.ark}" ]; then # Exclude auto-backups
logprint " Saved ARK ${f}"
fi
done
fi
fi
else
echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]"
fi
# Copy other world files
if [ -n "$backupAllMaps" ] && [ "$backupAllMaps" != "false" ]; then
echo -e "${NORMAL} Copying other ARK world files"
for f in "${savedir}/"*.ark; do
if [[ "${f}" != "${mapfile}" && "${f}" != "${savedir}/"*_??.??.????_??.??.??.ark ]]; then
cp -p "${f}" "${backupdir}/${f##*/}"
fi
done
fi
# Copy map autobackups
if [ -n "$includeAutoBackups" ] && [ "$includeAutoBackups" != "false" ]; then
echo -e "${NORMAL} Copying all ARK world autobackup files"
for f in "${savedir}/"*.ark; do
if [[ "${f}" == "${savedir}/"*_??.??.????_??.??.??.ark ]]; then
cp -p "${f}" "${backupdir}/${f##*/}"
fi
done
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
if [ -f "${f}" ]; then
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##*/}" && -f "${f%.arkprofile}.tmpprofile" ]]; 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
fi
done
# ARK server uses Lock-Truncate-Write-Unlock
echo -e "${NORMAL} Copying ARK tribe files "
for f in "${savedir}/"*.arktribe; do
if [ -f "${f}" ]; then
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##*/}" && -f "${f%.arktribe}.tmptribe" ]]; 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
fi
done
# ARK server uses Lock-Truncate-Write-Unlock
echo -e "${NORMAL} Copying ARK tribute tribe files "
for f in "${savedir}/"*.arktributetribe; do
if [ -f "${f}" ]; then
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##*/}" && -f "${f%.arktributetribe}.tmptributetribe" ]]; then
cp -p "${f%.arktributetribe}.tmptributetribe" "${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
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 "
backupfile="${arkbackupdir}/${daystamp}/${instance}.${datestamp}.tar.bz2"
tar -jcf "${backupfile}" -C "${arkbackupdir}" "${datestamp}"
rm -rf ${backupdir}
if [ -f "${backupfile}" ]; 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} ${instance}.${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
if [ -n "${arkPostBsckupCommand}" ] && [ -f "${backupfile}" ]; then
eval " ${arkPostBsckupCommand}"
fi
}
#
# Install a cron job to execute a particular command
#
doInstallCronJob(){
hour='*'
minute='0'
cmdopts="${arkCronExtraOpts}"
cmdargs=""
instargs=""
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="${cmdargs} $(printf "%q" "${opt}")"
;;
esac
done
if [ -n "$allinstances" ]; then
instargs="@all"
else
for inst in "${instances[@]}"; do
instargs="${instargs} $(printf "%q" "@${inst}")"
done
fi
cronjob="${minute} ${hour} * * * ${arkmanagerpath} --cronjob ${command} ${instargs} ${cmdopts} --args ${cmdargs} -- ${output}"
(crontab -l | \
sed -e "\\# [*] [*] [*] ${arkmanagerpath} --cronjob ${command} ${instargs} #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"
echo -e "$NORMAL" "Server PID: " "$GREEN" "$(getServerPID)" "$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)" "$(getMultiHomeIP)"
if isTheServerOnline; then
echo -e "$NORMAL" "Server online: " "$GREEN" "Yes" "$NORMAL"
echo -e "$NORMAL" "ARKServers link: " "$GREEN" "http://arkservers.net/server/${publicip}:$(getQueryPort)" "$NORMAL"
echo -e "$NORMAL" "Steam connect link: " "$GREEN" "steam://connect/${publicip}:$(getQueryPort)" "$NORMAL"
else
echo -e "$NORMAL" "Server online: " "$RED" "No" "$NORMAL"
fi
fi
instver="$(getCurrentVersion)"
echo -e "$NORMAL" "Server build ID: " "$GREEN" $instver "$NORMAL"
instbeta="$(getCurrentBranch)"
if [ -n "$instbeta" ]; then
echo -e "$NORMAL" "Server branch: " "$GREEN" "$instbeta" "$NORMAL"
fi
if [ -f "$arkserverroot/version.txt" ]; then
echo -e "$NORMAL" "Server version: " "$GREEN" "$(<"$arkserverroot/version.txt")" "$NORMAL"
fi
}
doWait(){
local waitall=
local waitany=
local waitonline=
local waitstopped=
declare -A pidfiles
for arg in "$@"; do
case "$arg" in
--all) waitall=1; ;;
--any) waitany=1; ;;
--online) waitonline=1; ;;
--stopped) waitstopped=1; ;;
esac
done
if [[ -z "${waitany}" && -z "${waitall}" ]]; then
waitany=1
fi
if [[ -z "${waitonline}" && -z "${waitstopped}" ]]; then
waitstopped=1
fi
for instance in "${instances[@]}"; do
pidfile="$(useConfig "$instance"; echo "${arkserverroot}/${arkmanagerpidfile}")"
pidfiles[$instance]="$pidfile"
done
(
trap exit INT
trap exit TERM
while sleep 5; do
anyrunning=0
allrunning=1
anyonline=0
allonline=1
for instance in "${instances[@]}"; do
pidfile="${pidfiles[$instance]}"
if [ -f "${pidfile}" ]; then
pid="$(<"${pidfile}")"
if kill -0 "${pid}"; then
anyrunning=1
if [ -n "$waitonline" ]; then
if (useConfig "$instance"; isTheServerOnline); then
anyonline=1
else
allonline=0
fi
fi
else
allrunning=0
allonline=0
fi
fi
done
if [[ ( "${waitstopped}" == 1 && "${waitall}" == 1 && "${anyrunning}" == 0 ) ||
( "${waitstopped}" == 1 && "${waitany}" == 1 && "${allrunning}" == 0 ) ||
( "${waitonline}" == 1 && "${waitall}" == 1 && "${allonline}" == 1 ) ||
( "${waitonline}" == 1 && "${waitany}" == 1 && "${anyonline}" == 1 ) ]]; then
return
fi
done
)
}
getAllInstanceNames(){
declare -A instancenames
if [ -n "${defaultinstance}" ]; then
instancenames[${defaultinstance}]="${defaultinstance}"
echo "${defaultinstance}"
fi
if [ -f "${HOME}/${arkstUserCfgFile}" ]; then
while read l <&3; do
v="${l%%=*}"
if [[ "${v}" = configfile_* ]]; then
if [ -n "${!v}" ]; then
instancename="${v#configfile_}"
if [ -z "${instancenames[${instancename}]}" ]; then
instancenames[${instancename}]="${instancename}"
echo "${instancename}"
fi
fi
fi
done 3<"${HOME}/${arkstUserCfgFile}"
fi
if [ -f "${arkstGlobalCfgFile}" ]; then
while read l <&3; do
v="${l%%=*}"
if [[ "${v}" = configfile_* ]]; then
if [ -n "${!v}" ]; then
instancename="${v#configfile_}"
if [ -z "${instancenames[${instancename}]}" ]; then
instancenames[${instancename}]="${instancename}"
echo "${instancename}"
fi
fi
fi
done 3<"${arkstGlobalCfgFile}"
fi
for f in ${HOME}/.config/arkmanager/instances/*.cfg; do
if [ -f "${f}" ]; then
instancename="${f##*/}"
instancename="${instancename%.cfg}"
if [ -z "${instancenames[${instancename}]}" ]; then
instancenames[${instancename}]="${instancename}"
echo "${instancename}"
fi
fi
done
for f in /etc/arkmanager/instances/*.cfg; do
if [ -f "${f}" ]; then
instancename="${f##*/}"
instancename="${instancename%.cfg}"
if [ -z "${instancenames[${instancename}]}" ]; then
instancenames[${instancename}]="${instancename}"
echo "${instancename}"
fi
fi
done
}
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}/${arkstUserCfgFile}" "${arkstGlobalCfgFile}"; do
if [ -r "$cfgfile" ]; then
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")
fi
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" && "${HOME}/.config/arkmanager/instances/${1}.cfg" -nt "/etc/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" && -n "$arkSingleInstance" ]]; then
if [ -f "${HOME}/${arkstUserCfgFile}" ]; then
configfile="${HOME}/${arkstUserCfgFile}"
else
configfile="${arkstGlobalCfgFile}"
fi
else
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"
fi
if [ -z "$arkserverroot" ]; then
echo "Error: arkserverroot not set"
exit 1
fi
arkserverdir="${arkserverdir:-ShooterGame}"
arkautorestartfile="${arkautorestartfile:-${arkserverdir}/Saved/.autorestart-${1}}"
arkoldautorestartfile="${arkserverdir}/Saved/.autorestart"
arkserverpidfile="${arkserverpidfile:-${arkserverdir}/Saved/.arkserver-${1}.pid}"
arkserveroldpidfile="${arkserverdir}/Saved/.arkserver.pid"
arkmanagerpidfile="${arkmanagerpidfile:-${arkserverdir}/Saved/.arkmanager-${1}.pid}"
arkwarnlockfile="${arkwarnlockfile:-${arkserverdir}/Saved/.ark-warn-${1}.lock}"
# This is linked to the directory, not to the instance
arkupdatelockfile="${arkupdatelockfile:-${arkserverdir}/Saved/.ark-update.lock}"
arkupdatetimefile="${arkupdatetimefile:-${arkserverdir}/Saved/.ark-update.time}"
}
addArkOpt() {
local optname="${1%%=*}"
local optval="${1#*=}"
local optdash="${optname:0:1}"
optname="${optname#-}"
optname="${optname//[^A-Za-z0-9_]/_}"
if [[ "${optval}" == "${optname}" ]]; then
optval=""
fi
if [[ "${optdash}" == "-" ]]; then
if [[ -z "${optval}" ]]; then
IFS="" read -r "arkflag_${optname}" <<<"true"
else
IFS="" read -r "arkopt_${optname}" <<<"${optval}"
fi
else
IFS="" read -r "ark_${optname}" <<<"${optval}"
fi
}
showUsage() {
echo -e "Usage: arkmanager [Commands]\n"
cat <<-EOE
Commands can be followed by one or more @instance arguments
The special '@all' instance selects all instances
Commands may also be followed by zero or more --options
Commands that take no instances:
Command Description
upgrade-tools Check for a new ARK Server Tools version and upgrades it if needed
uninstall-tools Uninstall the ARK Server Tools
useconfig <name> Sets the default instance for the commands that follow
remove-mods <modid> Remove one or more mods from the steamcmd workshop directory
list-instances Lists all available instances
--help Show this help
--version Show the version info of ARK Server Tools
Commands that take one or more instances:
Command Description
backup Saves a backup of your server inside the backup directory
broadcast <msg> Sends a message to all users connected to server
saveworld Saves the game world to disk
rconcmd <cmd> Execute RCON command on server
checkupdate Check for a new ARK server version
checkmodupdate Checks for any mods needing updates
install Install the ARK server files from steamcmd
installmod <modid> Installs a mod from the Steam workshop
uninstallmod <modid> Removes the mod from the Mods directory
reinstallmod <modid> Removes and re-installs a mod in the Mods directory
enablemod <modid> Enables a mod in the config
disablemod <modid> Disables a mod in the config
installmods Installs all enabled mods
uninstallmods Removes all installed mods from the Mods directory
install-cronjob <cmd> Adds a cron job using the specified command
remove-cronjob <cmd> Removes a cron job that used the specified command
restart Stops the server and then starts it
run Runs the server without daemonizing
start Starts the server
stop Stops the server
cancelshutdown Aborts a running stop --warn or update --warn
status Returns the status of the current ARK server instance
printconfig Displays which settings are sourced from which config file
getpid Gets the running server PID
update Check for a new ARK server version, if needed, stops the server, updates it, and starts it again
Commands which invoke steamcmd take the following options:
--verbose Show steamcmd output
--spinner Use a spinner for progress (default)
--dots Use dots for progress
Update command takes the following options:
--force Apply update without checking the current version
--safe Wait for server to perform world save and update (deprecated, use --saveworld)
--warn Warn players before updating server
--ifempty Apply the update only if no players are connected
--validate Validates all ARK server files
--saveworld Saves world before update
--update-mods Updates installed and requested mods
--backup Takes a backup of the save files before updating
--downloadonly Download the mod and/or server update without applying it
Requires arkStagingDir be set to a staging directory on the same filesystem as the server
--no-download Applies an already downloaded update from the staging directory
--systemd Use the SystemD arkmanager@instance.service to restart the server
--service Use the sysv-init arkmanager service to restart the server
--upstart Use the Upstart arkmanager service to restart the server
--no-autostart Don't start the server after updating
--warnreason=... Specify an update reason other than the default
--beta=... Specify a beta to install (use --beta=public to reset)
Stop and restart commands take the following options:
--saveworld Saves world before shutdown
--warn Warn players before stopping server
--warnreason=... Specify a shutdown reason other than the default
Start and restart commands take the following options:
--noautoupdate Disable any automatic update that may be enabled in the config
--alwaysrestart Always restart server on crash, even if it did not finish starting
--no-background Run the server without going into the background
--arkopt,opt=val Add an ARK option:
-opt=val Equivalent to adding arkopt_opt=val to config for this run
-opt Equivalent to adding arkflag_opt=true to config for this run
opt=val Equivalent to adding ark_opt=val to config for this run
installmod command takes the following options:
--validate Forcibly re-download the mod from the steam workshop
install-cronjob command takes the following options:
--daily Run once a day (default)
--hourly Run once an hour
--hour=<hour> Hour of the day to run command
--minute=<min> Minute of the hour to run command
--enable-output Don't redirect the output of the command
--arg=option Option to pass to command
list-instances command takes the following options:
--brief Only list instance names
EOE
}
#---------------------
# Main program
#---------------------
main(){
# check the configuration and throw errors or warnings if needed
checkConfig
status=0
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; ;;
enablemod) nrarg=1; ;;
disablemod) nrarg=1; ;;
broadcast) nrarg=1; ;;
rconcmd) nrarg=1; ;;
notify) 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
;;
--arkopt,*)
addArkOpt "${1#--arkopt,}"
;;
--*)
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
if [ -n "${arkstTag}" ]; then
echo "Release Tag: ${arkstTag}"
fi
blobsize="$(sed "s@^\\(arkst\\(Commit\\|Tag\\|RootUseEnv\\)\\)=.*@\\1=''@" "$0" | wc -c)"
echo "Blob SHA: $( (echo -ne "blob ${blobsize}\0"; sed "s@^\\(arkst\\(Commit\\|Tag\\|RootUseEnv\\)\\)=.*@\\1=''@" "$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 cronjob commands specially
case "$command" in
install-cronjob)
doInstallCronJob "${args[@]}" "${options[@]}" "$@"
exit
;;
remove-cronjob)
doRemoveCronJob "${args[@]}"
exit
;;
esac
# Handle all instances being requested
if [[ "$allinstances" == "yes" ]]; then
instances=( $(getAllInstanceNames) )
fi
# Handle wait command specially
case "$command" in
wait)
doWait "${options[@]}"
continue
;;
esac
# Run the command for each instance requested
for instance in "${instances[@]}"; do
(
echo "Running command '${command}' for instance '${instance}'"
useConfig "$instance"
checkConfig "$command"
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
;;
checkmodupdate)
checkForModUpdate "${options[@]}"
;;
installmod)
doInstallMod "${args[@]}"
;;
enablemod)
doEnableMod "${args[@]}"
;;
disablemod)
doDisableMod "${args[@]}"
;;
installmods)
doInstallAllMods
;;
uninstallmods)
doUninstallAllMods
;;
uninstallmod)
doUninstallMod "${args[@]}"
;;
reinstallmod)
doUninstallMod "${args[@]}"
doInstallMod "${args[@]}"
;;
list-mods)
listMods
;;
backup)
doBackup "${options[@]}"
;;
broadcast)
doBroadcast "${args[@]}"
;;
notify)
notify "${args[@]}"
;;
saveworld)
doSaveWorld
;;
rconcmd)
rconcmd "${args[@]}"
;;
printconfig)
doPrintConfig
;;
status)
printStatus
;;
getpid)
echo `getServerPID`
;;
*)
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
(
echo "`timestamp`: restart" >> "$logdir/$arkmanagerLog"
useConfig "$instance"
doStart "${options[@]}"
)
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 [[ "$0" = "${BASH_SOURCE[0]}" || -z "${BASH_SOURCE}" ]]; then
main "$@"
fi