Page 1 of 2

How to Install Ark: Survival Evolved on Ubuntu Server 20.04 LTS

Posted: Wed Apr 14, 2021 10:04 pm
by LHammonds
------------- WORK-IN-PROGRESS (almost done) -------------

EDIT: For those checking this thread over and over, sorry I have not published it yet. Its taking a while creating all the scripts how I like them and making sure they work as designed in various situations. When finished, I will also publish this on Steam Guides (assuming it can handle this much data...if not, will just have to post links to the code) and post a link here.

SECTIONS STILL UNDER DEVELOPMENT
Script - Fix Permissions
PROBLEM with Systemd service - ExecStartPre and ExecStart need different permissions....looking into a solution for this or a work-around.
LOGIC CHANGE with Game-Online exit codes....several scripts being updated to handle the reverse logic.

Overview

This article will cover how to install and maintain a dedicated Ark: Survival Evolved server on Ubuntu.

The dedicated server will be downloaded into a template folder and then copied from there to each game instance. This gives us the benefit of keeping the game instances online while we download updates in the background to the template folder.

RCON, script configuration file, a command script and scripts to start and stop the game instance will be necessary for the systemd services. You do not have to use my other scripts but they will help greatly in terms of speed due to automating many manual steps.

Two maps (TheIsland and Ragnarok) will be configured to demonstrate how to make an interconnected cluster of maps that players can transfer between. Six mods will also be used to demonstrate how to install, use and update mods.

These are the locations that will be utilized:

Code: Select all

