#!/bin/bash # ARK: survival evolved manager # # Original author: LeXaT # Maintainer: FezVrasta # Contributors: Sispheor, Atriusftw, klightspeed, lexat, puseidr # Check the user is not currently running this script as root if [ "$(id -u)" == "0" ]; then echo "This script must NOT be run as root" 1>&2 exit 1 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 arkmanagerLog="arkmanager.log" # here are logged the actions performed by arkmanager arkserverLog="arkserver.log" # here is logged the output of ShooterGameServer # Script version arkstVersion="1.3" arkstCommit='' #--------------------- # 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 ! getent passwd $steamcmd_user > /dev/null 2>&1 ; then echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tYour SteamCMD user is not valid." fi # Environment configuration # arkserverexec if [ ! -f "$arkserverroot/$arkserverexec" ] ; then echo -e "[" "$YELLOW" "WARN" "$NORMAL" "]" "\tYour ARK server exec could not be found." fi # Service configuration # logdir if [ ! -w "$logdir" ] ; then echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tYou have not rights to write in the log directory." fi } # # Get server admin password # getAdminPassword() { if [ -n "${ark_ServerAdminPassword}" ]; then echo "${ark_ServerAdminPassword}" else sed -ne '/^\[ServerSettings\]/,/^\[.*\]/{s/^ServerAdminPassword[[:space:]]*=[[:space:]]*//p;}' "${arkserverroot}/ShooterGame/Saved/Config/LinuxServer/GameUserSettings.ini" fi } # # Execute RCON command # rconcmd() { perl -MSocket -e ' sub sendpkt { my ($sock, $reqid, $reqtype, $body) = @_; my $packet = pack("VVV", length($body) + 10, $reqid, $reqtype) . $body . "\0\0"; send($sock, $packet, 0) or die "Error sending command to server"; } sub recvpkt { my ($sock) = @_; my $data = ""; recv($sock, $data, 12, 0); my ($pktlen, $resid, $restype) = unpack("VVV", $data); recv($sock, $data, $pktlen - 8, 0); return ($resid, $restype, substr($data, 0, $pktlen - 10)); } sub auth { my ($sock, $password) = @_; my $reqid = 1; sendpkt($sock, $reqid, 3, $password); my ($resid, $restype, $rcvbody) = recvpkt($sock); die "Authentication failed" if $resid == -1; } my $port = $ARGV[0]; my $password = $ARGV[1]; my $command = $ARGV[2]; 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("127.0.0.1")); 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"; ' "${ark_RCONPort}" "`getAdminPassword`" "$1" } # # Save world # doSaveWorld() { rconcmd saveworld } # # Exit cleanly # doExitServer() { rconcmd doexit } # # Broadcast message # doBroadcast(){ rconcmd "broadcast $1" >/dev/null } # # 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" 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!" fi } # # Check if the server need to be updated # Return 0 if update is needed, else return 1 # function isUpdateNeeded(){ getCurrentVersion getAvailableVersion if [ "$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 return $instver } # # Get the current available server version on steamdb # function getAvailableVersion(){ rm -f "$steamcmd_appinfocache" bnumber=`$steamcmdroot/$steamcmdexec +login anonymous +app_info_update 1 +app_info_print "$appid" +quit | while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".depots.branches.public" "buildid"; break; fi; done` return $bnumber } # # 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 and visible in steam server list # # function isTheServerUp(){ $lsof -i :"$ark_Port" > /dev/null result=$? # 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 } # # run function # doRun() { arkserveropts="$serverMap" if [ -n "$serverMapModId" ]; then arkserveropts="-MapModID=$serverMapModId" fi # 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}" 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 "$arkserverroot/$arkserverexec" "$arkserveropts" echo "`timestamp`: exited with status $?" } # # start function # doStart() { if isTheServerRunning; then echo "The server is already running" else 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 up" fi } # # stop the ARK server # doStop() { if isTheServerRunning; then tput sc echo "Stopping server..." echo "`timestamp`: stopping" >> "$logdir/$arkmanagerLog" # kill the server with the PID PID=`getServerPID` kill -INT $PID for (( i = 0; i < 10; 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 } # # install of ARK server # doInstall() { # Check if arkserverroot already exists if [ ! -d "$arkserverroot" ]; then # If it does not exist, try create it echo -e "Creating the ARK server root directory ($arkserverroot)" mkdir -p "$arkserverroot" if [ ! $? ] ; then echo -e "[" "$RED" "ERROR" "$NORMAL" "]" "\tFailed to create the defined ARK server root directory ($arkserverroot)" exit 1 fi fi cd "$steamcmdroot" # install the server ./$steamcmdexec +login anonymous +force_install_dir "$arkserverroot" +app_update $appid validate +quit # the current version should be the last version. We set our version getCurrentVersion } # # Stop the server, update it and then start it back. # doUpdate() { cd "$arkserverroot" if isUpdateNeeded; then forceUpdate else echo "Your server is already up to date! The most recent version is ${bnumber}." echo "`timestamp`: No update needed." >> "$logdir/update.log" fi; } forceUpdate(){ # 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 doStop cd "$steamcmdroot" ./$steamcmdexec +login anonymous +force_install_dir "$arkserverroot" +app_update $appid +quit # the current version should be the last version. We set our version getCurrentVersion echo "`timestamp`: update to $instver complete" >> "$logdir/update.log" # we restart the server only if it was started before the update if [ $serverWasAlive -eq 1 ]; then doStart fi } # # Waits for server to perform save before update (until save file is newer than 1 minute) # doSafeUpdate(){ cd "$arkserverroot" if isUpdateNeeded; 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" forceUpdate else echo "Your server is already up to date! The most recent version is ${bnumber}." echo "`timestamp`: No update needed." >> "$logdir/update.log" fi } # # Copies server state to a backup directory # doBackup(){ local datestamp=`date +"%Y-%m-%d_%H.%M.%S"` local backupdir="${arkbackupdir}/${datestamp}" mkdir -p "$backupdir" # extract the map name from the active map mod if [ -n "$serverMapModId" ]; then serverMap="$(perl -e ' my $data; { local $/; $data = <>; } my $mapnamelen = unpack("@0 L<", $data); my $mapname = substr($data, 4, $mapnamelen - 1); $mapnamelen += 4; my $mapfilelen = unpack("@" . ($mapnamelen + 4) . " L<", $data); my $mapfile = substr($data, $mapnamelen + 8, $mapfilelen - 1); print $mapfile; ' <"${arkserverroot}/ShooterGame/Content/Mods/${serverMapModId}/mod.info")" fi # ARK server uses Write-Unlink-Rename echo -ne "${NORMAL} Copying ARK world file " cp -p "${arkserverroot}/ShooterGame/Saved/SavedArks/${serverMap##*/}.ark" "${backupdir}/${serverMap##*/}.ark" if [ ! -f "${backupdir}/${serverMap##*/}.ark" ]; then sleep 2 cp -p "${arkserverroot}/ShooterGame/Saved/SavedArks/${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/SavedArks/${serverMap##*/}.tmp" "${backupdir##*/}/${serverMap##*/}.ark" fi if [ -f "${backupdir}/${serverMap##*/}.ark" ]; then echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" else echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" fi # ARK server uses Lock-Truncate-Write-Unlock # Unfortunately we can't lock the file, as # ARK server uses a non-blocking lock and will # fail to update the file if the lock fails. echo -e "${NORMAL} Copying ARK profile files" for f in "${arkserverroot}/ShooterGame/Saved/SavedArks/"*.arkprofile; do echo -ne "${NORMAL} ${f##*/} " cp -p "${f}" "${backupdir}/${f##*/}" if [ ! -s "${backupdir}/${f##*/}" ]; then sleep 2 cp -p "${f}" "${backupdir}/${f##*/}" fi # If both attempts fail, server may have # crashed between truncate and write if [ ! -s "${backupdir}/${f##*/}" ]; then cp -p "${f%.arkprofile}.tmpprofile" "${backupdir}/${f##*/}" fi if [ -s "${backupdir}/${f##*/}" ]; then echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" else echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" fi done # ARK server uses Lock-Truncate-Write-Unlock echo -e "${NORMAL} Copying ARK tribe files " for f in "${arkserverroot}/ShooterGame/Saved/SavedArks/"*.arktribe; do echo -ne "${NORMAL} ${f##*/} " cp -p "${f}" "${backupdir}/${f##*/}" if [ ! -s "${backupdir}/${f##*/}" ]; then sleep 2 cp -p "${f}" "${backupdir}/${f##*/}" fi # If both attempts fail, server may have # crashed between truncate and write if [ ! -s "${backupdir}/${f##*/}" ]; then cp -p "${f%.arktribe}.tmptribe" "${backupdir}/${f##*/}" fi if [ -s "${backupdir}/${f##*/}" ]; then echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" else echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" fi done # ARK server uses Lock-Truncate-Write-Unlock echo -ne "${NORMAL} Copying GameUserSettings.ini " cp -p "${arkserverroot}/ShooterGame/Saved/Config/LinuxServer/GameUserSettings.ini" "${backupdir}/GameUserSettings.ini" if [ ! -s "${backupdir}/GameUserSettings.ini" ]; then sleep 2 cp -p "${f}" "${backupdir}/${f##*/}" fi if [ -f "${backupdir}/GameUserSettings.ini" ]; then echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" else echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" fi } # # Print the status of the server (running? online? version?) # printStatus(){ if isTheServerRunning ;then echo -e "$NORMAL" "Server running: " "$GREEN" "Yes" "$NORMAL" else echo -e "$NORMAL" "Server running: " "$RED" "No" "$NORMAL" fi if isTheServerUp ;then echo -e "$NORMAL" "Server online: " "$RED" "No" "$NORMAL" else echo -e "$NORMAL" "Server online: " "$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("127.0.0.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"; ' "${ark_QueryPort}" fi getCurrentVersion echo -e "$NORMAL" "Server version: " "$GREEN" $instver "$NORMAL" } doUpgrade() { local sudo=sudo if [ "$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 > $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} else exit 0 fi elif [[ "$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} else exit 0 fi else echo "Your ARK server tools are already up to date" fi } useConfig() { for varname in "${!configfile_@}"; do if [ "configfile_$1" == "$varname" ]; then source "${!varname}" return fi done source "$1" } #--------------------- # Main program #--------------------- # check the configuration and throw errors or warnings if needed checkConfig while true; do case "$1" in start) doStart ;; stop) doStop ;; restart) doStop echo "`timestamp`: stop" >> "$logdir/$arkmanagerLog" sleep 1 doStart echo "`timestamp`: start" >> "$logdir/$arkmanagerLog" echo "`timestamp`: restart" >> "$logdir/$arkmanagerLog" ;; install) doInstall ;; update) if [ "$2" == "--force" ]; then forceUpdate shift elif [ "$2" == "--safe" ]; then doSafeUpdate shift else doUpdate fi ;; checkupdate) checkForUpdate ;; backup) doBackup ;; broadcast) doBroadcast "$2" shift ;; saveworld) doSaveWorld ;; rconcmd) rconcmd "$2" shift ;; status) printStatus ;; upgrade) doUpgrade ;; useconfig) useConfig "$2" shift ;; -h|--help) echo -e "Usage: arkmanager [OPTION]\n" echo "Option Description" echo "backup Saves a backup of your server inside the backup directory" echo "broadcast 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 "restart Stops the server and then starts it" 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 "update --force Apply update without check the current version" echo "update --safe Wait for server to perform world save and update." echo "upgrade Check for a new ARK Server Tools version and upgrades it if needed" echo "useconfig Use the configuration overrides in the specified config name or file" exit 1 ;; *) echo -n "arkmanager v${arkstVersion}: " if [ $# -eq 0 ]; then echo "no command specified" else echo "unknown command '$1' specified" fi echo "Try 'arkmanager -h' or 'arkmanager --help' for more information." exit 1 ;; esac shift if [ $# -eq 0 ]; then break fi done