2015-09-02 16:53:41 +10:00

707 lines
19 KiB
Bash
Executable File

#!/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 </dev/null >>"$logdir/$arkserverLog" 2>&1 & # output of this command is logged
echo "`timestamp`: start" >> "$logdir/$arkmanagerLog"
tput rc; tput ed;
echo "The server is now up"
fi
}
#
# 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`
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
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 <msg> Sends a message to all users connected to server"
echo "saveworld Saves the game world to disk"
echo "rconcmd <cmd> Execute RCON command on server"
echo "checkupdate Check for a new ARK server version"
echo "install Install the ARK server files from steamcmd"
echo "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 <name> 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