/opt/ark/scripts (custom Bash scripts for Ark)
/opt/ark/cluster (folder to hold cluster transfer information)
/opt/ark/bak (folder to hold backup archives)
/opt/ark/template (Ark dedicated server files and mods downloaded from Steam)
/opt/ark/*MapName* (folders containing game instances for each map)
/etc (holds configuration files)
/lib/systemd/system (holds configuration files related to systemctl start/stop commands)
These are the port settings that will be used for each map. Feel free to use different settings but these should not conflict with anything else or themselves:

Code: Select all

Map Name      Game Raw  Query RCON
------------- ---- ---- ----- -----
TheIsland     7777 7778 27051 27151
ScorchedEarth 7779 7780 27052 27152
Aberration    7781 7782 27053 27153
Extinction    7783 7784 27054 27154
Genesis1      7785 7786 27055 27155
Genesis2      7787 7788 27056 27156
TheCenter     7789 7790 27057 27157
Ragnarok      7791 7792 27058 27158
Valguero      7793 7794 27059 27159
CrystalIsles  7795 7796 27060 27160
Olympus       7797 7798 27061 27161
Tools utilized in this process

Install Ubuntu Server

Posted: Wed Apr 14, 2021 10:04 pm
by LHammonds
Installation of Ubuntu Server

This documentation will assume you have installed Ubuntu Server according to this article: How to install and configure Ubuntu Server

It is certainly not mandatory that you follow those instructions but some things covered in those instructions will be assumed as being done such as firewall settings, LVM usage, Fail2Ban, etc.

Since we are using /opt for all program/data storage, you might need to increase that logical volume and file system in order to have enough space to hold your instances. It is about 14 GB per instance before savegame data. Depending on amount of users, each map could have a savegame around 1 GB to several GB in size. You also need to account for versioned / archived backups. If you start getting tight on space, adjust the amount of backups to retain during each purge of old files.

Ubuntu Firewall Rules

Posted: Wed Apr 14, 2021 10:04 pm
by LHammonds
Ubuntu Firewall Rules

Edit the firewall script that was created during the initial setup of the server (if you followed my instructions):

Code: Select all

sudo vi /var/scripts/prod/en-firewall.sh
NOTE: The RCON port is commented out since it should not be open to the entire Internet but left there as documentation about the port. If you do not have the RCON ports opened on your hardware firewall / router, then opening them on Ubuntu will just mean RCON is accessible on your local network. RCON in this documentation is purely used locally via scripts so no need to have the ports opened.

Add the following but only for the maps you intend to run:

Code: Select all

echo "Adding Ark Server rules"
ufw allow proto udp to any port 27051 comment 'Island Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27151 comment 'Island RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7777 comment 'Island Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7778 comment 'Island Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27052 comment 'ScorchedEarth Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27152 comment 'ScorchedEarth RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7779 comment 'ScorchedEarth Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7780 comment 'ScorchedEarth Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27053 comment 'Aberration Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27153 comment 'Aberration RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7781 comment 'Aberration Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7782 comment 'Aberration Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27054 comment 'Extinction Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27154 comment 'Extinction RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7783 comment 'Extinction Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7784 comment 'Extinction Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27055 comment 'Genesis1 Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27155 comment 'Genesis1 RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7785 comment 'Genesis1 Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7786 comment 'Genesis1 Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27056 comment 'Genesis2 Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27156 comment 'Genesis2 RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7787 comment 'Genesis2 Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7788 comment 'Genesis2 Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27057 comment 'TheCenter Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27157 comment 'TheCenter RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7789 comment 'TheCenter Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7790 comment 'TheCenter Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27058 comment 'Ragnarok Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27158 comment 'Ragnarok RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7791 comment 'Ragnarok Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7792 comment 'Ragnarok Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27059 comment 'Valguero Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27159 comment 'Valguero RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7793 comment 'Valguero Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7794 comment 'Valguero Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27060 comment 'CrystalIsles Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27160 comment 'CrystalIsles RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7795 comment 'CrystalIsles Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7796 comment 'CrystalIsles Raw' 1>/dev/null 2>&1

ufw allow proto udp to any port 27061 comment 'Olympus Query' 1>/dev/null 2>&1
#ufw allow proto tcp to any port 27161 comment 'Olympus RCON' 1>/dev/null 2>&1
ufw allow proto udp to any port 7797 comment 'Olympus Game' 1>/dev/null 2>&1
ufw allow proto udp to any port 7798 comment 'Olympus Raw' 1>/dev/null 2>&1

Run the updated rules:

Code: Select all

sudo /var/scripts/prod/en-firewall.sh
Once activated, you can see the rules using this command:

Code: Select all

sudo ufw status
Example results:

Code: Select all

Status: active

To                         Action      From
--                         ------      ----
22                         ALLOW       192.168.1.0/24           # SSH via LAN
27051/udp                  ALLOW       Anywhere                   # Island Query
7777/udp                   ALLOW       Anywhere                   # Island Game
7778/udp                   ALLOW       Anywhere                   # Island Raw
27058/udp                  ALLOW       Anywhere                   # Ragnarok Query
7791/udp                   ALLOW       Anywhere                   # Ragnarok Game
7792/udp                   ALLOW       Anywhere                   # Ragnarok Raw
Once Ark is running, you will be able to see what ports Ark is listening to and whether or not they are TCP or UDP.

Example with TheIsland and Ragnarok running:

Code: Select all

lsof -i -P -n | grep ark
ShooterGa 546042      arkservice   13u  IPv4 251797      0t0  UDP *:27051
ShooterGa 546042      arkservice   19u  IPv4 251825      0t0  UDP *:7777
ShooterGa 546042      arkservice   20u  IPv4 278201      0t0  TCP *:27151 (LISTEN)
ShooterGa 546042      arkservice   22u  IPv4 272856      0t0  UDP *:7778
ShooterGa 546042      arkservice   23u  IPv4 286129      0t0  TCP 192.168.1.2:46288->52.216.141.187:80 (ESTABLISHED)
ShooterGa 546136      arkservice   14u  IPv4 284766      0t0  UDP *:27058
ShooterGa 546136      arkservice   19u  IPv4 283841      0t0  UDP *:7791
ShooterGa 546136      arkservice   20u  IPv4 283842      0t0  TCP *:27158 (LISTEN)
ShooterGa 546136      arkservice   22u  IPv4 284770      0t0  UDP *:7792
ShooterGa 546136      arkservice   23u  IPv4 283844      0t0  TCP 192.168.1.2:46290->52.216.141.187:80 (ESTABLISHED)

Credentials

Posted: Wed Apr 14, 2021 10:04 pm
by LHammonds
Credentials
There will be 3 levels of user access:
  1. root (system-level access for script schedules, backups and service control)
  2. arkserver (owner/update account that has read/write access to all Ark files and ability to use sudo command)
  3. arkservice (runtime account that belongs to the arkserver group which has the least permissions possible)
One group will be created called "arkserver" and will contain the minimum permissions necessary to run the server (read-only access everywhere except necessary locations such as cluster and savegame data)

Let's create the game folders, the user accounts and setup correct access.

Code: Select all

sudo mkdir --parents /opt/ark
sudo addgroup arkserver
sudo useradd --comment "Ark Survival Evolved" --shell /bin/bash --home /opt/ark/ --create-home --gid arkserver arkserver
sudo useradd --comment "Ark Survival Evolved Service" --shell /bin/bash --home /opt/ark/ --gid arkserver arkservice
sudo usermod -aG sudo arkserver
sudo chown arkserver:arkserver /opt/ark
sudo chmod g+s /opt/ark
sudo mkdir --parents /opt/ark/cluster
sudo mkdir --parents /opt/ark/template
sudo mkdir --parents /opt/ark/TheIsland
sudo mkdir --parents /opt/ark/ScorchedEarth
sudo mkdir --parents /opt/ark/Aberration
sudo mkdir --parents /opt/ark/Extinction
sudo mkdir --parents /opt/ark/Genesis1
sudo mkdir --parents /opt/ark/Genesis2
sudo mkdir --parents /opt/ark/TheCenter
sudo mkdir --parents /opt/ark/Ragnarok
sudo mkdir --parents /opt/ark/Valguero
sudo mkdir --parents /opt/ark/CrystalIsles
sudo mkdir --parents /opt/ark/Olympus
sudo chown --recursive arkserver:arkserver /opt/ark
sudo chmod --recursive 0775 /opt/ark
Setting a password for the arkservice account will not be needed since it will never be used for direct login access.

However, we need to set a password for the arkserver account which we will need when running the "sudo" command which asks for the password of the account. Type the following command and set the password to something you will remember (or document it in something like KeePass).

Code: Select all

sudo passwd arkserver
From this point forward, you can use the arkserver account when issuing commands, even the sudo commands.

Install SteamCMD

Posted: Wed Apr 14, 2021 10:04 pm
by LHammonds
Install SteamCMD

Reference Article

Code: Select all

sudo dpkg --add-architecture i386
sudo apt update
sudo apt install lib32gcc1 steamcmd libsdl2-2.0-0:i386

Download Ark to the Template folder

Posted: Wed Apr 14, 2021 10:04 pm
by LHammonds
Download Ark to the Template folder

We will only be downloading one copy of Ark and workshop mods from Steam into a template folder.
Once we have a copy of the server code downloaded from Steam, we can then copy these files to other folders which will become our game instances which the services will control.

Code: Select all

sudo su arkserver
steamcmd +login anonymous +force_install_dir /opt/ark/template +app_update 376030 validate +quit
exit

Re: How to Install Ark: Survival Evolved on Ubuntu Server 20.04 LTS

Posted: Wed Apr 14, 2021 10:04 pm
by LHammonds
Copy Ark from Template to the Game Instances

We already have the server files in the template folder. Now we can just copy from the local template folder to map folder(s) that we want to run.

Pick which commands to run based on what maps you are going to be running in the cluster. You most-likely will not be running all of them on the same server (due to RAM limitations). You also need to make sure you have room to not only have multiple copies of the server but also room to grow for savegame/map data and backups. Each instance will be around 14 GB in size based on the version of the game at the time of this writing (prior to Genesis Part 2 release).

Code: Select all

cp --recursive /opt/ark/template/* /opt/ark/TheIsland/.
cp --recursive /opt/ark/template/* /opt/ark/ScorchedEarth/.
cp --recursive /opt/ark/template/* /opt/ark/Aberration/.
cp --recursive /opt/ark/template/* /opt/ark/Extinction/.
cp --recursive /opt/ark/template/* /opt/ark/Genesis1/.
cp --recursive /opt/ark/template/* /opt/ark/Genesis2/.
cp --recursive /opt/ark/template/* /opt/ark/TheCenter/.
cp --recursive /opt/ark/template/* /opt/ark/Ragnarok/.
cp --recursive /opt/ark/template/* /opt/ark/Valguero/.
cp --recursive /opt/ark/template/* /opt/ark/CrystalIsles/.
cp --recursive /opt/ark/template/* /opt/ark/Olympus/.
If you used the arkserver ID when you logged in, the ownership settings after running the above commands will be fine.

If you did not use the arkserver ID, then make sure file ownership/permissions will allow the server to work. We will fine-tune the permissions later but these will work for now:

Code: Select all

sudo chown --recursive arkserver:arkserver /opt/ark
sudo chmod --recursive 0775 /opt/ark
These instances are NOT ready to launch. The GameUserSettings.ini for each have to be modified and the start/stop scripts needs to be setup to control the service from systemd.

RCON Utility

Posted: Wed Apr 14, 2021 10:05 pm
by LHammonds
RCON Utility

Reference: My Steam Guide: RCON Command-Line Utility

The game instances will be running as a service and thus, the console will not be available. This is where RCON comes in handy. Ark has built-in support for RCON clients sending console commands such as DoExit, Broadcast, ServerChat, DestroyWildDinos, SaveWorld, etc.

Ark does not ship with an RCON client though and neither does Ubuntu.

You can get source code written in C by [ASY]Zyrain that you can compile for use with your Ark server. However, I did not like the fact that his version required the password to be passed as a command-line parameter which exposes it to anyone that is watching the process list. So I modified it to pull the password from a configuration file and a few other minor tweaks documented in the change log. Both versions are on my github so you can easily see what was changed if interested.

All the scripts that interact with the Ark service will be using this RCON utility.

Here are the steps to download and compile my variation of the RCON utility.

Code: Select all

sudo apt install gcc
wget https://raw.githubusercontent.com/LHammonds/rcon/main/rcon.c --output-document /opt/ark/scripts/rcon.c
sudo gcc -o /opt/ark/scripts/rcon /opt/ark/scripts/rcon.c
sudo chown arkserver:arkserver /opt/ark/scripts/rcon
sudo chmod 0750 /opt/ark/scripts/rcon
sudo apt remove gcc
(I know, super difficult wasn't it!)

Might as well grab the manual for RCON and copy it where you can reference it anywhere:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/rcon/main/rcon.man --output-document /opt/ark/scripts/rcon.man
sudo cp /opt/ark/scripts/rcon.man /usr/local/share/man/man1/rcon.1
sudo chown root:root /usr/local/share/man/man1/rcon.1
sudo chmod 0644 /usr/local/share/man/man1/rcon.1
Now you can pull up the help page anytime you want:

Code: Select all

man rcon
Create a configuration file that RCON can use:

Code: Select all

sudo touch /etc/rcon.ini
sudo chown arkserver:arkserver /etc/rcon.ini
sudo chmod 0640 /etc/rcon.ini
cat << EOF > /etc/rcon.ini
[rcon]
password=YOUR_RCON_PASSWORD
ipaddress=127.0.0.1
port=27015
EOF
Be sure to edit the rcon config file and match the password to what you will / have set for "ServerAdminPassword" in your GameUserSettings.ini file. If you use different rcon passwords per map, you will need to make different rcon config files as well.

Usage Examples

Run a DoExit command:

Code: Select all

rcon -f "/etc/rcon.ini" -a 127.0.0.1 -p 27151 "DoExit"
Run a DoExit command and use the address and port defined in ini file:

Code: Select all

rcon -f "/etc/rcon.ini" "DoExit"
Run a Broadcast message command using an island-instance ini file:

Code: Select all

rcon -f "/etc/rcon-island.ini" "Broadcast About to save the world!"
Run a SaveWorld command using a ragnarok-instance ini file:

Code: Select all

rcon -f "/etc/rcon-ragnarok.ini" "SaveWorld"

Scripting - Configuration

Posted: Wed Apr 14, 2021 10:05 pm
by LHammonds
Scripting Configuration

You could put the startup command directly into the systemd startup scripts but I like to make it easy to manage the system in the fewest places possible. With that said, let's create a script configuration file that will hold all of our various settings related to the management of Ark.

All scripts will import the settings from this file and as such, any change to this configuration file will take immediate effect to any script that runs it after the change.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

sudo wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/gamectl.conf --output-document /etc/gamectl.conf
sudo chown arkserver:arkserver /etc/gamectl.conf
sudo chmod 0640 /etc/gamectl.conf
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /etc/gamectl.conf
sudo chown arkserver:arkserver /etc/gamectl.conf
sudo chmod 0640 /etc/gamectl.conf
Now edit the file:

Code: Select all

sudo vi /etc/gamectl.conf
Copy/paste the following into the file. Adjust whatever settings you like such as the SessionName to become your ark name/description instead of "MyArk" and set the GameModIds to the mods you want to use or set to empty string "" if you do not want to use any mods. Also make sure to set the ClusterID variable to a unique number.

Code: Select all

#############################################################
## Name          : gamectl.conf
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Common variables and functions for various scripts.
## Compatibility : Verified on to work on: Ubuntu Server 20.04 LTS
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################
## Mod Information
## 1999447172 = Super Structures
## 895711211  = Classic Flyers
## 1262248217 = Tribute and Element Transfers
## 1404697612 = Awesome SpyGlass!
## 751991809  = Death Recovery Mod (v1.12.1)
## 1609138312 = Dino Storage v2

## Common Variables ##
GameService="arkservice"
GameUser="arkserver"
GameGroup="arkserver"
ServerID="376030"
GameID="346110"
GameRootDir="/opt/ark"
ScriptDir="${GameRootDir}/scripts"
BackupDir="${GameRootDir}/bak"
TemplateDir="${GameRootDir}/template"
LogDir="/var/log"
TempDir="/tmp"
SteamDir="/usr/games"
SteamCMD="${SteamDir}/steamcmd"
ArkModDLCMD="/usr/bin/python3 ${ScriptDir}/Ark_Mod_Downloader.py"
RCONFile="/etc/rcon.ini"
VerboseMode="0"
MaxPlayers="30"
ShutdownDelay="10"
GameModIds="1999447172,895711211,1262248217,1404697612,751991809,1609138312"
ClusterID="2548675309"
OtherOptions="-noundermeshkilling -activeevent=easter -AutoDestroyStructures -EnableIdlePlayerKick -ClusterDirOverride=${GameRootDir}/cluster -NoTransferFromFiltering -UseBattlEye -servergamelog -servergamelogincludetribelogs -nostreamclient -game -server -log"

## Map-Specific Variables ##

## Array Note, the index for the Instance Name corresponds to the data value
## in the other data arrays for the same index number.  It also should match the
## Folder name where the instance is located.
## Example: arrInstanceName has "TheIsland" in index [0].  To get the game port
## for that instance, use the same index in arrGamePort[0] which is 7777
## To add another instance, just make sure to add the data to each array in
## the exact same index location.

arrInstanceName=("TheIsland" "ScorchedEarth" "Aberration" "Extinction" "Genesis1" "Genesis2" "TheCenter" "Ragnarok" "Valguero" "CrystalIsles" "Olympus")
arrSessionName=("MyArk The Island 5x" "MyArk Scorched Earth 5x" "MyArk Aberration 5x" "MyArk Extinction 5x" "MyArk Genesis1 5x" "MyArk Genesis2 5x" "MyArk The Center 5x" "MyArk Ragnarok 5x" "MyArk Valguero 5x" "MyArk Crystal Isles 5x" "MyArk Olympus 5x")
arrMapName=("TheIsland" "ScorchedEarth_P" "Aberration_P" "Extinction" "Genesis" "Gen2" "TheCenter" "Ragnarok" "Valguero_P" "CrystalIsles" "Olympus")
arrGamePort=("7777" "7779" "7781" "7783" "7785" "7787" "7789" "7791" "7793" "7795" "7797")
arrQueryPort=("27051" "27052" "27053" "27054" "27055" "27056" "27057" "27058" "27059" "27060" "27061")
arrRCONPort=("27151" "27152" "27153" "27154" "27155" "27156" "27157" "27158" "27159" "27160" "27161")

## Text color codes for use with "echo -e" ##
COLORRESET='\033[0m'
RED='\033[00;31m'
GREEN='\033[00;32m'
YELLOW='\033[00;33m'
BLUE='\033[00;34m'
PURPLE='\033[00;35m'
CYAN='\033[00;36m'
LIGHTGRAY='\033[00;37m'
LRED='\033[01;31m'
LGREEN='\033[01;32m'
LYELLOW='\033[01;33m'
LBLUE='\033[01;34m'
LPURPLE='\033[01;35m'
LCYAN='\033[01;36m'
WHITE='\033[01;37m'

function f_verbose()
{
  if [ "${VerboseMode}" == "1" ]; then
    printf "${1}${2}${3}${4}${5}${6}${7}${8}${9}\n" | tee -a ${LogFile}
  fi
}
End of Method #2

Clustering

A cluster in terms of Ark means a group of maps that allow you to transfer your character, items and dinos between maps and prevents transfers in or out of the cluster. It makes a self-contained bubble of sorts. These maps can be running on the same server or different servers. If on the same server, they simply share a common folder. If on different servers, then they all need to access a common network share.

The command-line parameters related to enabling a cluster are the following:

Code: Select all

-ClusterDirOverride=/path/to/cluster
-NoTransferFromFiltering
-clusterid=123456789
The ClusterDirOverride path for this documentation is located in the gamectl.conf file in the "OtherOptions" variable. The clusterid is also located in the gamectl.conf file as the "ClusterID" variable. Be sure to change this to unique number.

Scripting - RCON Commands

Posted: Thu May 13, 2021 4:34 pm
by LHammonds
Command Script

The purpose of this script is to send commands to Ark's console via the RCON utility.

Any script that needs to send RCON commands will do so using this script. This script assumes you are using the same password for all your instances. If you need to reference a different file for each instance, you can modify that functionality here.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-cmd.sh  --output-document /opt/ark/scripts/game-cmd.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-cmd.sh
sudo chmod 0750 /opt/ark/scripts/game-cmd.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-cmd.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-cmd.sh
sudo chmod 0750 /opt/ark/scripts/game-cmd.sh

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-cmd.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Send a command to a specific game instance.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : rcon - https://github.com/LHammonds/c/blob/main/rcon.c
## Run Frequency : As needed for sending console commands to server.
## Parameters    : #1 - Game Instance
##               : #2 - Command to send to console
## Exit Codes    :
##    0 = Success
##    1 = Invalid array configuration
##    2 = Missing parameter #1
##    3 = Missing parameter #2
##    4 = Invalid parameter #1
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

#######################################
##            FUNCTIONS              ##
#######################################

function f_showhelp()
{
  printf "[ERROR] Missing required parameter(s)\n"
  printf "Syntax  : ${0} {InstanceName} {Command}\n"
  printf "Example1: ${0} TheIsland DoExit\n"
  printf "Example2: ${0} Ragnarok \"Broadcast Hello World!\"\n\n"
  printf "List of existing game instances\n"
  printf "===============================\n"
  for intIndex in "${!arrInstanceName[@]}"
  do
    ## Verify instance is installed ##
    if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
      printf "${arrInstanceName[${intIndex}]}\n"
    fi
  done
}

function f_runcmd()
{
  ## Get rcon port number based on instance. ##
  for intIndex in "${!arrInstanceName[@]}"
  do
    if [ "${GameInstance}" == "${arrInstanceName[${intIndex}]}" ]; then
      RCONPort=${arrRCONPort[${intIndex}]}
      break
    fi
  done
  if [ "${RCONPort}" == "" ]; then
    echo "ERROR: RCONPort could not be matched with ${GameInstance}"
    return 1
  fi
  ${ScriptDir}/rcon -f ${RCONFile} -p ${RCONPort} ${cmd}
  return 0
}

#######################################
##          PREREQUISITES            ##
#######################################

## Check existance of required command-line parameters.
case "$1" in
  "")
    f_showhelp
    exit 2
    ;;
  --help|-h|-?)
    f_showhelp
    exit 2
    ;;
  *)
    GameInstance=$1
    ;;
esac

case "$2" in
  "")
    f_showhelp
    exit 3
    ;;
  *)
    cmd="$2 $3 $4 $5 $6 $7 $8 $9"
    ;;
esac

## Validate GameInstance ##

if [ ! -f "${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
  echo "ERROR: Invalid parameter. ${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer" does not exist."
  exit 4
fi

#######################################
##               MAIN                ##
#######################################

f_runcmd ${GameInstance} ${cmd}
exit $?
End of Method #2

Scripting - Online Status

Posted: Thu May 13, 2021 7:19 pm
by LHammonds
game-online.sh

This is a utility script that will allow other scripts to determine if a particular instance is online or offline. It requires a valid instance name to be passed as a parameter. It will return 0 if the instance is online or 1 if offline. It will also output a human-readable result.

Example output:

Code: Select all

[Offline] TheIsland:27051
You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-online.sh  --output-document /opt/ark/scripts/game-online.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-online.sh
sudo chmod 0750 /opt/ark/scripts/game-online.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-online.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-online.sh
sudo chmod 0750 /opt/ark/scripts/game-online.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-online.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-online.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Check if game instance is listening to its port.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified low-rights user.
## Run Frequency : As needed.
## Parameters    : Game Instance
## Exit Codes    :
##    0 = Success/Offline
##    1 = Success/Online
##    2 = Non-successful exit
##    NOTE: Exit code is used by other scripts to count online instances.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

#######################################
##            FUNCTIONS              ##
#######################################

function f_showhelp()
{
  printf "[ERROR] Missing required parameter(s)\n"
  printf "Syntax : ${0} {InstanceName}\n"
  printf "Example: ${0} TheIsland\n\n"
  printf "List of existing game instances\n"
  printf "===============================\n"
  for intIndex in "${!arrInstanceName[@]}"
  do
    ## Verify instance is installed ##
    if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
      printf "${arrInstanceName[${intIndex}]}\n"
    fi
  done
}

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access. ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameService}" ]; then
  printf "[ERROR] This script must be run as root or ${GameService}.\n"
  exit 2
fi

## Check existance of required command-line parameters. ##
case "$1" in
  "")
    f_showhelp
    exit 2
    ;;
  --help|-h|-?)
    f_showhelp
    exit 2
    ;;
  *)
    GameInstance=$1
    ;;
esac

## Validate GameInstance ##
if [ ! -f "${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
  printf "[ERROR] Invalid parameter. ${GameRootDir}/${GameInstance} is not a valid instance.\n"
  exit 2
fi

#######################################
##           MAIN PROGRAM            ##
#######################################

## Get query port used based on instance name. ##
for intIndex in "${!arrInstanceName[@]}"
do
  if [ "${GameInstance}" == "${arrInstanceName[${intIndex}]}" ]; then
    QueryPort=${arrQueryPort[${intIndex}]}
    break
  fi
done
if [ "${QueryPort}" == "" ]; then
  printf "[ERROR] QueryPort could not be matched with ${GameInstance}\n"
  exit 2
fi

/usr/bin/lsof -i:${QueryPort} 1>/dev/null 2>&1
if [ "$?" == "0" ]; then
  echo -e "[${GREEN}Online${COLORRESET}]  ${GameInstance}:${QueryPort}"
  ReturnCode=1
else
  echo -e "[${RED}Offline${COLORRESET}] ${GameInstance}:${QueryPort}"
  ReturnCode=0
fi
exit ${ReturnCode}
End of Method #2

game-listall.sh

This script will utilize the human-readable output from game-online.sh and show the status of all valid instances.

Example output:

Code: Select all

Online Status
=============
[Offline] TheIsland:27051
[Online]  ScorchedEarth:27052
[Offline] Ragnarok:27058
Total online: 1
Total offline: 2
You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-listall.sh  --output-document /opt/ark/scripts/game-listall.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-listall.sh
sudo chmod 0750 /opt/ark/scripts/game-listall.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-listall.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-listall.sh
sudo chmod 0750 /opt/ark/scripts/game-listall.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-listall.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-listall.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : List all game instances and their status.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified low-rights user.
## Run Frequency : As needed.
## Parameters    : None
## Exit Codes    : Number of online instances.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

#######################################
##           MAIN PROGRAM            ##
#######################################

intTotalOn=0
IntTotalOff=0
printf "Online Status\n"
printf "=============\n"
## Get query port used based on instance name. ##
for intIndex in "${!arrInstanceName[@]}"
do
  ## Check if folder for game instance exists. ##
  if [ -d "${GameRootDir}/${arrInstanceName[${intIndex}]}" ]; then
    ## Output status of instance. ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]}
    ReturnCode=$?
    if [ "${ReturnCode}" = "0" ]; then
      intTotalOn=$((${intTotalOn} + 1))
    elif [ "${ReturnCode}" = "1" ]; then
      intTotalOff=$((${intTotalOff} + 1))
    fi
  fi
done
printf "Total online: ${intTotalOn}\n"
printf "Total offline: ${intTotalOff}\n"
exit ${intTotalOn}
End of Method #2

Scripting - Start

Posted: Fri May 14, 2021 7:41 am
by LHammonds
Start Overview

There will be 3 start scripts.
  1. game-start.sh - This will be used by the systemd service to start the service automatically after a reboot or manually using the "systemctl start" command.
  2. game-start-1.sh - This will be used by an interactive menu to allow you to pick a single game instance that is offline to start it up.
  3. game-start-all.sh - This will be used by an interactive menu to allow you to start all offline game instances. This makes it easy after doing and update for all instances.
game-start.sh

This start script will be responsible for starting the game instance based on what instance name is passed to it from the command-line. This script will be called from the systemd service such as "systemctl start" and if started under the root account, it will start the service using the low-rights user as defined in the configuration file.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-start.sh  --output-document /opt/ark/scripts/game-start.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-start.sh
sudo chmod 0750 /opt/ark/scripts/game-start.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-start.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-start.sh
sudo chmod 0750 /opt/ark/scripts/game-start.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-start.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-start.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Start a specific game instance.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified low-rights user.
## Run Frequency : As needed or when starting the server.
## Parameters    : Game Instance
## Exit Codes    :
##    0 = Success
##    1 = ERROR Missing parameter
##    2 = ERROR Invalid parameter
##    3 = ERROR Invalid array configuration
##    4 = ERROR Invalid user
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################
## Import standard variables and functions. ##
source /etc/gamectl.conf
LogFile="${LogDir}/game-start.log"

#######################################
##            FUNCTIONS              ##
#######################################

function f_showhelp()
{
  printf "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] Missing required parameter(s)\n" | tee -a ${LogFile}
  printf "Syntax : ${0} {InstanceName}\n"
  printf "Example: ${0} TheIsland\n"
  printf "List of existing game instances\n"
  printf "===============================\n"
  for intIndex in "${!arrInstanceName[@]}"
  do
    ## Verify instance is installed ##
    if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
      printf "${arrInstanceName[${intIndex}]}\n"
    fi
  done
}

#######################################
##          PREREQUISITES            ##
#######################################

## Check existence of required command-line parameters ##
case "$1" in
  "")
    f_showhelp
    exit 1
    ;;
  --help|-h|-?)
    f_showhelp
    exit 1
    ;;
  *)
    GameInstance=$1
    ;;
esac

## Validate GameInstance ##
if [ ! -f "${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
  printf "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] Invalid parameter. ${GameRootDir}/${GameInstance} is not a valid instance.\n" | tee -a ${LogFile}
  exit 2
fi

## Get rcon port number based on instance ##
for intIndex in "${!arrInstanceName[@]}"
do
  if [ "${GameInstance}" == "${arrInstanceName[${intIndex}]}" ]; then
    MapName=${arrMapName[${intIndex}]}
    GamePort=${arrGamePort[${intIndex}]}
    QueryPort=${arrQueryPort[${intIndex}]}
    RCONPort=${arrRCONPort[${intIndex}]}
    break
  fi
done
if [ "${MapName}" == "" ]; then
  echo "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] MapName could not be matched with ${GameInstance}" | tee -a ${LogFile}
  exit 3
fi
if [ "${GamePort}" == "" ]; then
  echo "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] GamePort could not be matched with ${GameInstance}" | tee -a ${LogFile}
  exit 3
fi
if [ "${QueryPort}" == "" ]; then
  echo "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] QueryPort could not be matched with ${GameInstance}" | tee -a ${LogFile}
  exit 3
fi
if [ "${RCONPort}" == "" ]; then
  echo "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] RCONPort could not be matched with ${GameInstance}" | tee -a ${LogFile}
  exit 3
fi
if [ "${USER}" == "${GameService}" ]; then
  ## Already running as the low-rights user, start the instance ##
  printf "`date +%Y-%m-%d_%H:%M:%S` - [INFO] Started ${GameInstance}\n" >> ${LogFile}
  f_verbose "${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer ${MapName}?listen?MultiHome=0.0.0.0?Port=${GamePort}?QueryPort=${QueryPort}?RCONPort=${RCONPort}?MaxPlayers=${MaxPlayers}?ServerAutoForceRespawnWildDinosInterval=86400?AllowCrateSpawnsOnTopOfStructures=True?GameModIds=${GameModIds} -clusterid=${ClusterID} ${OtherOptions}"
  ${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer ${MapName}?listen?MultiHome=0.0.0.0?Port=${GamePort}?QueryPort=${QueryPort}?RCONPort=${RCONPort}?MaxPlayers=${MaxPlayers}?ServerAutoForceRespawnWildDinosInterval=86400?AllowCrateSpawnsOnTopOfStructures=True?GameModIds=${GameModIds} -clusterid=${ClusterID} ${OtherOptions}
elif [ "${USER}" == "root" ]; then
  ## Run command using low-rights user ##
  printf "`date +%Y-%m-%d_%H:%M:%S` - [INFO] Started ${GameInstance}\n" >> ${LogFile}
  f_verbose "su --command='${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer ${MapName}?listen?MultiHome=0.0.0.0?Port=${GamePort}?QueryPort=${QueryPort}?RCONPort=${RCONPort}?MaxPlayers=${MaxPlayers}?ServerAutoForceRespawnWildDinosInterval=86400?AllowCrateSpawnsOnTopOfStructures=True?GameModIds=${GameModIds} -clusterid=${ClusterID} ${OtherOptions}' ${GameService}"
  su --command="${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer ${MapName}?listen?MultiHome=0.0.0.0?Port=${GamePort}?QueryPort=${QueryPort}?RCONPort=${RCONPort}?MaxPlayers=${MaxPlayers}?ServerAutoForceRespawnWildDinosInterval=86400?AllowCrateSpawnsOnTopOfStructures=True?GameModIds=${GameModIds} -clusterid=${ClusterID} ${OtherOptions}" ${GameService}
else
  ## Exit script with reason and error code ##
  printf "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] ${GameInstance} service must be started by ${GameService}\n" | tee -a ${LogFile}
  exit 4
fi
exit 0
End of Method #2

game-start-1.sh

Present a menu to start just one offline instance. This is useful after updating one instance.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-start-1.sh  --output-document /opt/ark/scripts/game-start-1.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-start-1.sh
sudo chmod 0750 /opt/ark/scripts/game-start-1.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-start-1.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-start-1.sh
sudo chmod 0750 /opt/ark/scripts/game-start-1.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-start-1.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-start-1.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Present menu to start one offline instance.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Must be run as root
## Run Frequency : As needed
## Parameters    : None
## Exit Codes    : None
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

#######################################
##            FUNCTIONS              ##
#######################################

function f_start()
{
  printf "\n[INFO] systemctl start ${1}\n"
  printf "\nNOTE: It may take a while before instance processes command.\n"
  systemctl start ${1}
}
#######################################
##               MAIN                ##
#######################################

## Loop through all defined game instances and build list of what is offline ##
arrList=()

for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Check if instance is running ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "0" ]; then
      ## Instance is offline ##
      arrList+=(${arrInstanceName[${intIndex}]})
    fi
  fi
done

if [ ${#arrList[@]} -eq 0 ]; then
  ## If no instances are offline, the exit ##
  printf "[INFO] No instances are offline.\n"
else
  ## Loop thru offline instances, present options for user to select ##
  printf "Select which offline instance you want started:\n"
  for intIndex in "${!arrList[@]}"
  do
    printf " ${intIndex}) ${arrList[intIndex]}\n"
  done
  printf " x) Exit\n"
  read -n 1 -p "Your choice: " char_answer;
  ## This will break if there are more than 10 instances ##
  case ${char_answer} in
    0)   f_start ${arrList[0]};;
    1)   f_start ${arrList[1]};;
    2)   f_start ${arrList[2]};;
    3)   f_start ${arrList[3]};;
    4)   f_start ${arrList[4]};;
    5)   f_start ${arrList[5]};;
    6)   f_start ${arrList[6]};;
    7)   f_start ${arrList[7]};;
    8)   f_start ${arrList[8]};;
    9)   f_start ${arrList[9]};;
    x|X) printf "\nExit\n";;
    *)   printf "\nExit\n";;
  esac
fi
exit 0
End of Method #2

game-start-all.sh

Start all game instances. This script can be used when you want to start all offline instances after an update.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-start-all.sh  --output-document /opt/ark/scripts/game-start-all.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-start-all.sh
sudo chmod 0750 /opt/ark/scripts/game-start-all.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-start-all.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-start-all.sh
sudo chmod 0750 /opt/ark/scripts/game-start-all.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-start-all.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-start-all.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Start all game instances.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root
## Run Frequency : As needed or when starting all servers.
## Parameters    : None
## Exit Codes    : None
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

#######################################
##               MAIN                ##
#######################################

## Loop through all defined game instances ##
for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Check if instance is running ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "0" ]; then
      ## Instance is offline ##
      echo "[INFO] Starting ${arrInstanceName[${intIndex}]}"
      systemctl start ${arrInstanceName[${intIndex}]}
    fi
  fi
done
exit 0
End of Method #2

Scripting - Stop

Posted: Fri May 14, 2021 8:29 am
by LHammonds
Stop Overview

There will be 3 stop scripts.
  1. game-stop.sh - This will be used by the systemd service to stop the service automatically after a reboot or manually using the "systemctl stop" command.
  2. game-stop-1.sh - This will be used by an interactive menu to allow you to pick a single online game instance to be stopped.
  3. game-stop-all.sh - This will be used by an interactive menu to allow you to stop all online game instances. This makes it easy before doing an update for all instances.
game-stop.sh

The stop script will be responsible for stopping the game instance based on what instance name is passed to it from the command-line. This script will be called from the systemd service such as "sytemctl stop".

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-stop.sh  --output-document /opt/ark/scripts/game-stop.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-stop.sh
sudo chmod 0750 /opt/ark/scripts/game-stop.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-stop.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-stop.sh
sudo chmod 0770 /opt/ark/scripts/game-stop.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-stop.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-stop.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Stop a specific game instance.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or with the specified low-rights user.
## Run Frequency : As needed or when shutting down server.
## Parameters    : Game Instance
## Exit Codes    :
##    0 = Success
##    2 = ERROR Missing parameter
##    3 = ERROR Invalid parameter
##    4 = ERROR Unexpected parameter
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf
LogFile="${LogDir}/game-stop.log"

#######################################
##            FUNCTIONS              ##
#######################################

function f_showhelp()
{
  printf "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] Missing required parameter(s)\n" | tee -a ${LogFile}
  printf "Syntax : ${0} {InstanceName}\n"
  printf "Example: ${0} TheIsland\n"
  printf "List of existing game instances\n"
  printf "===============================\n"
  for intIndex in "${!arrInstanceName[@]}"
  do
    ## Verify instance is installed ##
    if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
      printf "${arrInstanceName[${intIndex}]}\n"
    fi
  done
}

#######################################
##          PREREQUISITES            ##
#######################################

## Check existence of required command-line parameters ##
case "$1" in
  "")
    f_showhelp
    exit 2
    ;;
  --help|-h|-?)
    f_showhelp
    exit 2
    ;;
  *)
    GameInstance=$1
    ;;
esac

## Validate GameInstance ##

if [ ! -d "${GameRootDir}/${GameInstance}" ]; then
  printf "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] Invalid parameter. ${GameRootDir}/${GameInstance} does not exist.\n" | tee -a ${LogFile}
  exit 3
fi

#######################################
##               MAIN                ##
#######################################

if [ "${USER}" == "${GameService}" ]; then
  ## Already running as the low-rights user, stop the instance ##
  printf "`date +%Y-%m-%d_%H:%M:%S` - [INFO] ${GameInstance} stopping...\n" | tee -a ${LogFile}
  f_verbose "[${GameInstance}] Notifying players." | tee -a ${LogFile}
  ${ScriptDir}/game-cmd.sh ${GameInstance} ServerChat "Stop server command has been issued."
  ${ScriptDir}/game-cmd.sh ${GameInstance} ServerChat "Saving world..."
  f_verbose "[${GameInstance}] Saving world." | tee -a ${LogFile}
  ${ScriptDir}/game-cmd.sh ${GameInstance} SaveWorld
  ${ScriptDir}/game-cmd.sh ${GameInstance} ServerChat "World stopped.  Goodbye!"
  sleep 2
  ${ScriptDir}/game-cmd.sh ${GameInstance} DoExit
  printf "`date +%Y-%m-%d_%H:%M:%S` - [INFO] ${GameInstance} stopped.\n" | tee -a ${LogFile}
elif [ "${USER}" == "root" ]; then
  ## Run command using low-rights user ##
  printf "`date +%Y-%m-%d_%H:%M:%S` - [INFO] ${GameInstance} stopping...\n" | tee -a ${LogFile}
  f_verbose "[${GameInstance}] Notifying players." | tee -a ${LogFile}
  su --command="${ScriptDir}/game-cmd.sh ${GameInstance} ServerChat \"Stop server command has been issued.\"" ${GameService}
  su --command="${ScriptDir}/game-cmd.sh ${GameInstance} ServerChat \"Saving world...\"" ${GameService}
  f_verbose "[${GameInstance}] Saving world." | tee -a ${LogFile}
  su --command="${ScriptDir}/game-cmd.sh ${GameInstance} SaveWorld" ${GameService}
  su --command="${ScriptDir}/game-cmd.sh ${GameInstance} ServerChat \"World stopped.  Goodbye!\"" ${GameService}
  sleep 2
  su --command="${ScriptDir}/game-cmd.sh ${GameInstance} DoExit" ${GameService}
  printf "`date +%Y-%m-%d_%H:%M:%S` - [INFO] ${GameInstance} stopped.\n" | tee -a ${LogFile}
else
  ## Exit script with reason and error code ##
  printf "`date +%Y-%m-%d_%H:%M:%S` - [ERROR] ${GameInstance} Service must be stopped by ${GameService}\n" | tee -a ${LogFile}
  exit 4
fi
exit 0
End of Method #2
game-stop-1.sh

Present a menu to stop just one online instance. This is useful when updating one instance at a time.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-stop-1.sh  --output-document /opt/ark/scripts/game-stop-1.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-stop-1.sh
sudo chmod 0750 /opt/ark/scripts/game-stop-1.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-stop-1.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-stop-1.sh
sudo chmod 0750 /opt/ark/scripts/game-stop-1.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-stop-1.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-stop-1.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Present menu to stop one online instance.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified low-rights user.
## Run Frequency : As needed
## Parameters    : None
## Exit Codes    : None
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

#######################################
##            FUNCTIONS              ##
#######################################

function f_stop()
{
  nohup ${ScriptDir}/game-stop.sh ${1} > /dev/null 2>&1 &
} ## f_stop() ##

#######################################
##               MAIN                ##
#######################################

## Loop through all defined game instances and build list of what is online ##
arrList=()

for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Check if instance is running ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "1" ]; then
      ## Instance is active ##
      arrList+=(${arrInstanceName[${intIndex}]})
    fi
  fi
done

if [ ${#arrList[@]} -eq 0 ]; then
  ## If no instances are online, the exit ##
  printf "[INFO] No instances are online.\n"
else
  ## Loop thru online instances, present options for user to select ##
  printf "Select which online instance you want stopped:\n"
  for intIndex in "${!arrList[@]}"
  do
    printf " ${intIndex}) ${arrList[intIndex]}\n"
  done
  printf " x) Exit\n"
  read -n 1 -p "Your choice: " char_answer;
  printf "\n"
  ## This will break if there are more than 10 instances ##
  case ${char_answer} in
    0)   f_stop ${arrList[0]};;
    1)   f_stop ${arrList[1]};;
    2)   f_stop ${arrList[2]};;
    3)   f_stop ${arrList[3]};;
    4)   f_stop ${arrList[4]};;
    5)   f_stop ${arrList[5]};;
    6)   f_stop ${arrList[6]};;
    7)   f_stop ${arrList[7]};;
    8)   f_stop ${arrList[8]};;
    9)   f_stop ${arrList[9]};;
    x|X) printf "\nExit\n";;
    *)   printf "\nExit\n";;
  esac
fi
printf "\nNOTE: It takes time for an instance to process a stop command.\n"
exit 0
End of Method #2

game-stop-all.sh

Stop all game instances. This script can be used when you want to stop all online instances before a reboot or update.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-stop-all.sh  --output-document /opt/ark/scripts/game-stop-all.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-stop-all.sh
sudo chmod 0750 /opt/ark/scripts/game-stop-all.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-stop-all.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-stop-all.sh
sudo chmod 0750 /opt/ark/scripts/game-stop-all.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-stop-all.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-stop-all.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Stop all game instances.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified low-rights user.
## Run Frequency : As needed such as before updates or rebooting server.
## Parameters    : None
## Exit Codes    :
##    0 = Success, all instances are offline
##    1 = Failure, one or more instances are still running.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

#######################################
##            FUNCTIONS              ##
#######################################

function f_stop()
{
  nohup ${ScriptDir}/game-stop.sh ${1} > /dev/null 2>&1 &
} ## f_stop() ##

#######################################
##               MAIN                ##
#######################################

## Loop through all defined game instances ##
for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Only shutdown instance if running ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "1" ]; then
      ## Instance is active ##
      printf "[INFO] Stopping ${arrInstanceName[${intIndex}]}\n"
      f_stop ${arrInstanceName[${intIndex}]}
    fi
  fi
done

ExitStatus=1
intIndex=1
intMax=20
while [[ ${intIndex} -lt ${intMax} ]]
do
  ${ScriptDir}/game-listall.sh
  ReturnCode=$?
  if [ "${ReturnCode}" == "0" ]; then
    ## All instances are down ##
    printf "[INFO] All instances are offline\n"
    ExitStatus=0
    break
  else
    if [ "${ReturnCode}" == 1 ]; then
      printf "[INFO] ${ReturnCode} instance is online. Continuing to wait...\n"
    else
      printf "[INFO] ${ReturnCode} instances are online. Continuing to wait...\n"
    fi
  fi
  printf "[INFO] Pause ${intIndex}/${intMax}.\n"
  sleep 10
  ((intIndex++))
done
exit ${ExitStatus}
End of Method #2

Scripting - Fix Permissions

Posted: Fri May 14, 2021 8:30 am
by LHammonds
Fix Permissions

This is a utility script that will correct the file/folder permissions and ownership of all game instances or just the one specified if a parameter is used.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-fixperms.sh  --output-document /opt/ark/scripts/game-fixperms.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-fixperms.sh
sudo chmod 0750 /opt/ark/scripts/game-fixperms.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-fixperms.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-fixperms.sh
sudo chmod 0750 /opt/ark/scripts/game-fixperms.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-fixperms.sh
Copy/paste the following into the file.

Code: Select all

>> INSERT CODE HERE <<
End of Method #2

Run as a Service - Systemd

Posted: Fri May 14, 2021 8:30 am
by LHammonds
Run as a Service - Systemd

A generic system service will be created that will work for every instance by passing it the instance name as a parameter. This will allow the operating system to start each service when the server boots up and stop each service upon shutdown or reboot. This also means that the user can start and stop the instances just like any other standard Ubuntu service using the "systemctl" command such as "sudo systemctl stop arkservice@Ragnarok"

I will demonstrate how to crate two services so you can see what changes are needed for each instance. Adjust the following commands to fit your needs.

NOTE: If you want to have the service synchronize the files from the template before each start of the service, then remove the hashtag comment character at the beginning of the "ExecStartPre" line and make sure you have installed the "game-sync-1.sh" script. If you setup the crontab schedule to automatically update the template on a regular basis, this sync job will ensure the instance will match the template before every start of the service (e.g. automatic upgrade)

Code: Select all

sudo touch /lib/systemd/system/arkservice@.service
sudo chown root:root /lib/systemd/system/arkservice@.service
sudo chmod 0644 /lib/systemd/system/arkservice@.service
Now edit the file:

Code: Select all

sudo vi /lib/systemd/system/arkservice\@
Copy/paste the following into the file.

Code: Select all

[Unit]
Description=ARK: %i
Wants=network-online.target
After=syslog.target network.target nss-lookup.target network-online.target
[Service]
Type=simple
Restart=on-failure
RestartSec=30
StartLimitInterval=180s
StartLimitBurst=3
KillMode=control-group
KillSignal=SIGQUIT
SendSIGHUP=no
SendSIGKILL=yes
FinalKillSignal=SIGKILL
RuntimeMaxSec=infinity
LimitNOFILE=100000
#ExecStartPre=/opt/ark/scripts/game-sync-1.sh %i
TimeoutStartSec=180
ExecStart=/opt/ark/scripts/game-start.sh %i
ExecReload=
TimeoutStopSec=120
ExecStop=/opt/ark/scripts/game-stop.sh %i
User=arkservice
Group=arkserver
[Install]
WantedBy=multi-user.target
Make sure the paths to the scripts are correct and that the "User" matches "GameUser" in /etc/gamectl.conf and "Group" matches "GameService" in /etc/gamectl.conf

Now reload the systemd daemon so it can see the newly created service file.

Code: Select all

sudo systemctl daemon-reload
Now we enable the service(s) so the operating system will start and stop the service automatically whenever the server starts up or shuts down.

Tailor the following commands to suit your environment:

Code: Select all

sudo systemctl enable arkservice@TheIsland
sudo systemctl enable arkservice@Ragnarok
To manually start TheIsland instance, you can run the following command:

Code: Select all

sudo systemctl start arkservice@TheIsland
To check status of TheIsland instance:

Code: Select all

sudo systemctl status arkservice@TheIsland
To stop TheIsland instance:

Code: Select all

sudo systemctl stop arkservice@TheIsland

GameUserSettings.ini

Posted: Fri May 14, 2021 8:30 am
by LHammonds
GameUserSettings.ini

When you start the instance for the first time, a default GameUserSettings.ini file will be created and placed in this location:

Code: Select all

/opt/ark/{GameInstanceName}/ShooterGame/Saved/Config/LinuxServer/GameUserSettings.ini
You can customize it however you like to make the game work how you want it. At the bare minimum, you need to look at and adjust these settings:

Code: Select all

ActiveMods=
RCONPort=
ServerAdminPassword=
SessionName=
Port=
QueryPort=
Message=
You may have noticed that some of these values such as the ports and mod IDs are duplicated from the command-line startup parameters. They can be set in one place or the other but I have found that it can be helpful to ensure the map starts with the required correct settings even if it has a problem loading the the .ini file for some odd reason. I have seen this behavior when a new map comes out. Even though I witnessed this issue on a Windows server using a server manager program, I still replicate this double-coverage procedure on Linux as extra insurance.

Here is an example of a modified .ini file for a modded 5x Ragnarok PVE instance:

Code: Select all

[ServerSettings]
ActiveMods=1999447172,895711211,1262248217,1404697612,751991809,1609138312
AdminLogging=False
AllowAnyoneBabyImprintCuddle=True
AllowCaveBuildingPvE=False
AllowCrateSpawnsOnTopOfStructures=True
AllowFlyerCarryPVE=True
AllowHideDamageSourceFromLogs=False
AllowHitMarkers=True
AllowIntegratedSPlusStructures=True
AllowRaidDinoFeeding=False
AllowSharedConnections=True
AllowTekSuitPowersInGenesis=False
AllowThirdPersonPlayer=True
AlwaysAllowStructurePickup=True
AlwaysNotifyPlayerJoined=False
AlwaysNotifyPlayerLeft=False
AutoDestroyDecayedDinos=True
AutoDestroyOldStructuresMultiplier=2.000000
AutoSavePeriodMinutes=61.000000
BanListURL="http://arkdedicated.com/banlist.txt"
ClampItemSpoilingTimes=False
ClampResourceHarvestDamage=False
CrossARKAllowForeignDinoDownloads=False
DestroyUnconnectedWaterPipes=True
DifficultyOffset=1.000000
DisableDinoDecayPvE=False
DisableImprintDinoBuff=False
DisablePvEGamma=False
DisableStructureDecayPVE=True
DisableWeatherFog=True
EnableCryoSicknessPVE=False
EnableExtraStructurePreventionVolumes=True
EnablePVPGamma=False
FastDecayUnsnappedCoreStructures=False
ForceAllStructureLocking=False
GlobalVoiceChat=False
HarvestAmountMultiplier=5.000000
ItemStackSizeMultiplier=1.000000
KickIdlePlayersPeriod=1800.000000
ListenServerTetherDistanceMultiplier=1.000000
MaxGateFrameOnSaddles=2
MaxHexagonsPerCharacter=2500000
MaxPersonalTamedDinos=500.000000
MaxPlatformSaddleStructureLimit=80
MaxTamedDinos=4000.000000
NonPermanentDiseases=False
NoTributeDownloads=False
OnlyAutoDestroyCoreStructures=True
OnlyDecayUnsnappedCoreStructures=True
OverrideOfficialDifficulty=5.000000
OverrideStructurePlatformPrevention=True
OxygenSwimSpeedStatMultiplier=1.000000
PerPlatformMaxStructuresMultiplier=1.000000
PersonalTamedDinosSaddleStructureCost=19
PlatformSaddleBuildAreaBoundsMultiplier=1.000000
PreventDiseases=False
PreventDownloadDinos=False
PreventDownloadItems=False
PreventDownloadSurvivors=False
PreventOfflinePvP=True
PreventOfflinePvPInterval=15
PreventSpawnAnimations=False
PreventTribeAlliances=False
PreventUploadDinos=False
PreventUploadItems=False
PreventUploadSurvivors=False
ProximityChat=False
PvEDinoDecayPeriodMultiplier=3.500000
PvPDinoDecay=False
PvPStructureDecay=False
RaidDinoCharacterFoodDrainMultiplier=1.000000
RCONEnabled=True
RCONPort=27158
RCONServerGameLogBuffer=600.000000
ServerAdminPassword=YOUR_SERVER_ADMIN_PASSWORD_HERE
ServerAutoForceRespawnWildDinosInterval=86400.000000
ServerCrosshair=True
ServerForceNoHud=False
ServerHardcore=False
ServerPassword=
ServerPvE=True
ShowFloatingDamageText=True
ShowMapPlayerLocation=True
SpectatorPassword=
StructurePickupHoldDuration=0.500000
StructurePickupTimeAfterPlacement=30.000000
StructurePreventResourceRadiusMultiplier=1.000000
TamingSpeedMultiplier=5.000000
TheMaxStructuresInRange=10500.000000
TribeLogDestroyedEnemyStructures=False
TribeNameChangeCooldown=15.000000
TributeCharacterExpirationSeconds=3600
TributeDinoExpirationSeconds=3600
TributeItemExpirationSeconds=3600
UseOptimizedHarvestingHealth=False
XPMultiplier=5.000000

[/Script/ShooterGame.ShooterGameUserSettings]
MasterAudioVolume=1.000000
MusicAudioVolume=1.000000
SFXAudioVolume=1.000000
VoiceAudioVolume=1.000000
UIScaling=1.000000
UIQuickbarScaling=0.650000
CameraShakeScale=0.650000
bFirstPersonRiding=False
bThirdPersonPlayer=False
bInventoryHideUnlearnedEngrams=False
bShowStatusNotificationMessages=True
TrueSkyQuality=0.000000
FOVMultiplier=1.000000
GroundClutterDensity=0.000000
bFilmGrain=False
bMotionBlur=False
bUseDistanceFieldAmbientOcclusion=False
bUseSSAO=False
bShowChatBox=True
bCameraViewBob=True
bInvertLookY=False
bFloatingNames=True
bChatBubbles=True
bHideServerInfo=False
bJoinNotifications=False
bCraftablesShowAllItems=False
bLocalInventoryItemsShowAllItems=False
bLocalInventoryCraftingShowAllItems=True
bRemoteInventoryItemsShowAllItems=False
bRemoteInventoryCraftingShowAllItems=False
bRemoteInventoryShowEngrams=True
LookLeftRightSensitivity=1.000000
LookUpDownSensitivity=1.000000
GraphicsQuality=1
ActiveLingeringWorldTiles=1
ClientNetQuality=3
LastServerSearchType=0
LastServerSort=2
LastPVESearchType=-1
LastDLCTypeSearchType=-1
LastServerSortAsc=True
LastAutoFavorite=True
LastServerSearchHideFull=False
LastServerSearchProtected=False
LastServerSearchIncludeServersWithActiveMods=True
HideItemTextOverlay=True
bQuickToggleItemNames=True
bDistanceFieldShadowing=False
LODScalar=0.780000
bToggleToTalk=False
HighQualityMaterials=True
HighQualitySurfaces=True
bTemperatureF=False
bDisableTorporEffect=False
bChatShowSteamName=False
bChatShowTribeName=True
bReverseTribeLogOrder=False
EmoteKeyBind1=0
EmoteKeyBind2=0
bNoBloodEffects=False
bLowQualityVFX=False
bSpectatorManualFloatingNames=False
bSuppressAdminIcon=False
bUseSimpleDistanceMovement=False
bDisableMeleeCameraSwingAnims=False
bHighQualityAnisotropicFiltering=False
bUseLowQualityLevelStreaming=True
bPreventInventoryOpeningSounds=False
bPreventItemCraftingSounds=False
bPreventHitMarkers=False
bPreventCrosshair=False
bPreventColorizedItemNames=False
bHighQualityLODs=False
bExtraLevelStreamingDistance=False
bEnableColorGrading=True
DOFSettingInterpTime=0.000000
bDisableBloom=False
bDisableLightShafts=False
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
LastJoinedSessionPerCategory=" "
bDisableMenuTransitions=False
bEnableInventoryItemTooltips=True
bRemoteInventoryShowCraftables=False
bNoTooltipDelay=False
LocalItemSortType=0
LocalCraftingSortType=0
RemoteItemSortType=0
RemoteCraftingSortType=0
VersionMetaTag=1
ShowExplorerNoteSubtitles=False
DisableMenuMusic=False
DisableDefaultCharacterItems=False
bHideFloatingPlayerNames=False
bHideGamepadItemSelectionModifier=False
bToggleExtendedHUDInfo=False
PlayActionWheelClickSound=True
CompanionReactionVerbosity=3
EnableEnvironmentalReactions=True
EnableRespawnReactions=True
EnableDeathReactions=True
EnableSayHelloReactions=True
EnableEmoteReactions=True
EnableMovementSounds=True
CompanionSubtitleVerbosityLevel=3
CompanionIsHiddenState=False
MaxAscensionLevel=0
bHostSessionHasBeenOpened=False
bForceTPVCameraOffset=False
bDisableTPVCameraInterpolation=False
bFPVClimbingGear=False
bFPVGlidingGear=False
Gamma1=2.200000
Gamma2=3.000000
AmbientSoundVolume=1.000000
bAllowAnimationStaggering=True
bUseOldThirdPersonCameraTrace=False
bUseOldThirdPersonCameraOffset=False
bLowQualityAnimations=True
bShowedGenesisDLCBackground=False
bViewedAnimatedSeriesTrailer=False
bViewedARK2Trailer=False
bUseVSync=False
MacroCtrl0=
MacroCtrl1=
MacroCtrl2=
MacroCtrl3=
MacroCtrl4=
MacroCtrl5=
MacroCtrl6=
MacroCtrl7=
MacroCtrl8=
MacroCtrl9=
ResolutionSizeX=1280
ResolutionSizeY=720
LastUserConfirmedResolutionSizeX=1280
LastUserConfirmedResolutionSizeY=720
WindowPosX=-1
WindowPosY=-1
bUseDesktopResolutionForFullscreen=False
FullscreenMode=2
LastConfirmedFullscreenMode=2
Version=5

[ScalabilityGroups]
sg.ResolutionQuality=100
sg.ViewDistanceQuality=3
sg.AntiAliasingQuality=3
sg.ShadowQuality=3
sg.PostProcessQuality=3
sg.TextureQuality=3
sg.EffectsQuality=3
sg.TrueSkyQuality=3
sg.GroundClutterQuality=3
sg.IBLQuality=1
sg.HeightFieldShadowQuality=3
sg.GroundClutterRadius=10000

[/Game/PrimalEarth/CoreBlueprints/TestGameMode.TestGameMode_C]
bServerGameLogEnabled=True

[SessionSettings]
SessionName=Dimlock Ragnarok 5x
Port=7791
QueryPort=27065
MultiHome=0.0.0.0

[/Script/Engine.GameSession]
MaxPlayers=30

[MessageOfTheDay]
Message=Server info and Discord link can be found at dimlock.enjin.com
Duration=20

[ClassicFlyers]
AllowWyvernBreeding=1
WyvernRotationRate=1.3

[DinoStorage2]
AAExcludeClass=
AAExcludeSource=
BabyGrowthMultiplier=0.0
BabyInstantGrowth=False
BuffsPreventTrapping=
CanTrapCorpse=False
CorpseRemovesImprint=False
CorpseResetsLevel=False
DATAdultOnly=False
DATBabifiesAdult=False
DATBabyOnly=False
DATDefaultsInactive=False
DATDoTDeathOnly=False
DATGiveToPlayer=False
DATGiveToTerminal=False
DATStarvedOnly=False
DeathAutoTrap=False
DropCostume=False
DropEquipment=False
DropHelmet=False
DropSaddle=False
DropWeapon=False
EquipmentBlacklist=
ExcludeAmphibious=False
ExcludeBaby=False
ExcludeBig=False
ExcludeBoss=False
ExcludeBuffClass=
ExcludeBuffSource=
ExcludeClass=
ExcludeCorrupted=False
ExcludeEnragedGiga=False
ExcludeFlyer=False
ExcludeForeignPure=False
ExcludeImmobile=False
ExcludeRaid=False
ExcludeRobot=False
ExcludeSource=
ExcludeTag=
ExcludeTorped=False
ExcludeUnclaimed=False
ExcludeWater=False
IncludeBuffClass=
IncludeBuffSource=
IncludeClass=
IncludeForeign=False
IncludeSource=
IncludeTag=
ItemsPreventTrapping=
KeepAllyLookingStatus=False
KeepCuddleType=False
KeepEnabledMatingStatus=False
KeepForceTamedStatus=False
KeepIgnoreWhistleStatus=False
LockImprinter=False
NoCombatTrap=False
PassiveCharge=1.05
PassiveHP=1.05
PassiveStam=1.05
PassiveWool=1.05
PassiveXP=1.05
ReleaseIgnoreType=False
ReleaseUseDefaults=False
RemoveAllBuffs=False
RemoveCloneTags=False
RemoveMatingCooldown=False
RemoveMutationCount=False
RemovePassiveCharge=False
RemovePassiveHP=False
RemovePassiveStam=False
RemovePassiveWool=False
RemovePassiveXP=False
RemoveTitanLoad=False
RemoveTorpRecovery=False
RevivalHealth=0.0
SicknessFromBase=0.0
SicknessFromTerminal=0.0
SicknessIgnoreBaby=False
SicknessPreventsRelease=False
SoulSicknessCooldown=0.0
SoulSicknessDamage=10.0
SoulSicknessDuration=5.0
TorpRecovery=1.05
TrapHealth=0.0
TrapHurtTime=0.0
TrapTamedTime=0.0
TrapTamesFish=False
UseCryoNerf=False
UseCryoSickness=False
AllowBossArenaRelease=False
AllowBossCaveRelease=False
AllowMissionAreaRelease=False
ConvertClass=
EnemyStructureRange=10.0
IgnoreEnemyStructures=True
IgnoreSpatialCheck=False
IsBaseIfLinkedStructures=50
KeepTrapperEquipped=False
LootBagLifeSpan=30.0
MutatorAllowMek=False
MutatorAllowXeno=False
PreventAllCaveRelease=False
PreventCaveRelease=False
NoBossArenaTrap=False
ReleaseConsumesTrapper=False
ReleaseNearBase=0.0
ReleaseNearTerminal=0.0
ReleasePreventDupeID=False
ReleaseTribeLogs=False
RequiredReleaseLevel=0
RequireOwnership=False
RequireTribeRanks=False
TrapNearBase=0.0
TrapNearTerminal=0.0
TrappingTribeLogs=False
BasicTerminal=False
PrimitiveTerminal=False
SoulTerminalStackSize=100
SoulTerminalWeight=20.0
DisableTerminalPickup=False
TerminalExcludeAllPlatforms=False
TerminalExcludeDinoPlatforms=False
TerminalExcludeFlyerPlatforms=False
TerminalExcludeNonFlyerPlatforms=False
TerminalExcludePlatforms=
TerminalExcludeVehiclePlatforms=False
TerminalIncludePlatforms=
TerminalTribeLimit=0
TerminalProximityLimit=1
TerminalProximityRange=0.0
AmmolessNewbornAutoTrap=False
DisableNewbornAutoTrap=False
NATAnyUnclaimed=False
NewbornAutoTrapRecovery=False
NewbornAutoTrapTribeLimit=0
AutomationFuelCounter=1
AutomationItemBlacklist=
DistributeFertilizerRange=30.0
DistributePelletRange=30.0
EnableAllGeneration=False
EnableEggGeneration=False
EnableFertEggCollection=False
EnableFruitSeeding=False
EnableHoneyGeneration=False
EnableIncubation=False
EnableMiscPoopGeneration=False
EnablePassiveGeneration=False
EnablePoopConversion=False
EnablePoopGeneration=False
EnableWoolGeneration=False
IncubationMultiplier=2.0
OviBonusMultiplier=1.5
PickUpableEggsOnly=False
PoopConvertSpeed=1.05
TerminalAutomationFuel=
TerminalCheckInterval=60.0
TerminalEggMultiplier=1.05
TerminalHoneyMultiplier=1.05
TerminalMiscPoopMultiplier=1.05
TerminalNeedsPower=False
TerminalPassiveMultiplier=1.05
TerminalPoopMultiplier=1.05
TerminalWoolMultiplier=1.05
DisableTerminalPinCodes=False
DisableTerminalTransfer=False
GhostTerminal=False
GodlyTerminal=False
LockTerminalMesh=False
TerminalExcludeItems=
TerminalHealth=50000.0
TerminalOnlyAllowItems=
TerminalRaidPurge=False
TerminalSlots=300
UnlockGhostTerminal=False
UnlockTributeTerminal=False
VaultTerminal=False
SoulTrapDelay=0.25
SoulTrapStackSize=300
SoulTrapWeight=0.0
DestroyXP=1.05
PreventUpload=False
RemoveDestroyXP=False
SoulChargeRate=0.0
SoulDecayTime=0.0
SoulDragWeight=0.0
SoulMaxWeight=0.0
SoulMinWeight=0.0
SoulTrapThrowRange=1.0
PreventSoulTrapRidingEquip=False
SoulGunWeight=10000.0
AmmolessSoulGun=False
LootPullAnyTeam=False
LootPullMaxWeight=0.85
PreventLootPull=False
PreventSoulGunRidingEquip=False
SoulGunFireDelay=0.35
SoulGunRange=1.0
SoulFinderWeight=0.5
DisableFinderPull=False
FinderRange=50.0
DisableRecovery=False
RecoveryFileLimit=1800
RepDataSize=200
RepDataRate=0.2
ClampItemStats=False
ItemStatClamps[0]=0
ItemStatClamps[1]=0
ItemStatClamps[2]=0
ItemStatClamps[3]=0
ItemStatClamps[4]=0
ItemStatClamps[5]=0
ItemStatClamps[7]=0
CryoCooldownImmunity=False
DisableAdminMode=False
DisableAutoConvertClass=False
DisableStarterSouls=False
PreventOfflineMatingInterval=0.0
ServerStartDestroyNewborns=False
ServerStartDestroyUnclaimed=False
ServerStartRemoveBuffClass=
UnlockHairAndEmotes=False
ReleaseDestroyCostume=False
ReleaseDestroyEquipment=False
ReleaseDestroyHelmet=False
ReleaseDestroySaddle=False
ReleaseDestroyWeapon=False
OverideNamedEngramEntries=(EngramClassName="EngramEntry_SoulTraps_DS_C",EngramLevelRequirement=99)
OverideNamedEngramEntries=(EngramClassName="EngramEntry_SoulTerminal_DS_C",EngramLevelRequirement=99)
OverideNamedEngramEntries=(EngramClassName="EngramEntry_SoulGun_DS_C",EngramHidden=true)
OverideNamedEngramEntries=(EngramClassName="EngramEntry_SoulFinder_DS_C",EngramLevelRequirement=99)
ConfigOverrideItemCraftingCosts=(ItemClassString="SoulTraps_DS_C",BaseCraftingResourceRequirements=((ResourceItemTypeString="PrimalItem_WeaponEmptyCryopod_C",BaseResourceRequirement=2.0,bCraftingRequireExactResourceType=false)))

[SuperStructures]
AllowIntakeToPlaceWithoutWater=True

Scripting - Updates

Posted: Fri May 14, 2021 8:30 am
by LHammonds
Updating Template from Steam

This script will update the application in the template folder from Steam. Since the template is never running so it can be updated at any time throughout the day.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-update-server.sh  --output-document /opt/ark/scripts/game-update-server.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-update-server.sh
sudo chmod 0750 /opt/ark/scripts/game-update-server.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-update-server.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-update-server.sh
sudo chmod 0750 /opt/ark/scripts/game-update-server.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-update-server.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-update-server.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Update the server template from Steam.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified install user.
## Run Frequency : Whenever needed.  Template should never be running.
## Parameters    : N/A
## Exit Codes    :
##    0 = Success
##    1 = Invalid user
##    ? = All other error codes are the result of steamcmd exit code.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

LogFile="${LogDir}/game-update-server.log"
SteamOut="${TempDir}/steam.out"
NoUpdate="already up to date"
UpgradeSuccess="fully installed"

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ]; then
  printf "[ERROR] This script must be run as root or ${GameUser}.\n"
  exit 1
fi

#######################################
##              MAIN                 ##
#######################################

if [ -f ${SteamOut} ]; then
  ## Remove the temp file before we use it ##
  rm ${SteamOut}
fi
printf "`date +%Y-%m-%d_%H:%M:%S` - Started template update.\n" | tee -a ${LogFile}
if [ "${USER}" == "root" ]; then
  ## Switch to the install user and update the instance ##
  f_verbose "su --command='${SteamCMD} +login anonymous +force_install_dir ${TemplateDir} +app_update ${ServerID} +quit > ${SteamOut}' ${GameUser}"
  su --command="${SteamCMD} +login anonymous +force_install_dir ${TemplateDir} +app_update ${ServerID} +quit > ${SteamOut}" ${GameUser}
  ReturnCode=$?
elif [ "${USER}" == "${GameUser}" ]; then
  ## Already running as the install user, update the instance ##
  f_verbose "${SteamCMD} +login anonymous +force_install_dir ${TemplateDir} +app_update ${ServerID} +quit > ${SteamOut}"
  ${SteamCMD} +login anonymous +force_install_dir ${TemplateDir} +app_update ${ServerID} +quit > ${SteamOut}
  ReturnCode=$?
fi
f_verbose "[INFO] SteamCMD ReturnCode=${ReturnCode}"
if grep -Fq "${NoUpdate}" ${SteamOut}; then
  ## No update found ##
  printf "[INFO] No update found.\n" | tee -a ${LogFile}
else
  if grep -Fq "${UpgradeSuccess}" ${SteamOut}; then
    ## Upgrade peformed and was successful ##
    printf "[INFO] Update performed and was successful.\n" | tee -a ${LogFile}
  else
    ## Other issue (could be error, lack of space, timeout, etc.) ##
    printf "[UNKNOWN] Unknown result...need exact wording.\n" | tee -a ${LogFile}
    printf "[SAVE] Output text saved to ${GameRootDir}/bak/`date +%Y-%m-%d_%H-%M-%S`-steam.out\n" | tee -a ${LogFile}
    cp ${SteamOut} ${BackupDir}/`date +%Y-%m-%d_%H-%M-%S`-steam.out
  fi
fi
printf "`date +%Y-%m-%d_%H:%M:%S` - Completed template update.\n" | tee -a ${LogFile}
exit ${ReturnCode}
End of Method #2

Updating Workshop Mod Versions from Steam

This script will update the mods in the template folder from Steam Workshop. Since the template is never running so it can be updated at any time throughout the day.

This process is a bit more complicated because a simple download of the mods is not enough. The mods on the workshop are compressed and not usable as is and thus must be decompressed and made ready. You see the same behavior in the client. On the client side, you subscribe to workshop mods and steam downloads them but they have to be "installed" which is done automatically when you have the game started and sitting at the main menu. It shows a "installing mod" message at the bottom-right corner.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/arkit.py  --output-document /opt/ark/scripts/arkit.py
wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/Ark_Mod_Downloader.py  --output-document /opt/ark/scripts/Ark_Mod_Downloader.py
wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-update-mods.sh  --output-document /opt/ark/scripts/game-update-mods.sh
sudo chown arkserver:arkserver /opt/ark/scripts/*.py
sudo chown arkserver:arkserver /opt/ark/scripts/game-update-mods.sh
sudo chmod 0640 /opt/ark/scripts/*.py
sudo chmod 0750 /opt/ark/scripts/game-update-mods.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/arkit.py
sudo touch /opt/ark/scripts/Ark_Mod_Downloader.py
sudo touch /opt/ark/scripts/game-update-mods.sh
sudo chown arkserver:arkserver /opt/ark/scripts/*.py
sudo chown arkserver:arkserver /opt/ark/scripts/game-update-mods.sh
sudo chmod 0640 /opt/ark/scripts/*.py
sudo chmod 0750 /opt/ark/scripts/game-update-mods.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/arkit.py
Copy/paste the following into the file.

Code: Select all

'''
ARK: Survival Evolved Toolkit

Only supports Python3, Python2 is end of life and outdated thus not supported.

Purpose:
  Provide a Python toolkit for ARK. Originally designed to unpack the workshop archives.

Notice:
  I use PEP 8 as per it was intended; if you want to PEP 8 me read it first instead of being foolish: "A Foolish Consistency is the Hobgoblin of Little Minds"
'''


import struct
import zlib
import sys
import logging


__author__ = "James E"
__contact__ = "https://github.com/project-umbrella/arkit.py"
__copyright__ = "Copyright 2015, Project Umbrella"
__version__ = "0.0.0.1"
__status__ = "Prototype"
__date__ = "16 October 2015"
__license__ = "GPL v3.0 https://github.com/project-umbrella/arkit.py/blob/master/LICENSE"


logging.basicConfig(stream=sys.stderr, level=logging.CRITICAL)

class UnpackException(Exception):
    pass

class SignatureUnpackException(UnpackException):
    pass

class CorruptUnpackException(UnpackException):
    pass

def unpack(src, dst):
    '''
    Unpacks ARK's Steam Workshop *.z archives.

    Accepts two arguments:
        src = Source File/Archive
        dst = Destination File

    Error Handling:
        Currently logs errors via logging with an archive integrity as well as raising a custom exception. Also logs some debug and info messages.
        All file system errors are handled by python core.

    Process:
        1. Open the source file.
        2. Read header information from archive:
            - 00 (8 bytes) signature (6 bytes) and format ver (2 bytes)
            - 08 (8 byes) unpacked/uncompressed chunk size
            - 10 (8 bytes) packed/compressed full size
            - 18 (8 bytes) unpacked/uncompressed size
            - 20 (8 bytes) first chunk packed/compressed size
            - 26 (8 bytes) first chunk unpacked/uncompressed size
            - 20 and 26 repeat until the total of all the unpacked/uncompressed chunk sizes matches the unpacked/uncompressed full size.
        2. Read all the archive data and verify integrity (there should only be one partial chunk, and each chunk should match the archives header).
        3. Write the file.

    Development Note:
        - Not thoroughly tested for errors. There may be instances where this method may fail either to extract a valid archive or detect a corrupt archive.
        - Prevent overwriting files unless requested to do so.
        - Create a batch method.
    '''

    with open(src, 'rb') as f:
        sigver = struct.unpack('q', f.read(8))[0]
        unpacked_chunk = f.read(8)
        packed = f.read(8)
        unpacked = f.read(8)
        size_unpacked_chunk = struct.unpack('q', unpacked_chunk)[0]
        size_packed = struct.unpack('q', packed)[0]
        size_unpacked = struct.unpack('q', unpacked)[0]

        #Verify the integrity of the Archive Header
        if sigver == 2653586369:
            if isinstance(size_unpacked_chunk, int) and isinstance(size_packed , int) and isinstance(size_unpacked , int):
                logging.info("Archive is valid.")
                logging.debug("Archive header size information. Unpacked Chunk: {}({}) Full Packed: {}({}) Full Unpacked: {}({})".format(size_unpacked_chunk, unpacked_chunk, size_packed, packed, size_unpacked, unpacked))

                #Obtain the Archive Compression Index
                compression_index = []
                size_indexed = 0
                while size_indexed < size_unpacked:
                    raw_compressed = f.read(8)
                    raw_uncompressed = f.read(8)
                    compressed = struct.unpack('q', raw_compressed)[0]
                    uncompressed = struct.unpack('q', raw_uncompressed)[0]
                    compression_index.append((compressed, uncompressed))
                    size_indexed += uncompressed
                    logging.debug("{}: {}/{} ({}/{}) - {} - {}".format(len(compression_index), size_indexed, size_unpacked, compressed, uncompressed, raw_compressed, raw_uncompressed))

                if size_unpacked != size_indexed:
                    msg = "Header-Index mismatch. Header indicates it should only have {} bytes when uncompressed but the index indicates {} bytes.".format(size_unpacked, size_indexed)
                    logging.critical(msg)
                    raise CorruptUnpackException(msg)

                #Read the actual archive data
                data = b''
                read_data = 0
                for compressed, uncompressed in compression_index:
                    compressed_data = f.read(compressed)
                    uncompressed_data = zlib.decompress(compressed_data)

                    #Verify the size of the data is consistent with the archives index
                    if len(uncompressed_data) == uncompressed:
                        data += uncompressed_data
                        read_data += 1

                        #Verify there is only one partial chunk
                        if len(uncompressed_data) != size_unpacked_chunk and read_data != len(compression_index):
                            msg = "Index contains more than one partial chunk: was {} when the full chunk size is {}, chunk {}/{}".format(len(uncompressed_data), size_unpacked_chunk, read_data, len(compression_index))
                            logging.critical(msg)
                            raise CorruptUnpackException(msg)
                    else:
                        msg = "Uncompressed chunk size is not the same as in the index: was {} but should be {}.".format(len(uncompressed_data), uncompressed)
                        logging.critical(msg)
                        raise CorruptUnpackException(msg)
            else:
                msg = "Data types in the headers should be int's. Size Types: unpacked_chunk({}), packed({}), unpacked({})".format(sigver, type(size_unpacked_chunk), type(size_packed), type(size_unpacked))
                logging.critical(msg)
                raise CorruptUnpackException(msg)
        else:
            msg = "The signature and format version is incorrect. Signature was {} should be 2653586369.".format(sigver)
            logging.critical(msg)
            raise SignatureUnpackException(msg)

    #Write the extracted data to disk
    with open(dst, 'wb') as f:
        f.write(data)
    logging.info("Archive has been extracted.")

Code: Select all

sudo vi /opt/ark/scripts/Ark_Mod_Downloader.py
Copy/paste the following into the file.

Code: Select all

#############################################################
## Name          : Ark_Mod_Download.py
## Version       : 1.1
## Date          : 2021-04-20
## Author        : barrycarey (Matthew Carey)
## Purpose       : Download and extract Ark mods from Steam workshop.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Python3, arkit.py
## Run Frequency : As needed.
## Parameters    : 
##   --modids - Space separated list of Mod IDs to download
##   --workingdir (Optional) - Home directory of your ARK server
##   --steamcmd (Optional) - Path to SteamCMD. If not provided the tool will
##                           download SteamCMD to the CWD
##   --update (Optional) - Update all current mods installed on the server
##   --namefile (Optional) - Creates "Modname.name" file in the mod folder.
## Example:
##   python3 /opt/ark/Ark_Mod_Downloader.py --modids "1609138312"
##   --workingdir "/opt/ark/template" --steamcmd "/usr/games" --namefile
## Exit Codes    :
##    0 = Success.
##    1 = SteamCMD not found.
##    2 = Ark not found in path.
##    3 = Failed to extract SteamCMD archive.
##    4 = Mod ID not provided nor request to update all mods.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2017-10-18 1.0 bc  Created script.
## 2021-04-20 1.1 LTH Modified to work on Linux and include exit codes.
#############################################################

import arkit
import sys
import os
import argparse
import shutil
import subprocess
from collections import OrderedDict
import struct
import urllib.request
import zipfile

class ArkModDownloader():

    def __init__(self, steamcmd, modids, working_dir, mod_update, modname, preserve=False):

        # If not working directory provided, check if CWD has an ARK server.
        self.working_dir = working_dir
        if not working_dir:
            self.working_dir_check()

        self.steamcmd = steamcmd  # Path to SteamCMD exe

        if not self.steamcmd_check():
            print("SteamCMD Not Found And We Were Unable To Download It")
            sys.exit(1)

        self.modname = modname
        self.installed_mods = []  # List to hold installed mods
        self.map_names = []  # Stores map names from mod.info
        self.meta_data = OrderedDict([])  # Stores key value from modmeta.info
        self.temp_mod_path = os.path.join(self.working_dir, r"steamapps/workshop/content/346110")
        self.preserve = preserve

        self.prep_steamcmd()

        if mod_update:
            print("[+] Mod Update Is Selected.  Updating Your Existing Mods")
            self.update_mods()

        # If any issues happen in download and extract chain this returns false
        if modids:
            for mod in modids:
                if self.download_mod(mod):
                    if self.move_mod(mod):
                        print("[+] Mod {} Installation Finished".format(str(mod)))
                else:
                    print("[+] There was as problem downloading mod {}.  See above errors".format(str(mod)))

    def create_mod_name_txt(self, mod_folder, modid):
        print(os.path.join(mod_folder, self.map_names[0] + " - " + modid + ".txt"))
        with open(os.path.join(mod_folder, self.map_names[0] + ".txt"), "w+") as f:
            f.write(modid)

    def working_dir_check(self):
        print("[!] No working directory provided.  Checking Current Directory")
        print("[!] " + os.getcwd())
        if os.path.isdir(os.path.join(os.getcwd(), "ShooterGame/Content")):
            print("[+] Current Directory Has Ark Server.  Using The Current Directory")
            self.working_dir = os.getcwd()
        else:
            print("[x] Current Directory Does Not Contain An ARK Server. Aborting")
            sys.exit(2)

    def steamcmd_check(self):
        """
        If SteamCMD path is provided verify that exe exists.
        If no path provided check TCAdmin path working dir.  If not located try to download SteamCMD.
        :return: Bool
        """

        # Check provided directory
        if self.steamcmd:
            print("[+] Checking Provided Path For SteamCMD")
            if os.path.isfile(os.path.join(self.steamcmd, "steamcmd")):
                self.steamcmd = os.path.join(self.steamcmd, "steamcmd")
                print("[+] SteamCMD Found At Provided Path")
                return True

        # Check TCAdmin Directory
        print("[+] SteamCMD Location Not Provided. Checking Common Locations")
        if os.path.isfile(r"C:\Program Files\TCAdmin2\Monitor\Tools\SteamCmd\steamcmd"):
            print("[+] SteamCMD Located In TCAdmin Directory")
            self.steamcmd = r"C:\Program Files\TCAdmin2\Monitor\Tools\SteamCmd\steamcmd"
            return True

        # Check working directory
        if os.path.isfile(os.path.join(self.working_dir, "SteamCMD/steamcmd")):
            print("[+] Located SteamCMD")
            self.steamcmd = os.path.join(self.working_dir, "SteamCMD/steamcmd")
            return True

        print("[+} SteamCMD Not Found In Common Locations. Attempting To Download")

        try:
            with urllib.request.urlopen("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip") as response:
                if not os.path.isdir(os.path.join(self.working_dir, "SteamCMD")):
                    os.mkdir(os.path.join(self.working_dir, "SteamCMD"))

                steam_cmd_zip = os.path.join(self.working_dir, "steamcmd.zip")
                with open(steam_cmd_zip, "w+b") as output:
                    output.write(response.read())

                zip_file = zipfile.ZipFile(steam_cmd_zip)
                try:
                    zip_file.extractall(os.path.join(self.working_dir, "SteamCMD"))
                except zipfile.BadZipfile as e:
                    print("[x] Failed To Extract steamcmd.zip. Aborting")
                    print("[x] Error: " + e)
                    sys.exit(3)

        except urllib.request.HTTPError as e:
            print("[x] Failed To Download SteamCMD. Aborting")
            print("[x] ERROR: " + e)
            return False

        self.steamcmd = os.path.join(self.working_dir, r"SteamCMD/steamcmd")

        return True

    def prep_steamcmd(self):
        """
        Delete steamapp folder to prevent Steam from remembering it has downloaded this mod before
        This is mainly for game hosts.  Example, hosts using TCAdmin have one SteamCMD folder. If mod was downloaded
        by another customer SteamCMD will think it already exists and not download again.
        :return:
        """

        if self.preserve:
            return

        steamapps = os.path.join(os.path.dirname(self.steamcmd), "steamapps")

        if os.path.isdir(steamapps):
            print("[+] Removing Steamapps Folder")
            try:
                shutil.rmtree(steamapps)
            except OSError:
                """
                If run on a TCAdmin server using TCAdmin's SteamCMD this may prevent mod from downloading if another
                user has downloaded the same mod.  This is due to SteamCMD's cache.  It will think this mod has is
                already installed and up to date.
                """
                print("[x] Failed To Remove Steamapps Folder. This is normally okay.")
                print("[x] If this is a TCAdmin Server and using the TCAdmin SteamCMD it may prevent mod from downloading")

    def update_mods(self):
        self.build_list_of_mods()
        if self.installed_mods:
            for mod in self.installed_mods:
                print("[+] Updating Mod " + mod)
                if not self.download_mod(mod):
                    print("[x] Error Updating Mod " + mod)
        else:
            print("[+] No Installed Mods Found.  Skipping Update")

    def build_list_of_mods(self):
        """
        Build a list of all installed mods by grabbing all directory names from the mod folder
        :return:
        """
        if not os.path.isdir(os.path.join(self.working_dir, "ShooterGame/Content/Mods")):
            return
        for curdir, dirs, files in os.walk(os.path.join(self.working_dir, "ShooterGame/Content/Mods")):
            for d in dirs:
                self.installed_mods.append(d)
            break

    def download_mod(self, modid):
        """
        Launch SteamCMD to download ModID
        /usr/games/steamcmd +login anonymous +force_install_dir /ArkGenesis +app_update 376030 +workshop_download_item 346110 849985437 +quit
        :return:
        """
        print("[+] Starting Download of Mod " + str(modid))
        args = []
        args.append(self.steamcmd)
        args.append("+login anonymous")
        args.append("+force_install_dir ")
        args.append(self.working_dir)
        args.append("+workshop_download_item")
        args.append("346110")
        args.append(modid)
        args.append("+quit")

        #wd = os.getcwd()
        #os.chdir(self.working_dir)
        res = subprocess.run(args, shell=False)
        #os.chdir(wd)
        
        print("cmd used: "+str(res.args))

        return True if self.extract_mod(modid) else False


    def extract_mod(self, modid):
        """
        Extract the .z files using the arkit lib.
        If any file fails to download this whole script will abort
        :return: None
        """

        print("[+] Extracting .z Files.")

        try:
            for curdir, subdirs, files in os.walk(os.path.join(self.temp_mod_path, modid, "WindowsNoEditor")):
                for file in files:
                    name, ext = os.path.splitext(file)
                    if ext == ".z":
                        src = os.path.join(curdir, file)
                        dst = os.path.join(curdir, name)
                        uncompressed = os.path.join(curdir, file + ".uncompressed_size")
                        arkit.unpack(src, dst)
                        #print("[+] Extracted " + file)
                        os.remove(src)
                        if os.path.isfile(uncompressed):
                            os.remove(uncompressed)

        except (arkit.UnpackException, arkit.SignatureUnpackException, arkit.CorruptUnpackException) as e:
            print("[x] Unpacking .z files failed, aborting mod install")
            return False

        if self.create_mod_file(modid):
            if self.move_mod(modid):
                return True
            else:
                return False


    def move_mod(self, modid):
        """
        Move mod from SteamCMD download location to the ARK server.
        It will delete an existing mod with the same ID
        :return:
        """

        ark_mod_folder = os.path.join(self.working_dir, "ShooterGame/Content/Mods")
        output_dir = os.path.join(ark_mod_folder, str(modid))
        source_dir = os.path.join(self.temp_mod_path, modid, "WindowsNoEditor")

        # TODO Need to handle exceptions here
        if not os.path.isdir(ark_mod_folder):
            print("[+] Creating Directory: " + ark_mod_folder)
            os.mkdir(ark_mod_folder)

        if os.path.isdir(output_dir):
            shutil.rmtree(output_dir)

        print("[+] Moving Mod Files To: " + output_dir)
        shutil.copytree(source_dir, output_dir)
        print("[+] Moving Mod File To: " + output_dir + ".mod")
        shutil.move(output_dir+"/.mod", output_dir+".mod")

        if self.modname:
            print("Creating Mod Name File")
            self.create_mod_name_txt(ark_mod_folder, modid)

        return True

    def create_mod_file(self, modid):
        """
        Create the .mod file.
        This code is an adaptation of the code from Ark Server Launcher.  All credit goes to Face Wound on Steam
        :return:
        """
        if not self.parse_base_info(modid) or not self.parse_meta_data(modid):
            return False

        print("[+] Writing .mod File")
        with open(os.path.join(self.temp_mod_path, modid, r"WindowsNoEditor/.mod"), "w+b") as f:

            modid = int(modid)
            #f.write(struct.pack('ixxxx', modid))  # Needs 4 pad bits
            f.write(struct.pack('Ixxxx', modid))  # Needs 4 pad bits
            self.write_ue4_string("ModName", f)
            self.write_ue4_string("", f)

            map_count = len(self.map_names)
            f.write(struct.pack("i", map_count))

            for m in self.map_names:
                self.write_ue4_string(m, f)

            # Not sure of the reason for this
            num2 = 4280483635
            f.write(struct.pack('I', num2))
            num3 = 2
            f.write(struct.pack('i', num3))

            if "ModType" in self.meta_data:
                mod_type = b'1'
            else:
                mod_type = b'0'

            # TODO The packing on this char might need to be changed
            f.write(struct.pack('p', mod_type))
            meta_length = len(self.meta_data)
            f.write(struct.pack('i', meta_length))

            for k, v in self.meta_data.items():
                self.write_ue4_string(k, f)
                self.write_ue4_string(v, f)

        return True

    def read_ue4_string(self, file):
        count = struct.unpack('i', file.read(4))[0]
        flag = False
        if count < 0:
            flag = True
            count -= 1

        if flag or count <= 0:
            return ""

        return file.read(count)[:-1].decode()

    def write_ue4_string(self, string_to_write, file):
        string_length = len(string_to_write) + 1
        file.write(struct.pack('i', string_length))
        barray = bytearray(string_to_write, "utf-8")
        file.write(barray)
        file.write(struct.pack('p', b'0'))

    def parse_meta_data(self, modid):
        """
        Parse the modmeta.info files and extract the key value pairs need to for the .mod file.
        How To Parse modmeta.info:
            1. Read 4 bytes to tell how many key value pairs are in the file
            2. Read next 4 bytes tell us how many bytes to read ahead to get the key
            3. Read ahead by the number of bytes retrieved from step 2
            4. Read next 4 bytes to tell how many bytes to read ahead to get value
            5. Read ahead by the number of bytes retrieved from step 4
            6. Start at step 2 again
        :return: Dict
        """

        print("[+] Collecting Mod Meta Data From modmeta.info")
        print("[+] Located The Following Meta Data:")

        mod_meta = os.path.join(self.temp_mod_path, modid, r"WindowsNoEditor/modmeta.info")
        if not os.path.isfile(mod_meta):
            print("[x] Failed To Locate modmeta.info. Cannot continue without it.  Aborting")
            return False

        with open(mod_meta, "rb") as f:

            total_pairs = struct.unpack('i', f.read(4))[0]

            for i in range(total_pairs):

                key, value = "", ""

                key_bytes = struct.unpack('i', f.read(4))[0]
                key_flag = False
                if key_bytes < 0:
                    key_flag = True
                    key_bytes -= 1

                if not key_flag and key_bytes > 0:

                    raw = f.read(key_bytes)
                    key = raw[:-1].decode()

                value_bytes = struct.unpack('i', f.read(4))[0]
                value_flag = False
                if value_bytes < 0:
                    value_flag = True
                    value_bytes -= 1

                if not value_flag and value_bytes > 0:
                    raw = f.read(value_bytes)
                    value = raw[:-1].decode()

                # TODO This is a potential issue if there is a key but no value
                if key and value:
                    print("[!] " + key + ":" + value)
                    self.meta_data[key] = value

        return True


    def parse_base_info(self, modid):

        print("[+] Collecting Mod Details From mod.info")

        mod_info = os.path.join(self.temp_mod_path, modid, r"WindowsNoEditor/mod.info")

        if not os.path.isfile(mod_info):
            print("[x] Failed to locate mod.info at " + mod_info + ". Cannot Continue.  Aborting")
            return False

        with open(mod_info, "rb") as f:
            self.read_ue4_string(f)
            map_count = struct.unpack('i', f.read(4))[0]

            for i in range(map_count):
                cur_map = self.read_ue4_string(f)
                if cur_map:
                    self.map_names.append(cur_map)

        return True



def main():
    parser = argparse.ArgumentParser(description="A utility to download ARK Mods via SteamCMD")
    parser.add_argument("--workingdir", default=None, dest="workingdir", help="Game server home directory.  Current Directory is used if this is not provided")
    parser.add_argument("--modids", nargs="+", default=None, dest="modids", help="ID of Mod To Download")
    parser.add_argument("--steamcmd", default=None, dest="steamcmd", help="Path to SteamCMD")
    parser.add_argument("--update", default=None, action="store_true", dest="mod_update", help="Update Existing Mods.  ")
    parser.add_argument("--preserve", default=None, action="store_true", dest="preserve", help="Don't Delete StreamCMD Content Between Runs")
    parser.add_argument("--namefile", default=None, action="store_true", dest="modname", help="Create a .name File With Mods Text Name")

    args = parser.parse_args()

    if not args.modids and not args.mod_update:
        print("[x] No Mod ID Provided and Update Not Selected.  Aborting")
        print("[?] Please provide a Mod ID to download or use --update to update your existing mods")
        sys.exit(4)

    ArkModDownloader(args.steamcmd,
                     args.modids,
                     args.workingdir,
                     args.mod_update,
                     args.modname,
                     args.preserve)



if __name__ == '__main__':
    main()

Code: Select all

sudo vi /opt/ark/scripts/game-update-mods.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-update-mods.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Update mods for the game template.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified install user.
## Run Frequency : As needed.
## Parameters    : None
## Exit Codes    :
##    0 = Success
##  200 = ERROR Incorrect user
##  201 = ERROR Invalid template path
##    ? = All other numbers are the sum of the total amount of errors.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

ErrorCount=0
LogFile="${LogDir}/game-update-mods.log"
StartTime="$(date +%s)"

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access. ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ]; then
  printf "[ERROR] This script must be run as root or ${GameUser}.\n"
  exit 200
fi

## Validate Template Folder ##
if [ ! -d "${TemplateDir}" ]; then
  printf "[ERROR] Invalid template path. ${TemplateDir} does not exist.\n"
  exit 201
fi

#######################################
##              MAIN                 ##
#######################################

printf "`date +%Y-%m-%d_%H:%M:%S` - Started mod update.\n" | tee -a ${LogFile}
if [ -d ${TempDir}/dumps ]; then
  ## Remove temporary folder used by ArkModDownloader ##
  rm -rf ${TempDir}/dumps
fi
for ModID in ${GameModIds//,/ }
do
  if [ -d ${TemplateDir}/steamapps/workshop/content/${GameID}/${ModID} ]; then
    ## Delete prior download folder ##
    rm -rf ${TemplateDir}/steamapps/workshop/content/${GameID}/${ModID}
  fi
  if [ -f ${TemplateDir}/steamapps/workshop/content/${GameID}/${ModID}.mod ]; then
    ## Delete prior mod descriptor file ##
    rm ${TemplateDir}/steamapps/workshop/content/${GameID}/${ModID}.mod
  fi
  if [ "${USER}" == "root" ]; then
    ## Switch to the install user and update the instance ##
    f_verbose "su --command='${ArkModDLCMD} --modids '${ModID}' --workingdir '${TemplateDir}' --steamcmd '${SteamDir}' --namefile' ${GameUser}"
    su --command="${ArkModDLCMD} --modids '${ModID}' --workingdir '${TemplateDir}' --steamcmd '${SteamDir}' --namefile" ${GameUser}
    ReturnCode=$?
  elif [ "${USER}" == "${GameUser}" ]; then
    ## Already running as the install user, update the instance ##
    f_verbose "${ArkModDLCMD} --modids '${ModID}' --workingdir '${TemplateDir}' --steamcmd '${SteamDir}' --namefile ${GameUser}"
    ${ArkModDLCMD} --modids "${ModID}" --workingdir "${TemplateDir}" --steamcmd "${SteamDir}" --namefile ${GameUser}
    ReturnCode=$?
  fi
  if [ "${ReturnCode}" == "0" ]; then
    printf "[SUCCESS] Mod ${ModID} downloaded.\n" | tee -a ${LogFile}
    chown --recursive ${GameUser}:${GameGroup} ${TemplateDir}/ShooterGame/Content/Mods/${ModID}*
    find ${TemplateDir}/ShooterGame/Content/Mods/${ModID} -type d -exec chmod 0750 {} \;
    find ${TemplateDir}/ShooterGame/Content/Mods/${ModID} -type f -exec chmod 0640 {} \;
  else
    printf "[ERROR] Mod ${ModID} failed with ReturnCode=${ReturnCode}\n" | tee -a ${LogFile}
    ((ErrorCount++))
  fi
done
f_verbose "[INFO] ErrorCount=${ErrorCount}"
## Calculate total runtime ##
FinishTime="$(date +%s)"
ElapsedTime="$(expr ${FinishTime} - ${StartTime})"
Hours=$((${ElapsedTime} / 3600))
ElapsedTime=$((${ElapsedTime} - ${Hours} * 3600))
Minutes=$((${ElapsedTime} / 60))
Seconds=$((${ElapsedTime} - ${Minutes} * 60))
printf "[INFO] Total runtime: ${Hours} hour(s) ${Minutes} minute(s) ${Seconds} second(s)\n" | tee -a ${LogFile}
printf "`date +%Y-%m-%d_%H:%M:%S` - Completed mod update.\n" | tee -a ${LogFile}
exit ${ErrorCount}
End of Method #2

Synchronize One Instance with the Template

This script will copy the files in the template folder that have changed (e.g. updated) to a specified offline instance. It does not matter if the changes were from a server update or a mod update...it will synchronize the files to be the same. Because this only copies what has changed, it makes the process very quick. However, the game instance CANNOT be running and should be stopped prior to trying to synchronize the files.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-sync-1.sh  --output-document /opt/ark/scripts/game-sync-1.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-sync-1.sh
sudo chmod 0750 /opt/ark/scripts/game-sync-1.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-sync-1.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-sync-1.sh
sudo chmod 0750 /opt/ark/scripts/game-sync-1.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-sync-1.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-sync-1.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Synchronize template to a game instance.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified install user.
##               : Instance needs to be stopped.
## Run Frequency : As needed or before starting a server.
## Parameters    : Game Instance (or none for interactive selection)
## Exit Codes    :
##    0 = Success
##    1 = ERROR Invalid user
##    2 = ERROR Cannot sync online instance
##    3 = ERROR Invalid parameter
##    4 = ERROR No offline instances
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

ErrorCount=0
LogFile="${LogDir}/game-sync-server.log"

#######################################
##            FUNCTIONS              ##
#######################################

function f_sync()
{
  StartTime="$(date +%s)"
  printf "`date +%Y-%m-%d_%H:%M:%S` - Sync ${1} started.\n" | tee -a ${LogFile}
  if [ "${USER}" = "root" ]; then
    ## Switch to the install user and update the instance ##
    f_verbose "su --command='rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}' ${GameUser}"
    su --command="rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}" ${GameUser}
  else
    ## Already running as install user, update the instance ##
    f_verbose "rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}"
    rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}
  fi
  ReturnCode=$?
  if [ "${ReturnCode}" != "0" ]; then
    printf "[ERROR] rsync ReturnCode=${ReturnCode}\n" | tee -a ${LogFile}
  fi
  ## Calculate total runtime ##
  FinishTime="$(date +%s)"
  ElapsedTime="$(expr ${FinishTime} - ${StartTime})"
  Hours=$((${ElapsedTime} / 3600))
  ElapsedTime=$((${ElapsedTime} - ${Hours} * 3600))
  Minutes=$((${ElapsedTime} / 60))
  Seconds=$((${ElapsedTime} - ${Minutes} * 60))
  printf "[INFO] Total runtime: ${Hours} hour(s) ${Minutes} minute(s) ${Seconds} second(s)\n" | tee -a ${LogFile}
  printf "`date +%Y-%m-%d_%H:%M:%S` - Sync ${1} completed.\n" | tee -a ${LogFile}
}

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ]; then
  printf "[ERROR] This script must be run as root or ${GameUser}.\n"
  exit 1
fi

#######################################
##              MAIN                 ##
#######################################

GameInstance=""
## Check command-line parameter for specified instance ##
case "$1" in
  "") printf "[INFO] Game instance not specified as parameter. Going into pick list mode.\n";;
  *)  GameInstance=$1;;
esac

if [ "${GameInstance}" != "" ]; then
  ## Validate GameInstance ##
  if [ -f "${GameRootDir}/${GameInstance}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Verify instance is offline ##
    ${ScriptDir}/game-online.sh ${GameInstance} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "0" ]; then
      ## Instance is offline, use parameter ##
      f_sync ${GameInstance}
      exit 0
    else
      printf "[ERROR] Cannot sync ${GameInstance} while online.\n"
      exit 2
    fi
  else
    printf "[ERROR] Invalid instance.\n"
    exit 3
  fi
fi

## Loop through all defined game instances and build list of what is online ##
arrList=()

for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Verify instance is offline ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "0" ]; then
      ## Instance is inactive ##
      arrList+=(${arrInstanceName[${intIndex}]})
    fi
  fi
done

if [ ${#arrList[@]} -eq 0 ]; then
  ## If no instances are offline, the exit ##
  printf "[INFO] No instances are offline. Cannot continue sync.\n"
  exit 4
else
  ## Loop thru offline instances, present options for user to select ##
  printf "Select which offline instance you want synchronized from the template:\n"
  for intIndex in "${!arrList[@]}"
  do
    printf " ${intIndex}) ${arrList[intIndex]}\n"
  done
  printf " x) Exit\n"
  read -n 1 -p "Your choice: " char_answer;
  printf "\n"
  ## This will break if there are more than 10 instances ##
  case ${char_answer} in
    0)   f_sync ${arrList[0]};;
    1)   f_sync ${arrList[1]};;
    2)   f_sync ${arrList[2]};;
    3)   f_sync ${arrList[3]};;
    4)   f_sync ${arrList[4]};;
    5)   f_sync ${arrList[5]};;
    6)   f_sync ${arrList[6]};;
    7)   f_sync ${arrList[7]};;
    8)   f_sync ${arrList[8]};;
    9)   f_sync ${arrList[9]};;
    x|X) printf "Exit\n";;
    *)   printf "Exit\n";;
  esac
fi
exit 0
End of Method #2

Synchronize All Instances with the Template

This script will copy the files in the template folder that have changed (e.g. updated) to all offline instances. It does not matter if the changes were from a server update or a mod update...it will synchronize the files to be the same. Because this only copies what has changed, it makes the process very quick. However, the game instance CANNOT be running and should be stopped prior to trying to synchronize the files.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-sync-all.sh  --output-document /opt/ark/scripts/game-sync-all.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-sync-all.sh
sudo chmod 0750 /opt/ark/scripts/game-sync-all.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-sync-all.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-sync-all.sh
sudo chmod 0750 /opt/ark/scripts/game-sync-all.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-sync-all.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-sync-all.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Synchronize template to all game instances.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified install user.
##               : Instance needs to be stopped.
## Run Frequency : As needed or before starting a server.
## Parameters    : Game Instance
## Exit Codes    :
##    0 = Success
##    1 = ERROR Invalid user
##    2 = ERROR No offline instances
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

ErrorCount=0
LogFile="${LogDir}/game-sync-server.log"

#######################################
##            FUNCTIONS              ##
#######################################

function f_sync()
{
  StartTime="$(date +%s)"
  printf "`date +%Y-%m-%d_%H:%M:%S` - Sync ${1} started.\n" | tee -a ${LogFile}
  if [ "${USER}" = "root" ]; then
    ## Switch to the install user and update the instance ##
    f_verbose "su --command='rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}' ${GameUser}"
    su --command="rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}" ${GameUser}
  else
    ## Already running as install user, update the instance ##
    f_verbose "rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}"
    rsync -a --exclude 'ShooterGame/Saved' --exclude 'steamapps/workshop' ${TemplateDir}/ ${GameRootDir}/${1}
  fi
  ReturnCode=$?
  if [ "${ReturnCode}" != "0" ]; then
    printf "[ERROR] rsync ReturnCode=${ReturnCode}\n" | tee -a ${LogFile}
  fi
  ## Calculate total runtime ##
  FinishTime="$(date +%s)"
  ElapsedTime="$(expr ${FinishTime} - ${StartTime})"
  Hours=$((${ElapsedTime} / 3600))
  ElapsedTime=$((${ElapsedTime} - ${Hours} * 3600))
  Minutes=$((${ElapsedTime} / 60))
  Seconds=$((${ElapsedTime} - ${Minutes} * 60))
  printf "[INFO] Total runtime: ${Hours} hour(s) ${Minutes} minute(s) ${Seconds} second(s)\n" | tee -a ${LogFile}
  printf "`date +%Y-%m-%d_%H:%M:%S` - Sync ${1} completed.\n" | tee -a ${LogFile}
}

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ]; then
  printf "[ERROR] This script must be run as root or ${GameUser}.\n"
  exit 1
fi

#######################################
##              MAIN                 ##
#######################################

## Loop through all defined game instances and build list of what is online ##
arrList=()

for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Verify instance is offline ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "0" ]; then
      ## Instance is inactive ##
      arrList+=(${arrInstanceName[${intIndex}]})
    fi
  fi
done
if [ ${#arrList[@]} -eq 0 ]; then
  ## If no instances are offline, the exit ##
  printf "[INFO] No instances are offline. Cannot continue sync.\n"
  exit 2
else
  ## Loop thru offline instances, run sync ##
  for intIndex in "${!arrList[@]}"
  do
    ## Sync instance ##
    f_sync ${arrList[intIndex]}
  done
fi
exit 0
End of Method #2

Scripting - Compare

Posted: Fri May 14, 2021 8:31 am
by LHammonds
Comparing Server Versions

This script will compare the dedicated server version number in the template to the version number in the game instances. It will return zero if everything matches which can be helpful if you want to schedule automated downtime whenever an update needs to be applied. They will also display the results in human-readable format if you want to check manually.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-compare-server.sh  --output-document /opt/ark/scripts/game-compare-server.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-compare-server.sh
sudo chmod 0750 /opt/ark/scripts/game-compare-server.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-compare-server.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-compare-server.sh
sudo chmod 0750 /opt/ark/scripts/game-compare-server.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-compare-server.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-compare-server.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Compare game engine version between template and instances.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root, the specified low-rights or install user.
## Run Frequency : As needed.
## Parameters    : None
## Exit Codes    :
##    0 = Current: Template matches all instance versions
##    1 = OutDated: Template does not match one or more instance versions
##    2 = Other Error
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

LogFile="${LogDir}/game-compare-server.log"
MatchStatus=0

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access. ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ] && [ "${USER}" != "${GameService}" ]; then
  printf "[ERROR] This script must be run as root or ${GameUser}.\n"
  exit 2
fi

#######################################
##              MAIN                 ##
#######################################

printf "`date +%Y-%m-%d\ %H:%M:%S` - Started server comparison.\n" | tee -a ${LogFile}
## Get version number of template ##
if [ -f ${TemplateDir}/version.txt ]; then
  read -r TemplateVer < ${TemplateDir}/version.txt
  ## Remote newline, carriage return or spaces from variable ##
  TemplateVer=${TemplateVer//$'\n'/}
  TemplateVer=${TemplateVer//$'\r'/}
  TemplateVer=${TemplateVer//$' '/}
  printf "[CURRENT] ${TemplateVer} - template\n" | tee -a ${LogFile}
else
  printf "[ERROR] Missing version file: ${TemplateDir}/version.txt\n" | tee -a ${LogFile}
fi
## Loop through all valid instances and get their version ##
intVersion=0
for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    if [ -f ${GameRootDir}/${arrInstanceName[${intIndex}]}/version.txt ]; then
      read -r InstanceVer < ${GameRootDir}/${arrInstanceName[${intIndex}]}/version.txt
      ## Remote newline, carriage return or spaces from variable ##
      InstanceVer=${InstanceVer//$'\n'/}
      InstanceVer=${InstanceVer//$'\r'/}
      InstanceVer=${InstanceVer//$' '/}
      if [ "${TemplateVer}" == "${InstanceVer}" ]; then
        printf "[CURRENT] ${InstanceVer} - ${arrInstanceName[${intIndex}]}\n" | tee -a ${LogFile}
      else
        printf "[OLD-VER] ${InstanceVer} - ${arrInstanceName[${intIndex}]}\n" | tee -a ${LogFile}
        MatchStatus=1
      fi
    else
      ## Missing the expected version file ##
      printf "[ERROR] Missing version file: ${GameRootDir}/${arrInstanceName[${intIndex}]}/version.txt\n"
    fi
  fi
done
if [ "${MatchStatus}" == "0" ]; then
  printf "[INFO] All instances match the template version of ${TemplateVer}\n" | tee -a ${LogFile}
else
  printf "[WARNING] One or more instances do not match template version of ${TemplateVer}\n" | tee -a ${LogFile}
fi
printf "`date +%Y-%m-%d\ %H:%M:%S` - Completed server comparison.\n" | tee -a ${LogFile}
exit ${MatchStatus}
End of Method #2

Example output when there are updates available:

Code: Select all

2021-05-11 17:46:14 - Started server comparison.
[CURRENT] 327.20 - template
[OLD-VER] 327.19 - TheIsland
[OLD-VER] 327.19 - ScorchedEarth
[OLD-VER] 327.19 - Ragnarok
[WARNING] One or more instances do not match template version of 327.20
2021-05-11 17:46:14 - Completed server comparison.
Example output when there are no updates available:

Code: Select all

2021-06-01 17:07:56 - Started server comparison.
[CURRENT] 327.21 - template
[CURRENT] 327.21 - TheIsland
[CURRENT] 327.21 - ScorchedEarth
[CURRENT] 327.21 - Ragnarok
[INFO] All instances match the template version of 327.21
2021-06-01 17:07:56 - Completed server comparison.
Comparing Workshop Mod Versions

Since there is no "version.txt" file for mods, this script will compare the .mod files in the template to the .mod files in the game instances. It will return zero if everything matches which can be helpful if you want to schedule automated downtime whenever an update needs to be applied. They will also display the results in human-readable format if you want to check manually.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-compare-mods.sh  --output-document /opt/ark/scripts/game-compare-mods.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-compare-mods.sh
sudo chmod 0750 /opt/ark/scripts/game-compare-mods.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-compare-mods.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-compare-mods.sh
sudo chmod 0750 /opt/ark/scripts/game-compare-mods.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-compare-mods.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-compare-mods.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Compare mods in template to those in each instance.
## Compatibility : Verified on Ubuntu Server 20.04 LTS
## Requirements  : Run as root, the specified low-rights or install user.
## Run Frequency : As needed.
## Parameters    : None
## Exit Codes    :
##    0 = Current: Template matches all instance versions
##    1 = OutDated: Template does not match one or more instance versions
##    2 = Other Error
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

MatchStatus=0
TotalCompares=0
TotalMismatched=0
MismatchList=""
CurrentMismatch=""

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access. ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ] && [ "${USER}" != "${GameService}" ]; then
  printf "[ERROR] This script must be run as root, ${GameUser} or ${GameService}.\n"
  exit 2
fi

#######################################
##              MAIN                 ##
#######################################

printf "`date +%Y-%m-%d\ %H:%M:%S` - Started mod comparison.\n" | tee -a ${LogFile}
## Loop through all .mod files
cd ${TemplateDir}/ShooterGame/Content/Mods/
for TemplateFile in *.mod
do
  ## Loop through all instances
  for intIndex in "${!arrInstanceName[@]}"
  do
    ## Verify instance is installed ##
    if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Compare template .mod to instance .mod
      if cmp --silent "${TemplateDir}/ShooterGame/Content/Mods/${TemplateFile}" "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Content/Mods/${TemplateFile}"
      then
        ((TotalCompares++))
      else
        ((TotalCompares++))
        ((TotalMismatched++))
        MatchStatus=1
        if [ "${CurrentMismatch}" != "${arrInstanceName[${intIndex}]}" ]; then
          if [ "${MismatchList}" == "" ]; then
            MismatchList="${arrInstanceName[${intIndex}]}"
          else
            MismatchList="${MismatchList},${arrInstanceName[${intIndex}]}"
          fi
          CurrentMismatch=${arrInstanceName[${intIndex}]}
        fi
      fi
    fi
  done
done
if [ ${TotalMismatched} -eq 0 ]; then
  printf "[INFO] All mods match the template.\n" | tee -a ${LogFile}
else
  printf "[WARNING] There are ${TotalMismatched} instances with outdated mods.\n[OUTDATED] ${MismatchList}\n" | tee -a ${LogFile}
fi
printf "`date +%Y-%m-%d\ %H:%M:%S` - Completed mod comparison.\n" | tee -a ${LogFile}
exit ${MatchStatus}
End of Method #2

Example output when there are updates available:

Code: Select all

2021-05-13 13:48:39 - Started mod comparison.
[WARNING] There are 3 instances with outdated mods.
[OUTDATED] TheIsland,ScorchedEarth,Ragnarok
2021-05-13 13:48:39 - Completed mod comparison.
Example output when there are no updates available:

Code: Select all

2021-06-02 08:08:54 - Started mod comparison.
[INFO] All mods match the template.
2021-06-02 08:08:54 - Completed mod comparison.

Scripting - Backup

Posted: Tue May 25, 2021 3:23 am
by LHammonds
Scripting - Backup

This script will backup all valid instances, configurations, logs and scripts to date-stamped archives.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-backup.sh  --output-document /opt/ark/scripts/game-backup.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-backup.sh
sudo chmod 0750 /opt/ark/scripts/game-backup.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-backup.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-backup.sh
sudo chmod 0750 /opt/ark/scripts/game-backup.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-backup.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-backup.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Create archive of all game instances.
## Compatibility : Verified on Ubuntu Server 20.04
## Requirements  : Run as root or the specified install user.
## Run Frequency : Designed to run as needed.
## Exit Codes    :
##   0 = success
## 200 = ERROR Invalid user.
##   # = Number of times archive creation failed.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

LogFile="${LogDir}/game-backup.log"
TimeStamp="`date +%Y-%m-%d_%H-%M-%S`"
ArchivePattern="_ark.tar.gz"
RsyncFailure=0
ErrorFlag=0
StartTime="$(date +%s)"

#######################################
##            FUNCTIONS              ##
#######################################

function f_sendmsg()
{
  ## Parameter #1 = RCON Port ##
  ## Parameter #2 = RCON Command ##
  ${ScriptDir}/rcon -f /etc/rcon.ini -p ${1} "${2}"
}   ## f_sendmsg() ##

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access. ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ]; then
  printf "[ERROR] This script must be run as root or ${GameUser}.\n"
  exit 200
fi

#######################################
##           MAIN PROGRAM            ##
#######################################

printf "`date +%Y-%m-%d\ %H:%M:%S` - Backup started.\n" | tee -a ${LogFile}

## Create specific backup target folders if the do not exist ##
if [ ! -d ${BackupDir}/map ]; then
  mkdir ${BackupDir}/map
fi
if [ ! -d ${BackupDir}/log ]; then
  mkdir ${BackupDir}/log
fi
if [ ! -d ${BackupDir}/config ]; then
  mkdir ${BackupDir}/config
fi
if [ ! -d ${BackupDir}/cluster ]; then
  mkdir ${BackupDir}/cluster
fi
if [ ! -d ${BackupDir}/scripts ]; then
  mkdir ${BackupDir}/scripts
fi

## Cluster backup ##
cd ${GameRootDir}/cluster
tar -czpf ${BackupDir}/cluster/${TimeStamp}_cluster.tar.gz * 1>/dev/null 2>&1
if [ ! -f ${BackupDir}/cluster/${TimeStamp}_cluster.tar.gz ]; then
  ## Error detected ##
  printf "`date +%Y-%m-%d\ %H:%M:%S` [ERROR] Failed to create ${BackupDir}/cluster/${TimeStamp}_cluster.tar.gz.\n" | tee -a ${LogFile}
  ((ErrorFlag++))
else
  ## Record some stats to the log file ##
  ArchiveSize=`ls -lh "${BackupDir}/cluster/${TimeStamp}_cluster.tar.gz" | awk '{ print $5 }'`
  f_verbose "[FILE] ${ArchiveSize}, ${BackupDir}/cluster/${TimeStamp}_cluster.tar.gz"
fi

## Scripts backup ##
cd ${GameRootDir}/scripts
tar -czpf ${BackupDir}/scripts/${TimeStamp}_scripts.tar.gz * 1>/dev/null 2>&1
if [ ! -f ${BackupDir}/scripts/${TimeStamp}_scripts.tar.gz ]; then
  ## Error detected ##
  printf "`date +%Y-%m-%d\ %H:%M:%S` [ERROR] Failed to create ${BackupDir}/scripts/${TimeStamp}_scripts.tar.gz.\n" | tee -a ${LogFile}
  ((ErrorFlag++))
else
  ## Record some stats to the log file ##
  ArchiveSize=`ls -lh "${BackupDir}/scripts/${TimeStamp}_scripts.tar.gz" | awk '{ print $5 }'`
  f_verbose "[FILE] ${ArchiveSize}, ${BackupDir}/scripts/${TimeStamp}_scripts.tar.gz"
fi

## Loop through each game instance ##
for intIndex in "${!arrInstanceName[@]}"
do
  MapName=${arrMapName[${intIndex}]}
  RCONPort=${arrRCONPort[${intIndex}]}
  InstanceName=${arrInstanceName[${intIndex}]}
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Check if instance is running ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "1" ]; then
      ## Instance is active. Send in-game notice ##
      f_sendmsg ${RCONPort} "ServerChat `date +%H:%M` Backup started."
      f_sendmsg ${RCONPort} "SaveWorld"
    fi
    ## Create map archive ##
    cd ${GameRootDir}/${InstanceName}/ShooterGame/Saved
    tar -czpf ${BackupDir}/map/${TimeStamp}_${InstanceName}.tar.gz SavedArks/${MapName}.ark SavedArks/*.arktribe SavedArks/*.arkprofile SavedArks/*.arktributetribe > /dev/null 2>&1
    if [ ! -f ${BackupDir}/map/${TimeStamp}_${InstanceName}.tar.gz ]; then
      ## Error detected ##
      printf "`date +%Y-%m-%d\ %H:%M:%S` [ERROR] Failed to create ${BackupDir}/map/${TimeStamp}_${InstanceName}.tar.gz.\n" | tee -a ${LogFile}
      ((ErrorFlag++))
    else
      ## Record some stats to the log file ##
      ArchiveSize=`ls -lh "${BackupDir}/map/${TimeStamp}_${InstanceName}.tar.gz" | awk '{ print $5 }'`
      f_verbose "[FILE] ${ArchiveSize}, ${BackupDir}/map/${TimeStamp}_${InstanceName}.tar.gz"
      if [ "${ReturnCode}" == "1" ]; then
        ## Instance is active. Send in-game notice ##
      f_sendmsg ${RCONPort} "ServerChat `date +%H:%M` Backup completed. Archive size = ${ArchiveSize}"
      fi
    fi
    ## Create log archive ##
    cd ${GameRootDir}/${InstanceName}/ShooterGame/Saved/Logs
    tar -czpf ${BackupDir}/log/${TimeStamp}_${InstanceName}-log.tar.gz *.log 1>/dev/null 2>&1
    if [ ! -f ${BackupDir}/log/${TimeStamp}_${InstanceName}-log.tar.gz ]; then
      ## Error detected ##
      printf "`date +%Y-%m-%d\ %H:%M:%S` [ERROR] Failed to create ${BackupDir}/log/${TimeStamp}_${InstanceName}-log.tar.gz.\n" | tee -a ${LogFile}
      ((ErrorFlag++))
    else
      ## Record some stats to the log file ##
      ArchiveSize=`ls -lh "${BackupDir}/log/${TimeStamp}_${InstanceName}-log.tar.gz" | awk '{ print $5 }'`
      f_verbose "[FILE] ${ArchiveSize}, ${BackupDir}/log/${TimeStamp}_${InstanceName}-log.tar.gz"
    fi
    ## Create config archive ##
    cd ${GameRootDir}/${InstanceName}/ShooterGame/Saved/Config/LinuxServer
    tar -czpf ${BackupDir}/config/${TimeStamp}_${InstanceName}-config.tar.gz *.ini 1>/dev/null 2>&1
    if [ ! -f ${BackupDir}/config/${TimeStamp}_${InstanceName}-config.tar.gz ]; then
      ## Error detected ##
      printf "`date +%Y-%m-%d\ %H:%M:%S` [ERROR] Failed to create ${BackupDir}/config/${TimeStamp}_${InstanceName}-config.tar.gz.\n" | tee -a ${LogFile}
      ((ErrorFlag++))
    else
      ## Record some stats to the log file ##
      ArchiveSize=`ls -lh "${BackupDir}/config/${TimeStamp}_${InstanceName}-config.tar.gz" | awk '{ print $5 }'`
      f_verbose "[FILE] ${ArchiveSize}, ${BackupDir}/config/${TimeStamp}_${InstanceName}-config.tar.gz"
    fi
  fi
done
## Calculate total runtime ##
FinishTime="$(date +%s)"
ElapsedTime="$(expr ${FinishTime} - ${StartTime})"
Hours=$((${ElapsedTime} / 3600))
ElapsedTime=$((${ElapsedTime} - ${Hours} * 3600))
Minutes=$((${ElapsedTime} / 60))
Seconds=$((${ElapsedTime} - ${Minutes} * 60))
printf "[INFO] Total runtime: ${Hours} hour(s) ${Minutes} minute(s) ${Seconds} second(s)\n" | tee -a ${LogFile}
printf "[INFO] Exit code = ${ErrorFlag}\n" | tee -a ${LogFile}
printf "`date +%Y-%m-%d\ %H:%M:%S` - Backup completed.\n" | tee -a ${LogFile}
exit ${ErrorFlag}
End of Method #2


Scripting - Purge Files

As storage needs increase with multiple backups, it is necessary to curb the growth by purging old backup files that are no longer needed.

This script will purge files of a particular type in specific locations that are older than a specific amount of days.

You can set the amount of days to retain files using the variable called "Default" which is set to 15 (days). You can also manually set the number of days to something different when calling the function which just specifies the location, file type and amount of days.

Examples:

Code: Select all

f_purge ${BackupDir}/map *.gz ${Default}
f_purge ${BackupDir}/log *.gz 30
You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-purgefiles.sh  --output-document /opt/ark/scripts/game-purgefiles.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-purgefiles.sh
sudo chmod 0750 /opt/ark/scripts/game-purgefiles.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-purgefiles.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-purgefiles.sh
sudo chmod 0750 /opt/ark/scripts/game-purgefiles.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-purgefiles.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-purgefiles.sh
## Version       : 1.0
## Date          : 2020-10-08
## Author        : LHammonds
## Purpose       : Purge files older than x days
## Compatibility : Verified on to work on Ubuntu Server 20.04 LTS
## Requirements  : Run as root or the specified install user.
## Run Frequency : As needed (such as daily)
## Exit Codes    :
##     0 = Normal exit.
##   200 = ERROR Invalid user.
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2020-10-08 1.0 LTH Created script.
#############################################################

## Import common variables and functions. ##
source /etc/gamectl.conf

LogFile="${LogDir}/game-purgefiles.log"
Default=15

#######################################
##            FUNCTIONS              ##
#######################################

function f_purge()
{
  Folder=$1
  FilePattern=$2
  Days=$3
  ## Document files to be deleted in the log ##
  f_verbose "[INFO] ${Folder}/${FilePattern} +${Days}"
  if [ "${VerboseMode}" == "1" ]; then
    /usr/bin/find ${Folder} -maxdepth 1 -name "${FilePattern}" -mtime +${Days} -type f -exec /usr/bin/ls -l {} \; >> ${LogFile}
  fi
  /usr/bin/find ${Folder} -maxdepth 1 -name "${FilePattern}" -mtime +${Days} -type f -delete 1>/dev/null 2>&1
}  ## f_purge() ##

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access ##
if [ "${USER}" != "root" ] && [ "${USER}" != "${GameUser}" ]; then
  printf "[ERROR] This script must be run as root or ${GameUser}.\n"
  exit 200
fi

#######################################
##           MAIN PROGRAM            ##
#######################################

printf "`date +%Y-%m-%d_%H:%M:%S` - Purge started.\n" | tee -a ${LogFile}
f_purge ${BackupDir}/cluster *.gz ${Default}
f_purge ${BackupDir}/config *.gz ${Default}
f_purge ${BackupDir}/map *.gz ${Default}
f_purge ${BackupDir}/log *.gz ${Default}
f_purge ${BackupDir}/scripts *.gz ${Default}
printf "`date +%Y-%m-%d_%H:%M:%S` - Purge completed.\n" | tee -a ${LogFile}
exit 0
End of Method #2

Example output:

Code: Select all

2021-05-31_12:24:12 - Purge started.
[INFO] /opt/ark/bak/cluster/*.gz +15
[INFO] /opt/ark/bak/config/*.gz +15
[INFO] /opt/ark/bak/map/*.gz +15
[INFO] /opt/ark/bak/log/*.gz +15
[INFO] /opt/ark/bak/scripts/*.gz +15
2021-05-31_12:24:12 - Purge completed.

Scripting - Restore

Posted: Tue May 25, 2021 4:01 am
by LHammonds
Scripting - Restore

This script will walk you through restoring an instance from a prior backup archive.

You can download the script straight from my github using the below commands or manually create the file yourself by copying the code from this forum (which may get outdated compared to github)

Method #1 - Obtain from GitHub:

Code: Select all

wget https://raw.githubusercontent.com/LHammonds/ark-bash/main/game-restore-map.sh  --output-document /opt/ark/scripts/game-restore-map.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-restore-map.sh
sudo chmod 0750 /opt/ark/scripts/game-restore-map.sh
End of Method #1

Method #2 - Copy/Paste from this forum (but check GitHub for newer version):

Code: Select all

sudo touch /opt/ark/scripts/game-restore-map.sh
sudo chown arkserver:arkserver /opt/ark/scripts/game-restore-map.sh
sudo chmod 0750 /opt/ark/scripts/game-restore-map.sh
Now edit the file:

Code: Select all

sudo vi /opt/ark/scripts/game-restore-map.sh
Copy/paste the following into the file.

Code: Select all

#!/bin/bash
#############################################################
## Name          : game-restore-map.sh
## Version       : 1.0
## Date          : 2021-04-20
## Author        : LHammonds
## Purpose       : Restore from prior backup.
## Compatibility : Ubuntu Server 20.04
## Parameters    : None
## Requirements  : Run as root
## Run Frequency : Designed to run on demand.
## Exit Codes    :
##    0 = Success or user abort
##    1 = ERROR Invalid user
##    2 = Archive folder does not exist
##    3 = Archive extraction failure
##    4 = Missing map file after extraction
######################## CHANGE LOG #########################
## DATE       VER WHO WHAT WAS CHANGED
## ---------- --- --- ---------------------------------------
## 2021-04-20 1.0 LTH Created script.
#############################################################

## Import standard variables and functions. ##
source /etc/gamectl.conf

LogFile="${LogDir}/game-restore-map.log"
InstanceName=""

#######################################
##            FUNCTIONS              ##
#######################################

function f_abort()
{
  printf "`date +%Y-%m-%d\ %H:%M:%S` [ABORT] ErrorCode=${1}\n" >> ${LogFile}
  exit ${1}
} ## f_abort()

function f_checkspace()
{
  printf "Checkspace function called but nothing to do.\n"
} ## f_checkspace()

#######################################
##          PREREQUISITES            ##
#######################################

## Verify required user access ##
if [ "${USER}" != "root" ]; then
  printf "[ERROR] This script must be run as root, EC=1\n"
  exit 1
fi

## Check to make sure the archive folder exists ##
if [ ! -d "${BackupDir}" ]; then
  ## Archive folder does not exist ##
  printf "[ERROR] The archive folder does not exist! ${BackupDir}, EC=2\n"
  exit 2
fi

#######################################
##           MAIN PROGRAM            ##
#######################################

clear
printf "           R E S T O R E\n"
printf "           -------------\n"
printf "            Step 1 of 4\n\n"
## Build a list of installed and offline instances ##
for intIndex in "${!arrInstanceName[@]}"
do
  ## Verify instance is installed ##
  if [ -f "${GameRootDir}/${arrInstanceName[${intIndex}]}/ShooterGame/Binaries/Linux/ShooterGameServer" ]; then
    ## Check if instance is running ##
    ${ScriptDir}/game-online.sh ${arrInstanceName[${intIndex}]} > /dev/null 2>&1
    ReturnCode=$?
    if [ "${ReturnCode}" == "0" ]; then
      ## Instance is offline ##
      arrList+=(${arrInstanceName[${intIndex}]})
    fi
  fi
done

if [ ${#arrList[@]} -eq 0 ]; then
  ## If no instances are offline, the exit ##
  printf "[INFO] No instances are offline.\n"
else
  ## Loop thru offline instances, present options for user to select ##
  printf "Select which offline instance you want restored:\n"
  for intIndex in "${!arrList[@]}"
  do
    printf " ${intIndex}) ${arrList[intIndex]}\n"
  done
  printf " x) Exit\n"
  read -n 1 -p "Your choice: " char_answer;
  ## This will break if there are more than 10 instances ##
  case ${char_answer} in
    0)   InstanceName=${arrList[0]};;
    1)   InstanceName=${arrList[1]};;
    2)   InstanceName=${arrList[2]};;
    3)   InstanceName=${arrList[3]};;
    4)   InstanceName=${arrList[4]};;
    5)   InstanceName=${arrList[5]};;
    6)   InstanceName=${arrList[6]};;
    7)   InstanceName=${arrList[7]};;
    8)   InstanceName=${arrList[8]};;
    9)   InstanceName=${arrList[9]};;
    *)   printf "\n"
         f_abort 0;;
  esac
fi
if [ "${InstanceName}" = "" ]; then
  printf "\n"
  f_abort 0
fi

clear
printf "           R E S T O R E\n"
printf "           -------------\n"
printf "            Step 2 of 4\n\n"
printf "You have chosen to restore ${InstanceName}\n"

## Define user prompt using the special PS3 variable ##
PS3="Type number for the desired archive or 'q' to quit: "

## Get sorted list of all archives (newest at the bottom) ##
FileList=$(find ${BackupDir}/map/*${InstanceName}* -maxdepth 1 -type f | sort -f)

## Prompt user to select a file to use. ##
## NOTE: If it is a long list, user can scroll ##
##       up if using PuTTY to see older files. ##
select GetFile in ${FileList}; do
  if [ "${GetFile}" != "" ]; then
    Filename=${GetFile}
  fi
  break
done
if [ "${Filename}" = "" ]; then
  ## User opted to quit ##
  f_abort 0
fi
clear
printf "           R E S T O R E\n"
printf "           -------------\n"
printf "            Step 3 of 4\n\n"
printf "Selected file:\n${Filename}\n"
read -p "Purge current files before restore (y/n)? "
if [ "${REPLY}" = "y" ]; then
  DeleteBeforeRestore="y"
else
  DeleteBeforeRestore="n"
fi
clear
printf "    R E S T O R E   S U M M A R Y\n"
printf "    -----------------------------\n"
printf "            Step 4 of 4\n\n"
printf "Instance to restore: ${InstanceName}\n"
printf "Archive to restore: ${Filename}\n"
printf "Delete before restore? ${DeleteBeforeRestore}\n\n"
read -p "Are you absolutely sure you wish to restore (y/n)? "
if [ "${REPLY}" != "y" ]; then
  printf "Restore aborted.\n"
  f_abort 0
fi
printf "`date +%Y-%m-%d\ %H:%M:%S` - Restore started.\n" | tee -a ${LogFile}
printf "Instance: ${InstanceName}\n" >> ${LogFile}
printf "Archive: ${Filename}\n" >> ${LogFile}
printf "Delete before restore: ${DeleteBeforeRestore}\n" >> ${LogFile}
if [ "${DeleteBeforeRestore}" = "y" ]; then
  rm ${GameRootDir}/${InstanceName}/ShooterGame/Saved/SavedArks/*.ark
  rm ${GameRootDir}/${InstanceName}/ShooterGame/Saved/SavedArks/*.arktribe
  rm ${GameRootDir}/${InstanceName}/ShooterGame/Saved/SavedArks/*.arkprofile
fi
cd ${GameRootDir}/${InstanceName}/ShooterGame/Saved
tar -xf ${Filename} > /dev/null 2>&1
ReturnValue=$?
if [ ${ReturnValue} -ne 0 ]; then
  ## Extract command failed.  Display warning message ##
  printf "[ERROR] Extract return value = {$ReturnValue}\n" | tee -a ${LogFile}
  f_abort 3
fi
## Sanity check...make sure at least the map is there ##
if [ ! -f ${GameRootDir}/${InstanceName}/ShooterGame/Saved/SavedArks/*.ark ]; then
  printf "[ERROR] A map file was not found in ${GameRootPath}/${InstanceName}/ShooterGame/Saved/SavedArks\n" | tee -a ${LogFile}
  f_abort 4
fi
printf "`date +%Y-%m-%d\ %H:%M:%S` - Restore completed.\n" | tee -a ${LogFile}
printf "FYI - Do not forget to start the instance.\n"
exit 0
End of Method #2

Example of page 1:

Code: Select all

           R E S T O R E
           -------------
            Step 1 of 4

Select which offline instance you want restored:
 0) TheIsland
 1) ScorchedEarth
 x) Exit
Your choice:
Example of page 2:

Code: Select all

           R E S T O R E
           -------------
            Step 2 of 4

You have chosen to restore TheIsland
 1) /opt/ark/bak/map/2021-05-23-05-00-01_TheIsland.tar.gz
 2) /opt/ark/bak/map/2021-05-24-05-00-01_TheIsland.tar.gz
 3) /opt/ark/bak/map/2021-05-25-05-00-01_TheIsland.tar.gz
 4) /opt/ark/bak/map/2021-05-26-05-00-01_TheIsland.tar.gz
Type number for the desired archive or 'q' to quit:
Example of page 3:

Code: Select all

           R E S T O R E
           -------------
            Step 3 of 4

Selected file:
/opt/ark/bak/map/2021-05-26-05-00-01_TheIsland.tar.gz
Purge current files before restore (y/n)?
Example of page 4:

Code: Select all

    R E S T O R E   S U M M A R Y
    -----------------------------
            Step 4 of 4

Instance to restore: TheIsland
Archive to restore: /opt/ark/bak/map/2021-05-26-05-00-01_TheIsland.tar.gz
Delete before restore? y

Are you absolutely sure you wish to restore (y/n)? y
2021-05-28 15:41:23 - Restore started.
2021-05-28 15:41:23 - Restore completed.
FYI - Do not forget to start the instance.