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