diff --git a/.version b/.version index c068b24..c239c60 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -1.4 +1.5 diff --git a/tools/arkmanager b/tools/arkmanager index 69109cc..cc75360 100755 --- a/tools/arkmanager +++ b/tools/arkmanager @@ -7,7 +7,7 @@ # Contributors: Sispheor, Atriusftw, klightspeed, lexat, puseidr # Script version -arkstVersion="1.4" +arkstVersion="1.5" arkstCommit='' doUpgradeTools() { @@ -158,6 +158,7 @@ else install_datadir="${install_datadir:-${install_bindir%/*}/share/arkmanager}" fi + #--------------------- # functions #--------------------- @@ -191,10 +192,32 @@ checkConfig() { # Environment configuration # arkserverexec - if [ ! -f "$arkserverroot/$arkserverexec" ] ; then + 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 @@ -253,17 +276,26 @@ getQueryPort(){ # 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"; + 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)); @@ -284,12 +316,12 @@ rconcmd() { 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"; + connect($socket, $sockaddr) or die "Error connecting to server: $!"; auth($socket, $password); sendpkt($socket, 2, 2, $command); my ($resid, $restype, $rcvbody) = recvpkt($socket); print $rcvbody, "\n"; - ' "$(getRconPort)" "${ark_MultiHome:-127.0.0.1}" "$(getAdminPassword)" "$1" + ' "$(getRconPort)" "${ark_MultiHome:-127.0.0.1}" "$adminpass" "$1" } # @@ -321,6 +353,45 @@ doBroadcastWithEcho(){ 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 + if [ -z "$progressDisplayType" ]; then + if stty <&1 >/dev/null 2>&1; then + progressDisplayType=spinner + else + progressDisplayType=dots + fi + fi + runSteamCMD "$@" >/dev/null 2>&1 & + 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 +} + # # Check if a new version is available but not apply it # @@ -402,7 +473,7 @@ function getCurrentVersion(){ # function getAvailableVersion(){ rm -f "$steamcmd_appinfocache" - bnumber=`$steamcmdroot/$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +app_info_update 1 +app_info_print "$appid" +quit | while read name val; do if [ "${name}" == "{" ]; then parseSteamACF ".depots.branches.public" "buildid"; break; fi; done` + 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 @@ -484,13 +555,31 @@ function isTheServerOnline(){ # 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 + 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 # @@ -627,13 +716,19 @@ 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 up" + echo "The server is now running, and should be up within 10 minutes" fi } @@ -657,6 +752,12 @@ doStartAll(){ # 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" @@ -701,6 +802,13 @@ doStopAll(){ done } +# +# install / update / download update +# +runSteamCMDAppUpdate(){ + runSteamCMDspinner +force_install_dir "$1" +app_update $appid $2 +} + # # install of ARK server # @@ -718,7 +826,7 @@ doInstall() { cd "$steamcmdroot" # install the server - ./$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +force_install_dir "$arkserverroot" +app_update $appid validate +quit + runSteamCMDAppUpdate "$arkserverroot" validate # the current version should be the last version. We set our version getCurrentVersion } @@ -726,10 +834,49 @@ doInstall() { # # Waits for a configurable number of minutes before updating the server # -doUpdateWarn(){ +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 )) @@ -737,45 +884,59 @@ doUpdateWarn(){ warnminutes=60 fi - local warnintervals=( 90 60 45 30 20 15 10 5 4 3 2 1 ) + 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 update" + echo "Server has stopped. Aborting $1" return 1 fi - if (( arkwarnminutes > warninterval )); then - if [ -n "$msgWarnUpdateMinutes" ]; then - warnmsg="$(printf "$msgWarnUpdateMinutes" "$warnminutes")" - else - warnmsg="$(printf "This ARK server will shutdown for an update in %d minutes" "$warnminutes")" - fi + if (( warnminutes > warninterval )); then + sleep 1m & + sleeppid=$! + warnmsg="$(printf "$warnmsgmin" "$warnminutes")" doBroadcastWithEcho "$warnmsg" - sleep $(( warnminutes - warninterval ))m + 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=90 - warnintervals=( 60 45 30 20 15 10 5 0 ) + 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 - if [ -n "$msgWarnUpdateSeconds" ]; then - warnmsg="$(printf "$msgWarnUpdateSeconds" "$warnseconds")" - else - warnmsg="$(printf "This ARK server will shutdown for an update in %d seconds" "$warnseconds")" - fi + warnmsg="$(printf "$warnmsgsec" "$warnseconds")" doBroadcastWithEcho "$warnmsg" - sleep $(( warnseconds - warninterval ))s + 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 update" + echo "Server has stopped. Aborting $1" return 1 fi @@ -791,6 +952,7 @@ doUpdate() { local validate= local modupdate= local saveworld= + local downloadonly= for arg in "$@"; do if [ "$arg" == "--force" ]; then @@ -799,6 +961,8 @@ doUpdate() { updatetype=safe elif [ "$arg" == "--warn" ]; then updatetype=warn + elif [ "$arg" == "--ifempty" ]; then + updatetype=ifempty elif [ "$arg" == "--validate" ]; then validate=validate appupdate=1 @@ -808,16 +972,24 @@ doUpdate() { 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.$$" + echo "$$" >"${arkserverroot}/.ark-update.lock.$$" 2>/dev/null while true; do - if ! ln "${arkserverroot}/.ark-update.lock.$$" "${arkserverroot}/.ark-update.lock"; then + 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"; then + if [ -n "$lockpid" ] && [ "$lockpid" != "$$" ] && kill -0 "$lockpid" 2>/dev/null; then echo "Update already in progress (PID: $lockpid)" - rm -f "${arkserverroot}/.ark-update.lock.$$" + rm -f "${arkserverroot}/.ark-update.lock.$$" 2>/dev/null return 1 fi rm -f "${arkserverroot}/.ark-update.lock" @@ -840,9 +1012,44 @@ doUpdate() { 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 "$appupdate" -o -n "$modupdate" ]; then + 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 @@ -851,7 +1058,13 @@ doUpdate() { done echo "`timestamp`: Save file newer than 1 minute. Performing an update." >> "$logdir/update.log" elif [ "$updatetype" == "warn" ]; then - if ! doUpdateWarn; 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 @@ -869,16 +1082,43 @@ doUpdate() { fi doStop - + # If user wants to back-up, we do it here. - + if [ "$arkBackupPreUpdate" == "true" ]; then doBackup fi if [ -n "$appupdate" ]; then - cd "$steamcmdroot" - ./$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +force_install_dir "$arkserverroot" +app_update $appid $validate +quit + 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" @@ -887,15 +1127,16 @@ doUpdate() { 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 + doStart --noautoupdate fi else echo "Your server is already up to date! The most recent version is ${bnumber}." @@ -923,16 +1164,28 @@ getModIds(){ doDownloadMod(){ local modid=$1 local modsrcdir="$steamcmdroot/steamapps/workshop/content/$mod_appid/$modid" - local moddldir="$steamcmdroot/steamapps/workshop/downloads/$mod_appid/$modid" - local dlsize=0 + local moddldir="$steamcmdroot/steamapps/workshop/downloads/$mod_appid" cd "$steamcmdroot" + retries=10 while true; do - ./$steamcmdexec +@NoPromptForPassword 1 +login ${steamlogin:-anonymous} +workshop_download_item $mod_appid $modid +quit - if [ ! -d "$moddldir" ]; then break; fi - local newsize="`du -s "$moddldir/.." | cut -f1`" - if [ $newsize -eq $dlsize ]; then break; fi - dlsize=$newsize + 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 @@ -960,7 +1213,7 @@ isModUpdateNeeded(){ local modid=$1 local modsrcdir="$steamcmdroot/steamapps/workshop/content/$mod_appid/$modid" local moddestdir="$arkserverroot/ShooterGame/Content/Mods/$modid" - local modbranch="${mod_branch:-Linux}" + local modbranch="${mod_branch:-Windows}" for varname in "${!mod_branch_@}"; do if [ "mod_branch_$modid" == "$varname" ]; then @@ -1028,6 +1281,18 @@ doExtractMod(){ 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" @@ -1041,26 +1306,26 @@ doExtractMod(){ 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"; + 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"; + 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"; + 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"; + 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); @@ -1117,15 +1382,29 @@ doInstallMod(){ fi } +# +# Removes mod from mods directory +# +doUninstallMod(){ + local modid=$1 + local moddir="$arkserverroot/ShooterGame/Content/Mods/$modid" + if [ -d "${moddir}" ]; then + rm -rf "${moddir}" + 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 ' @@ -1158,9 +1437,9 @@ doBackup(){ cp -p "${arkserverroot}/ShooterGame/Saved/${savedir}/${serverMap##*/}.tmp" "${backupdir##*/}/${serverMap##*/}.ark" fi if [ -f "${backupdir}/${serverMap##*/}.ark" ]; then - echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" + echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]" else - echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" + echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]" fi # ARK server uses Lock-Truncate-Write-Unlock @@ -1181,9 +1460,9 @@ doBackup(){ cp -p "${f%.arkprofile}.tmpprofile" "${backupdir}/${f##*/}" fi if [ -s "${backupdir}/${f##*/}" ]; then - echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" + echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]" else - echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" + echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]" fi done @@ -1202,9 +1481,9 @@ doBackup(){ cp -p "${f%.arktribe}.tmptribe" "${backupdir}/${f##*/}" fi if [ -s "${backupdir}/${f##*/}" ]; then - echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" + echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]" else - echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" + echo -e "${NORMAL}\e[68G[ ${RED}FAILED${NORMAL} ]" fi done @@ -1216,12 +1495,58 @@ doBackup(){ cp -p "${f}" "${backupdir}/${f##*/}" fi if [ -f "${backupdir}/GameUserSettings.ini" ]; then - echo -e "${NORMAL}[ ${GREEN}OK${NORMAL} ]" + echo -e "${NORMAL}\e[68G[ ${GREEN}OK${NORMAL} ]" else - echo -e "${NORMAL}[ ${RED}FAILED${NORMAL} ]" + 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?) # @@ -1263,17 +1588,107 @@ printStatus(){ 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 [ "$1" == "main" ]; then - return + 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}" - return + break fi done - source "$1" + 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 "uninstallmod Removes the mod from the Mods directory" + echo "reinstallmod Removes and re-installs a mod in the Mods directory" + 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" } #--------------------- @@ -1284,89 +1699,81 @@ useConfig() { checkConfig while true; do - case "$1" in - run) - doRun - ;; - start) - if [ "$2" == "--all" ]; then - doStartAll - shift - else - doStart - fi - ;; - stop) - if [ "$2" == "--all" ]; then - doStopAll - shift - else - doStop - fi - ;; - restart) - if [ "$2" == "--all" ]; then - doStopAll - else - doStop - fi - echo "`timestamp`: stop" >> "$logdir/$arkmanagerLog" - sleep 1 - if [ "$2" == "--all" ]; then - doStartAll - shift - else - doStart - fi - echo "`timestamp`: start" >> "$logdir/$arkmanagerLog" - echo "`timestamp`: restart" >> "$logdir/$arkmanagerLog" - ;; - install) - doInstall - ;; - update) - args=() + options=( ) + allinstances=no + instances=( ) + args=( ) + command="$1" + shift + nrarg=0 - while [[ "$2" =~ ^-- ]]; do - args=( "${args[@]}" "$2" ) - shift - done + # get the number of arguments for commands that take arguments + case "$command" in + installmod) nrarg=1; ;; + uninstallmod) nrarg=1; ;; + reinstallmod) nrarg=1; ;; + broadcast) nrarg=1; ;; + rconcmd) nrarg=1; ;; + useconfig) nrarg=1; ;; + esac - doUpdate "${args[@]}" - ;; - checkupdate) - checkForUpdate - ;; - installmod) - doInstallMod "$2" - shift - ;; - backup) - doBackup - ;; - broadcast) - doBroadcast "$2" - shift - ;; - saveworld) - doSaveWorld - ;; - rconcmd) - rconcmd "$2" - shift - ;; - status) - printStatus - ;; + # 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 + ;; + --*) + 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) - useConfig "$2" - shift + defaultinstance="${args[0]}" + continue + ;; + list-instances) + doListAllInstances "${options[@]}" + exit ;; --version) echo "Version: ${arkstVersion}" @@ -1377,53 +1784,120 @@ while true; do exit 1 ;; -h|--help) - echo -e "Usage: arkmanager [OPTION]\n" - echo "Option Description" - echo "backup Saves a backup of your server inside the backup directory" - echo "broadcast 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 "restart --all Restarts all servers specified in configfile_xxxxx" - echo "run Runs the server without daemonizing" - echo "start Starts the server" - echo "start --all Starts all servers specified in configfile_xxxxx" - echo "stop Stops the server" - echo "stop --all Stops all servers specified in configfile_xxxxx" - echo "status Returns the status of the current ARK server instance" - echo "update [OPTION ...] Check for a new ARK server version, if needed, stops the server, updates it, and starts it again" - echo "upgrade-tools Check for a new ARK Server Tools version and upgrades it if needed" - echo "uninstall-tools Uninstall the ARK Server Tools" - echo "useconfig Use the configuration overrides in the specified config name or file" - echo "--help Show this help" - echo "--version Show the version info of ARK Server Tools" - echo - echo "Update command takes the below options:" - echo " --force Apply update without checking the current version" - echo " --safe Wait for server to perform world save and update." - echo " --warn Warn players before updating server" - echo " --validate Validates all ARK server files" - echo " --saveworld Saves world before update" - echo " --update-mods Updates installed and requested mods" - echo " --backup Takes a backup of the save files before updating" + showUsage 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." + "") + echo "arkmanager v${arkstVersion}: no command specified" + showUsage exit 1 ;; esac - status=$? - shift + + # 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[@]}" + ;; + uninstallmod) + doUninstallMod "${args[@]}" + ;; + reinstallmod) + doUninstallMod "${args[@]}" + 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 diff --git a/tools/arkmanager.cfg b/tools/arkmanager.cfg index 6349781..b199a93 100644 --- a/tools/arkmanager.cfg +++ b/tools/arkmanager.cfg @@ -11,29 +11,35 @@ steamcmd_appinfocache="/home/steam/Steam/appcache/appinfo.vdf" # cache of t #steamlogin="anonymous" # Uncomment this to specify steam login instead of using anonymous login # config environment -arkserverroot="/home/steam/ARK" # path of your ARK server files (default ~/ARK) arkserverexec="ShooterGame/Binaries/Linux/ShooterGameServer" # name of ARK server executable arkbackupdir="/home/steam/ARK-Backups" # path to backup directory arkwarnminutes="60" # number of minutes to warn players when using update --warn arkautorestartfile="ShooterGame/Saved/.autorestart" # path to autorestart file +arkAutoUpdateOnStart="false" # set this to true if you want to always update before startup arkBackupPreUpdate="false" # set this to true if you want to perform a backup before updating +#arkStagingDir="/home/steam/ARK-Staging" # Uncomment to enable updates to be fully downloaded before restarting the server (reduces downtime while updating) + +# Options to automatically remove old backups to keep backup size in check +# Each compressed backup is generally about 1-2MB in size. +arkMaxBackupSizeMB="500" # Set to automatically remove old backups when size exceeds this limit +#arkMaxBackupSizeGB="2" # Uncomment this and comment the above to specify the limit in whole GB # Update warning messages # Modify as desired, putting the %d replacement operator where the number belongs msgWarnUpdateMinutes="This ARK server will shutdown for an update in %d minutes" msgWarnUpdateSeconds="This ARK server will shutdown for an update in %d seconds" +msgWarnRestartMinutes="This ARK server will shutdown for a restart in %d minutes" +msgWarnRestartSeconds="This ARK server will shutdown for a restart in %d seconds" +msgWarnShutdownMinutes="This ARK server will shutdown in %d minutes" +msgWarnShutdownSeconds="This ARK server will shutdown in %d seconds" -# ARK server options - use ark_= +# ARK server common options - use ark_= # comment out these values if you want to define them # inside your GameUserSettings.ini file serverMap="TheIsland" # server map (default TheIsland) #serverMapModId="469987622" # Uncomment this to specify the Map Mod Id ( in http://steamcommunity.com/sharedfiles/filedetails/?id=) #ark_TotalConversionMod="496735411" # Uncomment this to specify a total-conversion mod ark_RCONEnabled="True" # Enable RCON Protocol -ark_RCONPort="32330" # RCON Port -ark_SessionName="ARK Server Tools" # if your session name needs special characters please use the .ini instead -ark_Port="7778" # ARK server port (default 7778) -ark_QueryPort="27015" # ARK query port (default 27015) ark_ServerPassword="" # ARK server password, empty: no password required to login ark_ServerAdminPassword="keyboardcat" # ARK server admin password, KEEP IT SAFE! ark_MaxPlayers="70" @@ -48,7 +54,6 @@ ark_MaxPlayers="70" #arkopt_StructureDestructionTag=DestroySwampSnowStructures # config Service -servicename="arkserv" # Name of the service (don't change if you don't know what are you doing) logdir="/var/log/arktools" # Logs path (default /var/log/arktools) # steamdb specific @@ -63,3 +68,5 @@ mod_branch=Windows # alternate configs # example for config name "ark1": #configfile_ark1="/path/to/config/file" + +#defaultinstance="main" diff --git a/tools/install.sh b/tools/install.sh index 3c3fbbc..cb724f6 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -99,10 +99,14 @@ if [ "$userinstall" == "yes" ]; then PREFIX="${PREFIX:-${HOME}}" EXECPREFIX="${EXECPREFIX:-${PREFIX}}" DATAPREFIX="${DATAPREFIX:-${PREFIX}/.local/share}" + CONFIGFILE="${PREFIX}/.arkmanager.cfg" + INSTANCEDIR="${PREFIX}/.config/arkmanager/instances" else PREFIX="${PREFIX:-/usr/local}" EXECPREFIX="${EXECPREFIX:-${PREFIX}}" DATAPREFIX="${DATAPREFIX:-${PREFIX}/share}" + CONFIGFILE="/etc/arkmanager/.arkmanager.cfg" + INSTANCEDIR="/etc/arkmanager/instances" fi BINDIR="${BINDIR:-${EXECPREFIX}/bin}" @@ -155,8 +159,17 @@ if [ "$userinstall" == "yes" ]; then # Create a folder in ~/logs to let Ark tools write its own log files mkdir -p "${INSTALL_ROOT}${PREFIX}/logs/arktools" + # Create a folder in ~/.config/arkamanger to hold instance configs + mkdir -p "${INSTALL_ROOT}${INSTANCEDIR}" + + # Copy example instance config + cp instance.cfg.example "${INSTALL_ROOT}/${INSTANCEDIR}/instance.cfg.example" + # Change the defaults in the new instance config template + sed -i -e "s|\"/home/steam|\"${PREFIX}|" \ + "${INSTALL_ROOT}${INSTANCEDIR}/instance.cfg.example" + # Copy arkmanager.cfg to ~/.arkmanager.cfg.NEW - cp arkmanager.cfg "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg.NEW" + cp arkmanager.cfg "${INSTALL_ROOT}${CONFIGFILE}.NEW" # Change the defaults in the new config file sed -i -e "s|^steamcmd_user=\"steam\"|steamcmd_user=\"--me\"|" \ -e "s|\"/home/steam|\"${PREFIX}|" \ @@ -164,35 +177,18 @@ if [ "$userinstall" == "yes" ]; then -e "s|^install_bindir=.*|install_bindir=\"${BINDIR}\"|" \ -e "s|^install_libexecdir=.*|install_libexecdir=\"${LIBEXECDIR}\"|" \ -e "s|^install_datadir=.*|install_datadir=\"${DATADIR}\"|" \ - "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg.NEW" + "${INSTALL_ROOT}${CONFIGFILE}.NEW" # Copy arkmanager.cfg to ~/.arkmanager.cfg if it doesn't already exist - if [ -f "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg" ]; then - newopts=( arkbackupdir arkautorestartfile install_bindir install_libexecdir install_datadir mod_appid ) - newopt_steamcmd_appinfocache="${PREFIX}/Steam/appcache/appinfo.vdf" - newopt_arkbackupdir="${PREFIX}/ARK-Backups" - newopt_arkautorestartfile="ShooterGame/Saved/.autorestart" - newopt_install_bindir="${BINDIR}" - newopt_install_libexecdir="${LIBEXECDIR}" - newopt_install_datadir="${DATADIR}" - newopt_mod_appid=346110 - - if grep '^\(servermail\|arkstVersion\)=' "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg" >/dev/null 2>&1; then - sed -i '/^\(servermail\|arkstVersion\)=/d' "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg" - fi - - for optname in "${newopts[@]}"; do - if ! grep "^${optname}=" "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg" >/dev/null 2>&1; then - noptname="newopt_${optname}" - echo "${optname}='${!noptname}'" >>"${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg" - fi - done + if [ -f "${INSTALL_ROOT}${CONFIGFILE}" ]; then + bash ./migrate-config.sh "${INSTALL_ROOT}${CONFIGFILE}" + bash ./migrate-main-instance.sh "${INSTALL_ROOT}${CONFIGFILE}" "${INSTALL_ROOT}${INSTANCEDIR}/main.cfg" echo "A previous version of ARK Server Tools was detected in your system, your old configuration was not overwritten. You may need to manually update it." - echo "A copy of the new configuration file was included in '${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg.NEW'. Make sure to review any changes and update your config accordingly!" + echo "A copy of the new configuration file was included in '${CONFIGFILE}.NEW'. Make sure to review any changes and update your config accordingly!" exit 2 else - mv -n "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg.NEW" "${INSTALL_ROOT}${PREFIX}/.arkmanager.cfg" + mv -n "${INSTALL_ROOT}${CONFIGFILE}.NEW" "${INSTALL_ROOT}${CONFIGFILE}" fi else # Copy arkmanager to /usr/bin and set permissions @@ -214,11 +210,12 @@ else if [ -f /etc/systemd/system.conf ]; then # used by systemd mkdir -p "${INSTALL_ROOT}${LIBEXECDIR}" cp systemd/arkmanager.init "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" + sed -i "s|^DAEMON=\"/usr/bin/|DAEMON=\"${BINDIR}/|" "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" chmod +x "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" cp systemd/arkmanager.service "${INSTALL_ROOT}/etc/systemd/system/arkmanager.service" sed -i "s|=/usr/libexec/arkmanager/|=${LIBEXECDIR}/|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager.service" cp systemd/arkmanager@.service "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" - sed -i "s|=/usr/bin/|=${BINDIR}/|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" + sed -i "s|=/usr/bin/|=${BINDIR}/|;s|=steam$|=${steamcmd_user}|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" if [ -z "${INSTALL_ROOT}" ]; then systemctl daemon-reload systemctl enable arkmanager.service @@ -241,11 +238,12 @@ else if [ -f /etc/systemd/system.conf ]; then # used by systemd mkdir -p "${INSTALL_ROOT}${LIBEXECDIR}" cp systemd/arkmanager.init "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" + sed -i "s|^DAEMON=\"/usr/bin/|DAEMON=\"${BINDIR}/|" "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" chmod +x "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" cp systemd/arkmanager.service "${INSTALL_ROOT}/etc/systemd/system/arkmanager.service" sed -i "s|=/usr/libexec/arkmanager/|=${LIBEXECDIR}/|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager.service" cp systemd/arkmanager@.service "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" - sed -i "s|=/usr/bin/|=${BINDIR}/|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" + sed -i "s|=/usr/bin/|=${BINDIR}/|;s|=steam$|=${steamcmd_user}|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" if [ -z "${INSTALL_ROOT}" ]; then systemctl daemon-reload systemctl enable arkmanager.service @@ -274,11 +272,12 @@ else elif [ -f /etc/systemd/system.conf ]; then # used by systemd mkdir -p "${INSTALL_ROOT}${LIBEXECDIR}" cp systemd/arkmanager.init "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" + sed -i "s|^DAEMON=\"/usr/bin/|DAEMON=\"${BINDIR}/|" "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" chmod +x "${INSTALL_ROOT}${LIBEXECDIR}/arkmanager.init" cp systemd/arkmanager.service "${INSTALL_ROOT}/etc/systemd/system/arkmanager.service" sed -i "s|=/usr/libexec/arkmanager/|=${LIBEXECDIR}/|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager.service" cp systemd/arkmanager@.service "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" - sed -i "s|=/usr/bin/|=${BINDIR}/|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" + sed -i "s|=/usr/bin/|=${BINDIR}/|;s|=steam$|=${steamcmd_user}|" "${INSTALL_ROOT}/etc/systemd/system/arkmanager@.service" if [ -z "${INSTALL_ROOT}" ]; then systemctl daemon-reload systemctl enable arkmanager.service @@ -291,43 +290,34 @@ else mkdir -p "${INSTALL_ROOT}/var/log/arktools" chown "$steamcmd_user" "${INSTALL_ROOT}/var/log/arktools" + # Create a folder in /etc/arkmanager to hold instance config files + mkdir -p "${INSTALL_ROOT}${INSTANCEDIR}" + chown "$steamcmd_user" "${INSTALL_ROOT}${INSTANCEDIR}" + + # Copy example instance config + cp instance.cfg.example "${INSTALL_ROOT}${INSTANCEDIR}/instance.cfg.example" + # Copy arkmanager.cfg inside linux configuation folder if it doesn't already exists mkdir -p "${INSTALL_ROOT}/etc/arkmanager" - cp arkmanager.cfg "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg.NEW" - chown "$steamcmd_user" "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg.NEW" + chown "$steamcmd_user" "${INSTALL_ROOT}/etc/arkmanager" + cp arkmanager.cfg "${INSTALL_ROOT}${CONFIGFILE}.NEW" + chown "$steamcmd_user" "${INSTALL_ROOT}${CONFIGFILE}.NEW" sed -i -e "s|^steamcmd_user=\"steam\"|steamcmd_user=\"$steamcmd_user\"|" \ -e "s|\"/home/steam|\"/home/$steamcmd_user|" \ -e "s|^install_bindir=.*|install_bindir=\"${BINDIR}\"|" \ -e "s|^install_libexecdir=.*|install_libexecdir=\"${LIBEXECDIR}\"|" \ -e "s|^install_datadir=.*|install_datadir=\"${DATADIR}\"|" \ - "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg.NEW" + "${INSTALL_ROOT}${CONFIGFILE}.NEW" - if [ -f "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg" ]; then - newopts=( arkbackupdir arkautorestartfile install_bindir install_libexecdir install_datadir mod_appid ) - newopt_steamcmd_appinfocache="/home/${steamcmd_user}/Steam/appcache/appinfo.vdf" - newopt_arkbackupdir="/home/${steamcmd_user}/ARK-Backups" - newopt_arkautorestartfile="ShooterGame/Saved/.autorestart" - newopt_install_bindir="${BINDIR}" - newopt_install_libexecdir="${LIBEXECDIR}" - newopt_install_datadir="${DATADIR}" - newopt_mod_appid=346110 - - if grep '^\(servermail\|arkstVersion\)=' "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg" >/dev/null 2>&1; then - sed -i '/^\(servermail\|arkstVersion\)=/d' "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg" - fi - - for optname in "${newopts[@]}"; do - if ! grep "^${optname}=" "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg" >/dev/null 2>&1; then - noptname="newopt_${optname}" - echo "${optname}='${!noptname}'" >>"${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg" - fi - done + if [ -f "${INSTALL_ROOT}${CONFIGFILE}" ]; then + bash ./migrate-config.sh "${INSTALL_ROOT}${CONFIGFILE}" + bash ./migrate-main-instance.sh "${INSTALL_ROOT}${CONFIGFILE}" "${INSTALL_ROOT}${INSTANCEDIR}/main.cfg" echo "A previous version of ARK Server Tools was detected in your system, your old configuration was not overwritten. You may need to manually update it." echo "A copy of the new configuration file was included in /etc/arkmanager. Make sure to review any changes and update your config accordingly!" exit 2 else - mv -n "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg.NEW" "${INSTALL_ROOT}/etc/arkmanager/arkmanager.cfg" + mv -n "${INSTALL_ROOT}${CONFIGFILE}.NEW" "${INSTALL_ROOT}${CONFIGFILE}.cfg" fi fi diff --git a/tools/instance.cfg.example b/tools/instance.cfg.example new file mode 100644 index 0000000..0513b44 --- /dev/null +++ b/tools/instance.cfg.example @@ -0,0 +1,28 @@ +# config environment +arkserverroot="/home/steam/ARK" # path of your ARK server files (default ~/ARK) + +# ARK server options - use ark_= +# comment out these values if you want to define them +# inside your GameUserSettings.ini file +serverMap="TheIsland" # server map (default TheIsland) +#serverMapModId="469987622" # Uncomment this to specify the Map Mod Id ( in http://steamcommunity.com/sharedfiles/filedetails/?id=) +#ark_TotalConversionMod="496735411" # Uncomment this to specify a total-conversion mod +ark_RCONEnabled="True" # Enable RCON Protocol +ark_RCONPort="32330" # RCON Port +ark_SessionName="ARK Server Tools" # if your session name needs special characters please use the .ini instead +ark_Port="7778" # ARK server port (default 7778) +ark_QueryPort="27015" # ARK query port (default 27015) +ark_ServerPassword="" # ARK server password, empty: no password required to login +ark_ServerAdminPassword="keyboardcat" # ARK server admin password, KEEP IT SAFE! +ark_MaxPlayers="70" +#ark_GameModIds="487516323,487516324,487516325" # Uncomment to specify additional mods by Mod Id separated by commas +#ark_AltSaveDirectoryName="SotF" # Uncomment to specify a different save directory name + +# ARK server flags - use arkflag_=true +#arkflag_OnlyAdminRejoinAsSpectator=true # Uncomment to only allow admins to rejoin as spectator +#arkflag_DisableDeathSpectator=true # Uncomment to disable players from becoming spectators when they die + +# ARK server options - i.e. for -optname=val, use arkopt_optname=val +#arkopt_StructureDestructionTag=DestroySwampSnowStructures + + diff --git a/tools/lsb/arkdaemon b/tools/lsb/arkdaemon index 484d5d8..6390f59 100755 --- a/tools/lsb/arkdaemon +++ b/tools/lsb/arkdaemon @@ -20,46 +20,99 @@ NAME="ShooterGameServer" LOGFILE="${logdir}/${NAME}.log" DAEMON="/usr/bin/arkmanager" +SVCNAME="${0##*/}" +INSTANCE="${SVCNAME#*.}" + +if [ "$INSTANCE" == "$SVCNAME" ]; then + INSTANCE="$2" +fi + set -e # If the daemon is not there, then exit. test -x $DAEMON || exit 5 +function start_instance(){ + local INSTANCE="$1" + PID="$(<"/var/run/arkmanager.${INSTANCE}.pid")" + if [ -n "$PID" ] && kill -0 "$PID" >/dev/null 2>&1; then + if grep " ${PID} .* ${DAEMON}" <(ps -ef) >/dev/null 2>&1; then + echo "$NAME @${INSTANCE} is already running" + return 0 + fi + fi + log_daemon_msg "Starting" "$NAME @${INSTANCE}" + ulimit -n 100000 + "${DAEMON}" run "@${INSTANCE}" & + PID="$!" + + sleep 5 + if kill -0 "$PID" >/dev/null 2>&1; then + echo "$PID" >"/var/run/arkmanager.${INSTANCE}.pid" + log_end_msg 0 + return 0 + else + log_end_msg 1 + return 1 + fi +} + +function start_all_instances(){ + local nosuccess=0 + local anyfailure=0 + for instance in $("${DAEMON}" list-instances --brief); do + if start_instance "$instance"; then + nosuccess=0 + else + anyfailure=1 + fi + done + + return $nosuccess +} + +function stop_instance(){ + local INSTANCE="$1" + log_daemon_msg "Stopping $NAME @${INSTANCE}: " + "${DAEMON}" stop "@${INSTANCE}" & + rm -f "/var/run/arkmanager.${INSTANCE}.pid" + log_end_msg 0 + return 0 +} + case "$1" in start) - log_daemon_msg "Starting" "$NAME" - ulimit -n 100000 - su -s /bin/sh -c "$DAEMON start --all" $steamcmd_user - sleep 5 - PID=`ps -ef | grep $NAME | grep -v grep | awk '{print $2}'` - if [ -n "$PID" ]; then - echo "$PID" >/var/run/arkmanager.pid - log_end_msg 0 + if [ -n "$INSTANCE" ]; then + start_instance "$INSTANCE" + exit $? else - log_end_msg 1 + if start_all_instances; then + exit 0 + else + exit 1 + fi fi ;; stop) - log_daemon_msg "Stopping" "$NAME" - su -s /bin/sh -c "$DAEMON stop --all" $steamcmd_user - sleep 5 - PID=`ps -ef | grep $NAME | grep -v grep | awk '{print $2}'` - if [ -n "$PID" ]; then - log_end_msg 1 + if [ -n "$INSTANCE" ]; then + stop_instance "$INSTANCE" + exit $? else - rm /var/run/arkmanager.pid - log_end_msg 0 + for instance in $("${DAEMON}" list-instances --brief); do + stop_instance "$instance" + done + exit $? fi ;; restart) - ulimit -n 100000 - su -s /bin/sh -c "$DAEMON restart --all" $steamcmd_user + "$0" stop + "$0" start ;; status) - su -s /bin/sh -c "$DAEMON status" $steamcmd_user + "$DAEMON" status "@${INSTANCE:-all}" ;; *) diff --git a/tools/migrate-config.sh b/tools/migrate-config.sh new file mode 100755 index 0000000..e7bd017 --- /dev/null +++ b/tools/migrate-config.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +configfile="$1" +newopts=( arkbackupdir arkautorestartfile install_bindir install_libexecdir install_datadir mod_appid ) +newopt_steamcmd_appinfocache="${PREFIX}/Steam/appcache/appinfo.vdf" +newopt_arkbackupdir="${PREFIX}/ARK-Backups" +newopt_arkautorestartfile="ShooterGame/Saved/.autorestart" +newopt_install_bindir="${BINDIR}" +newopt_install_libexecdir="${LIBEXECDIR}" +newopt_install_datadir="${DATADIR}" +newopt_mod_appid=346110 + +if grep '^\(servermail\|arkstVersion\)=' "${configfile}" >/dev/null 2>&1; then + sed -i '/^\(servermail\|arkstVersion\)=/d' "${configfile}" +fi + +for optname in "${newopts[@]}"; do + if ! grep "^${optname}=" "${configfile}" >/dev/null 2>&1; then + noptname="newopt_${optname}" + echo "${optname}='${!noptname}'" >>"${configfile}" + fi +done + diff --git a/tools/migrate-main-instance.sh b/tools/migrate-main-instance.sh new file mode 100755 index 0000000..79cdfd6 --- /dev/null +++ b/tools/migrate-main-instance.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +configfile="$1" +instancefile="$2" + +if grep "^arkserverroot=" <"$configfile" >/dev/null 2>&1 && [ ! -f "$instancefile" ]; then + sed -n '/^#*\(ark\(\|flag\|opt\)_[^=]*\|arkserverroot\|serverMap\(\|ModId\)\)=/p' <"$configfile" >"$instancefile" + sed -i '/^ark\(serverroot\|_\(RCONPort\|Port\|QueryPort\)\)=/d' "$configfile" + echo 'defaultinstance="main"' >>"$configfile" +fi diff --git a/tools/openrc/arkdaemon b/tools/openrc/arkdaemon index 5aa9340..ac47eb1 100644 --- a/tools/openrc/arkdaemon +++ b/tools/openrc/arkdaemon @@ -13,27 +13,25 @@ depend(){ } start(){ + INSTANCE="${RC_SVCNAME#*.}" ebegin "Starting ARK manager daemon" ulimit -n 100000 - su -s /bin/sh -c "$DAEMON start --all" $steamcmd_user - sleep 5 - PID=`ps -ef | grep $NAME | grep -v grep | awk '{print $2}'` - if [ -n "$PID" ]; then - eend 0 + if [ "$INSTANCE" != "$RC_SVCNAME" ]; then + "$DAEMON" start "@${INSTANCE}" else - eend 1 + "$DAEMON" start "@all" fi + eend $? } stop(){ + INSTANCE="${RC_SVCNAME#*.}" ebegin "Stopping ARK manager daemon" - su -s /bin/sh -c "$DAEMON stop --all" $steamcmd_user - sleep 5 - PID=`ps -ef | grep $NAME | grep -v grep | awk '{print $2}'` - if [ -n "$PID" ]; then - eend 0 + if [ "$INSTANCE" != "$RC_SVCNAME" ]; then + "$DAEMON" stop "@${INSTANCE}" else - eend 1 + "$DAEMON" stop "@all" fi + eend $? } diff --git a/tools/redhat/arkdaemon b/tools/redhat/arkdaemon index fa0ed1a..48f0594 100755 --- a/tools/redhat/arkdaemon +++ b/tools/redhat/arkdaemon @@ -34,54 +34,103 @@ GREEN="\\033[1;32m" RED="\\033[1;31m" NORMAL="\\033[0;39m" +SVCNAME="${0##*/}" +INSTANCE="${SVCNAME#*.}" + +if [ "$INSTANCE" == "$SVCNAME" ]; then + INSTANCE="$2" +fi + set -e # If the daemon is not there, then exit. test -x $DAEMON || exit 5 +function start_instance(){ + local INSTANCE="$1" + PID="$(<"/var/run/arkmanager.${INSTANCE}.pid")" + if [ -n "$PID" ] && kill -0 "$PID" >/dev/null 2>&1; then + if grep " ${PID} .* ${DAEMON}" <(ps -ef) >/dev/null 2>&1; then + echo "$NAME @${INSTANCE} is already running" + return 0 + fi + fi + echo -n "Starting $NAME @${INSTANCE}: " + ulimit -n 100000 + "${DAEMON}" run "@${INSTANCE}" & + PID="$!" + + sleep 5 + if kill -0 "$PID" >/dev/null 2>&1; then + echo "$PID" >"/var/run/arkmanager.${INSTANCE}.pid" + touch "/var/lock/subsys/arkmanager.${INSTANCE}" + echo "[" "$GREEN" " OK " "$NORMAL" "]" + return 0 + else + echo "[" "$RED" " FAILED " "$NORMAL" "]" + return 1 + fi +} + +function start_all_instances(){ + local nosuccess=0 + local anyfailure=0 + for instance in $("${DAEMON}" list-instances --brief); do + if start_instance "$instance"; then + nosuccess=0 + else + anyfailure=1 + fi + done + + return $nosuccess +} + +function stop_instance(){ + local INSTANCE="$1" + echo -n "Stopping $NAME @${INSTANCE}: " + "${DAEMON}" stop "@${INSTANCE}" & + rm -f "/var/lock/subsys/arkmanager.${INSTANCE}" + rm -f "/var/run/arkmanager.${INSTANCE}.pid" + echo "[" "$GREEN" " OK " "$NORMAL" "]" + return 0 +} + case "$1" in start) - echo -n "Starting $NAME: " - ulimit -n 100000 - su -s /bin/sh -c "$DAEMON start --all" $steamcmd_user > /dev/null - sleep 5 - PID=`ps -ef | grep $NAME | grep -v grep | awk '{print $2}'` - if [ -n "$PID" ]; then - echo "${PID}" >/var/run/arkmanager.pid - touch /var/lock/subsys/arkmanager - echo "[" "$GREEN" " OK " "$NORMAL" "]" - exit 0 + if [ -n "$INSTANCE" ]; then + start_instance "$INSTANCE" + exit $? else - echo "[" "$RED" " FAILED " "$NORMAL" "]" - exit 1 + if start_all_instances; then + touch /var/lock/subsys/arkmanager + exit 0 + else + exit 1 + fi fi ;; stop) - echo -n "Stopping $NAME: " - su -s /bin/sh -c "$DAEMON stop --all" $steamcmd_user > /dev/null - sleep 5 - PID=`ps -ef | grep $NAME | grep -v grep | awk '{print $2}'` - if [ -n "$PID" ]; then - echo "[" "$RED" " FAILED " "$NORMAL" "]" - exit 1 + if [ -n "$INSTANCE" ]; then + stop_instance "$INSTANCE" + exit $? else - echo "[" "$GREEN" " OK " "$NORMAL" "]" - rm -f /var/lock/subsys/arkmanager - rm -f /var/run/arkmanager.pid - exit 0 + for instance in $("${DAEMON}" list-instances --brief); do + stop_instance "$instance" + done + rm -f "/var/lock/subsys/arkmanager" + exit $? fi ;; restart) - echo -n "Restarting $NAME: " - ulimit -n 100000 - su -s /bin/sh -c "$DAEMON restart --all" $steamcmd_user > /dev/null - echo "OK" + "$0" stop + "$0" start ;; status) - su -s /bin/sh -c "$DAEMON status" $steamcmd_user + "$DAEMON" status "@${INSTANCE:-all}" exit 0 ;; diff --git a/tools/systemd/arkmanager.init b/tools/systemd/arkmanager.init index be25029..fa760d2 100755 --- a/tools/systemd/arkmanager.init +++ b/tools/systemd/arkmanager.init @@ -1,5 +1,14 @@ #!/bin/bash -for service in main $(grep -o '^configfile_[^=]*' /etc/arkmanager/arkmanager.cfg); do - systemctl start arkmanager@${service#configfile_} +DAEMON=/usr/bin/arkmanager + +for service in $(${DAEMON} list-instances --brief); do + case "$1" in + start) + systemctl start arkmanager@${service} + ;; + stop) + systemctl stop arkmanager@${service} + ;; + esac done diff --git a/tools/systemd/arkmanager@.service b/tools/systemd/arkmanager@.service index 49b722c..4c92d0f 100644 --- a/tools/systemd/arkmanager@.service +++ b/tools/systemd/arkmanager@.service @@ -2,5 +2,6 @@ Description=Daemon to start an ark server instance [Service] +User=steam ExecStart=/usr/bin/arkmanager useconfig %i run Type=simple diff --git a/tools/upstart/arkmanager-instance.conf b/tools/upstart/arkmanager-instance.conf index 0b219a4..4f203bf 100644 --- a/tools/upstart/arkmanager-instance.conf +++ b/tools/upstart/arkmanager-instance.conf @@ -2,6 +2,8 @@ description "ARK Server Tools service" instance $service +setuid steam + env DAEMON="/usr/bin/arkmanager" exec "$DAEMON" useconfig $service run diff --git a/tools/upstart/arkmanager.conf b/tools/upstart/arkmanager.conf index 688cbe8..5cc8008 100644 --- a/tools/upstart/arkmanager.conf +++ b/tools/upstart/arkmanager.conf @@ -1,8 +1,10 @@ start on runlevel [345] stop on runlevel [!345] +env DAEMON="/usr/bin/arkmanager" + script - for service in main $(grep -o '^configfile_[^=]*' /etc/arkmanager/arkmanager.cfg); do - start arkmanager-instance service=${service#configfile_} + for service in $(${DAEMON} list-instances --brief); do + start arkmanager-instance service=${service} done end script