chore: align ark-suac app and refresh appstore zip
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/ruby.ruby3.4
|
||||
require 'json'
|
||||
|
||||
db_path = '/home/gameserver/server-files/mods.json'
|
||||
|
||||
unless File.exist?(db_path)
|
||||
print ""
|
||||
exit! 0
|
||||
end
|
||||
|
||||
begin
|
||||
mods = JSON.parse(File.read(db_path))
|
||||
args = "-mods="
|
||||
counter = 0
|
||||
|
||||
mods.each do |mod|
|
||||
if mod['enabled']
|
||||
args += ',' if counter > 0
|
||||
args += mod['mod_id'].to_s
|
||||
|
||||
counter += 1
|
||||
end
|
||||
end
|
||||
|
||||
if counter > 0
|
||||
print args
|
||||
end
|
||||
rescue JSON::ParserError
|
||||
File.write('/tmp/mod-read-error', 'mods.json is corrupted')
|
||||
print ""
|
||||
rescue => err
|
||||
File.write('/tmp/mod-read-error', err.to_s)
|
||||
print ""
|
||||
end
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
if [ "$ENABLE_DEBUG" = "1" ]; then
|
||||
echo "Entering debug mode..."
|
||||
sleep 999999999999
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# download steamcmd if necessary
|
||||
if [ ! -d "/home/gameserver/steamcmd/linux32" ]; then
|
||||
cd /home/gameserver/steamcmd
|
||||
wget https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz
|
||||
tar xfvz steamcmd_linux.tar.gz
|
||||
fi
|
||||
|
||||
# download/update server files
|
||||
cd /home/gameserver/steamcmd
|
||||
./steamcmd.sh +force_install_dir /home/gameserver/server-files +login anonymous +app_update 2430930 validate +quit
|
||||
|
||||
PROTON_VERSION="10-17"
|
||||
PROTON_DIR_NAME="GE-Proton$PROTON_VERSION"
|
||||
PROTON_ARCHIVE_NAME="$PROTON_DIR_NAME.tar.gz"
|
||||
STEAM_COMPAT_DATA=/home/gameserver/server-files/steamapps/compatdata
|
||||
STEAM_COMPAT_DIR=/home/gameserver/Steam/compatibilitytools.d
|
||||
ASA_COMPAT_DATA=$STEAM_COMPAT_DATA/2430930
|
||||
ASA_BINARY_DIR="/home/gameserver/server-files/ShooterGame/Binaries/Win64"
|
||||
START_PARAMS_FILE="/home/gameserver/server-files/start-parameters"
|
||||
MODS="$(/usr/bin/cli-asa-mods)"
|
||||
ASA_START_PARAMS="$ASA_START_PARAMS $MODS"
|
||||
ASA_BINARY_NAME="ArkAscendedServer.exe"
|
||||
ASA_PLUGIN_BINARY_NAME="AsaApiLoader.exe"
|
||||
ASA_PLUGIN_LOADER_ARCHIVE_NAME=$(basename $ASA_BINARY_DIR/AsaApi_*.zip)
|
||||
ASA_PLUGIN_LOADER_ARCHIVE_PATH="$ASA_BINARY_DIR/$ASA_PLUGIN_LOADER_ARCHIVE_NAME"
|
||||
ASA_PLUGIN_BINARY_PATH="$ASA_BINARY_DIR/$ASA_PLUGIN_BINARY_NAME"
|
||||
LAUNCH_BINARY_NAME="$ASA_BINARY_NAME"
|
||||
|
||||
# install proton if necessary
|
||||
if [ ! -d "$STEAM_COMPAT_DIR/$PROTON_DIR_NAME" ]; then
|
||||
mkdir -p $STEAM_COMPAT_DIR
|
||||
echo "Downloading Proton version $PROTON_VERSION... This might take a while"
|
||||
wget -P /tmp https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton$PROTON_VERSION/GE-Proton$PROTON_VERSION.tar.gz
|
||||
EXIT_CODE=$?
|
||||
|
||||
if [ $EXIT_CODE -ne 0 ]; then
|
||||
echo "Error: Error while downloading Proton ($EXIT_CODE)"
|
||||
exit 200
|
||||
fi
|
||||
|
||||
echo "Download finished, comparing checksums..."
|
||||
sha512sum -c /usr/share/proton/GE-Proton$PROTON_VERSION.sha512sum
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Proton checksum mismatch!"
|
||||
exit 201
|
||||
fi
|
||||
|
||||
tar -xf /tmp/$PROTON_ARCHIVE_NAME -C $STEAM_COMPAT_DIR
|
||||
rm /tmp/$PROTON_ARCHIVE_NAME
|
||||
fi
|
||||
|
||||
# install proton compat game data
|
||||
if [ ! -d "$ASA_COMPAT_DATA" ]; then
|
||||
mkdir -p $STEAM_COMPAT_DATA
|
||||
cp -r $STEAM_COMPAT_DIR/$PROTON_DIR_NAME/files/share/default_pfx $ASA_COMPAT_DATA
|
||||
fi
|
||||
|
||||
echo "Starting the ARK: Survival Ascended dedicated server..."
|
||||
echo "Start parameters: $ASA_START_PARAMS"
|
||||
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u)
|
||||
export STEAM_COMPAT_CLIENT_INSTALL_PATH=/home/gameserver/Steam
|
||||
export STEAM_COMPAT_DATA_PATH=$ASA_COMPAT_DATA
|
||||
|
||||
cd "$ASA_BINARY_DIR"
|
||||
|
||||
# unzip the asa plugin api archive if it exists. delete it afterwards
|
||||
if [ -f "$ASA_PLUGIN_LOADER_ARCHIVE_PATH" ]; then
|
||||
unzip -o $ASA_PLUGIN_LOADER_ARCHIVE_NAME
|
||||
rm $ASA_PLUGIN_LOADER_ARCHIVE_NAME
|
||||
fi
|
||||
|
||||
if [ -f "$ASA_PLUGIN_BINARY_PATH" ]; then
|
||||
echo "Detected ASA Server API loader. Launching server through $ASA_PLUGIN_BINARY_NAME"
|
||||
LAUNCH_BINARY_NAME="$ASA_PLUGIN_BINARY_NAME"
|
||||
fi
|
||||
|
||||
# Remove steamclient64.dll to prevent server from crashing.
|
||||
# File is not needed and was probably accidentally committed to Steam.
|
||||
# See: https://github.com/mschnitzer/ark-survival-ascended-linux-container-image/issues/123
|
||||
rm -f /home/gameserver/server-files/ShooterGame/Binaries/Win64/steamclient64.dll
|
||||
|
||||
$STEAM_COMPAT_DIR/$PROTON_DIR_NAME/proton run $LAUNCH_BINARY_NAME $ASA_START_PARAMS
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'slop', '= 4.10.1'
|
||||
gem 'iniparse', '= 1.5.0'
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
iniparse (1.5.0)
|
||||
slop (4.10.1)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux-gnu
|
||||
|
||||
DEPENDENCIES
|
||||
iniparse (= 1.5.0)
|
||||
slop (= 4.10.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.0.dev
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
class CliInterface
|
||||
def initialize(opts)
|
||||
@opts = opts
|
||||
|
||||
print_help! if opts[:help]
|
||||
end
|
||||
|
||||
def print_help!
|
||||
raise "Help not implemented!"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
class ModsInterface < CliInterface
|
||||
def initialize(opts)
|
||||
super(opts)
|
||||
|
||||
execute!
|
||||
end
|
||||
|
||||
def execute!
|
||||
if @opts[:enable]
|
||||
enable_mod!
|
||||
end
|
||||
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
|
||||
def enable_mod!
|
||||
mod_id = @opts[:enable]
|
||||
AsaCtrl::Mods::Database.get_instance.enable_mod!(mod_id)
|
||||
|
||||
puts "Enabled mod id '#{mod_id}' successfully. The server will download the mod upon startup."
|
||||
rescue AsaCtrl::Errors::ModAlreadyEnabledError
|
||||
AsaCtrl::Cli.exit_with_error!("This mod is already enabled! Use 'asa-ctrl mods --list' to see what mods are currently enabled.",
|
||||
AsaCtrl::ExitCodes::MOD_ALREADY_ENABLED)
|
||||
end
|
||||
|
||||
def print_help!
|
||||
puts "Usage: asa-ctrl mods [--install] (--dry-run)"
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
class RconInterface < CliInterface
|
||||
def initialize(opts)
|
||||
super(opts)
|
||||
|
||||
execute!
|
||||
end
|
||||
|
||||
def execute!
|
||||
if @opts[:exec]
|
||||
run_command!
|
||||
end
|
||||
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
|
||||
def run_command!
|
||||
rcon_command = @opts[:exec]
|
||||
response = AsaCtrl::Rcon.exec_command!('127.0.0.1', AsaCtrl::Rcon.identify_port, rcon_command, AsaCtrl::Rcon.identify_password)
|
||||
|
||||
if response[:id] == AsaCtrl::Rcon::PacketTypes::RESPONSE_VALUE
|
||||
puts response[:body]
|
||||
else
|
||||
AsaCtrl::Cli.exit_with_error!("Rcon command execution failed: #{response}",
|
||||
AsaCtrl::ExitCodes::RCON_COMMAND_EXECUTION_FAILED)
|
||||
end
|
||||
rescue AsaCtrl::Errors::RconPasswordNotFoundError
|
||||
AsaCtrl::Cli.exit_with_error!("Could not read RCON password. Make sure it is properly configured, either as start parameter ?ServerAdminPassword=mypass or " \
|
||||
"in GameUserSettings.ini in the [ServerSettings] section as ServerAdminPassword=mypass", AsaCtrl::ExitCodes::RCON_PASSWORD_NOT_FOUND)
|
||||
rescue AsaCtrl::Errors::RconAuthenticationError
|
||||
AsaCtrl::Cli.exit_with_error!("Could not execute this RCON command. Authentication failed (wrong server password).",
|
||||
AsaCtrl::ExitCodes::RCON_PASSWORD_WRONG)
|
||||
end
|
||||
|
||||
def print_help!
|
||||
puts "Usage: asa-ctrl rcon [--exec]"
|
||||
exit! AsaCtrl::ExitCodes::OK
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
module AsaCtrl
|
||||
module Cli
|
||||
HELP_ARGUMENT = '--help'
|
||||
HELP_DESCRIPTION = 'Prints a help message'
|
||||
|
||||
def self.passed_command(args)
|
||||
if ARGV.size == 0
|
||||
[]
|
||||
else
|
||||
[ARGV[0]]
|
||||
end
|
||||
end
|
||||
|
||||
def self.print_usage
|
||||
puts "Usage: asa-ctrl [rcon] (--help)"
|
||||
end
|
||||
|
||||
def self.exit_with_error!(message, code)
|
||||
$stderr.puts "Error: #{message}"
|
||||
exit! code
|
||||
end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class BaseError < StandardError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
require_relative './base_error.rb'
|
||||
require_relative './mod_already_enabled_error.rb'
|
||||
require_relative './rcon_authentication_error.rb'
|
||||
require_relative './rcon_password_not_found_error.rb'
|
||||
require_relative './rcon_port_not_found_error.rb'
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class ModAlreadyEnabledError < BaseError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class RconAuthenticationError < BaseError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class RconPasswordNotFoundError < BaseError; end
|
||||
end
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module AsaCtrl
|
||||
module Errors
|
||||
class RconPortNotFoundError < BaseError; end
|
||||
end
|
||||
end
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
module AsaCtrl
|
||||
module ExitCodes
|
||||
OK = 0
|
||||
CORRUPTED_MODS_DATABASE = 1
|
||||
MOD_ALREADY_ENABLED = 2
|
||||
RCON_PASSWORD_NOT_FOUND = 3
|
||||
RCON_PASSWORD_WRONG = 4
|
||||
RCON_COMMAND_EXECUTION_FAILED = 5
|
||||
end
|
||||
end
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
require_relative './start_params_helper.rb'
|
||||
require_relative './ini_config_helper.rb'
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
module AsaCtrl
|
||||
module IniConfigHelper
|
||||
def self.game_user_settings_ini
|
||||
self.parse('/home/gameserver/server-files/ShooterGame/Saved/Config/WindowsServer/GameUserSettings.ini')
|
||||
end
|
||||
|
||||
def self.game_ini
|
||||
self.parse('/home/gameserver/server-files/ShooterGame/Saved/Config/WindowsServer/Game.ini')
|
||||
end
|
||||
|
||||
def self.parse(path)
|
||||
return unless File.exist?(path)
|
||||
|
||||
IniParse.parse(File.read(path))
|
||||
end
|
||||
end
|
||||
end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
module AsaCtrl
|
||||
module StartParamsHelper
|
||||
def self.get_value(start_params, key)
|
||||
return unless start_params
|
||||
|
||||
value = ''
|
||||
offset = start_params.index("#{key}=")
|
||||
|
||||
return unless offset
|
||||
|
||||
offset += "#{key}=".length
|
||||
|
||||
start_params[offset..-1].each_char do |char|
|
||||
break if char == ' ' || char == '?'
|
||||
|
||||
value += char
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/ruby.ruby3.4
|
||||
require 'json'
|
||||
require 'slop'
|
||||
require 'iniparse'
|
||||
require 'socket'
|
||||
|
||||
if ENV['DEV'] == '1'
|
||||
require 'byebug'
|
||||
end
|
||||
|
||||
require_relative './exit_codes.rb'
|
||||
require_relative './errors/errors.rb'
|
||||
require_relative './helpers/helpers.rb'
|
||||
require_relative './mods/database.rb'
|
||||
require_relative './rcon/rcon.rb'
|
||||
require_relative './cli/utils.rb'
|
||||
require_relative './cli/interfaces/cli_interface.rb'
|
||||
require_relative './cli/interfaces/mods_interface.rb'
|
||||
require_relative './cli/interfaces/rcon_interface.rb'
|
||||
|
||||
main_args = Slop.parse(AsaCtrl::Cli.passed_command(ARGV)) do |args|
|
||||
args.on 'rcon', 'Interface for RCON command execution' do
|
||||
opts = Slop.parse(ARGV[1..-1]) do |opt|
|
||||
opt.string '--exec', 'An RCON command to execute'
|
||||
opt.bool AsaCtrl::Cli::HELP_ARGUMENT, AsaCtrl::Cli::HELP_DESCRIPTION
|
||||
end
|
||||
|
||||
AsaCtrl::Cli::RconInterface.new(opts)
|
||||
end
|
||||
|
||||
args.on AsaCtrl::Cli::HELP_ARGUMENT, AsaCtrl::Cli::HELP_DESCRIPTION do
|
||||
# handled once slop exits
|
||||
end
|
||||
end
|
||||
|
||||
AsaCtrl::Cli.print_usage
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
module AsaCtrl
|
||||
module Mods
|
||||
MOD_DATABASE_PATH = '/home/gameserver/server-files/mods.json'
|
||||
|
||||
class Database
|
||||
@@singleton_reference = nil
|
||||
|
||||
def initialize(database_path)
|
||||
@database_path = database_path
|
||||
|
||||
ensure_database_presence!
|
||||
load_database
|
||||
end
|
||||
|
||||
def self.get_instance
|
||||
return @@singleton_reference if @@singleton_reference
|
||||
@@singleton_reference = Database.new(MOD_DATABASE_PATH)
|
||||
end
|
||||
|
||||
def enable_mod!(mod_id)
|
||||
@database.each do |record|
|
||||
if record['mod_id'].to_i == mod_id.to_i
|
||||
raise AsaCtrl::Errors::ModAlreadyEnabledError if record['enabled']
|
||||
|
||||
record['enabled'] = true
|
||||
write_database!
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
add_new_record!(mod_id, 'unknown', true, false)
|
||||
end
|
||||
|
||||
def add_new_record!(mod_id, name, enabled, scanned)
|
||||
@database << {
|
||||
mod_id: mod_id.to_i,
|
||||
name: name,
|
||||
enabled: enabled,
|
||||
scanned: scanned
|
||||
}
|
||||
|
||||
write_database!
|
||||
end
|
||||
|
||||
def write_database!
|
||||
File.write(@database_path, JSON.pretty_generate(@database))
|
||||
end
|
||||
|
||||
def ensure_database_presence!
|
||||
return if File.exist?(@database_path)
|
||||
|
||||
@database = []
|
||||
write_database!
|
||||
end
|
||||
|
||||
def load_database
|
||||
@database = JSON.parse(File.read(@database_path))
|
||||
rescue JSON::ParserError
|
||||
# we do not want to delete the file for the user, as they might want to save its content first
|
||||
AsaCtrl::Cli.exit_with_error!("mods.json file is corrupted and cannot be parsed, please delete this file " \
|
||||
"manually. It can be found in the server files root directory.", AsaCtrl::ExitCodes::CORRUPTED_MODS_DATABASE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
module AsaCtrl
|
||||
module Rcon
|
||||
module PacketTypes
|
||||
RESPONSE_VALUE = 0
|
||||
EXEC_COMMAND = 2
|
||||
AUTH_RESPONSE = 2
|
||||
AUTH = 3
|
||||
end
|
||||
|
||||
Packet = Struct.new(:size, :id, :type, :body)
|
||||
|
||||
def self.exec_command!(server_ip, rcon_port, rcon_command, password)
|
||||
socket = TCPSocket.new(server_ip, rcon_port)
|
||||
raise AsaCtrl::Errors::RconAuthenticationError unless self.authenticate!(socket, password)
|
||||
|
||||
self.send_packet!(socket, rcon_command, PacketTypes::EXEC_COMMAND)
|
||||
end
|
||||
|
||||
def self.authenticate!(socket, password)
|
||||
response = self.send_packet!(socket, password, PacketTypes::AUTH)
|
||||
response[:id] != -1
|
||||
end
|
||||
|
||||
def self.send_packet!(socket, data, packet_id)
|
||||
packet = Packet.new(10+data.bytesize, 0, packet_id, data)
|
||||
|
||||
self.send_to(packet, socket)
|
||||
self.recv_from(socket)
|
||||
end
|
||||
|
||||
def self.send_to(packet, socket)
|
||||
szb = [packet[:size]].pack 'l<'
|
||||
idb = [packet[:id]].pack 'l<'
|
||||
type_b = [packet[:type]].pack 'l<'
|
||||
body_b = [packet[:body]].pack 'Z*'
|
||||
data = szb + idb + type_b + body_b + "\0"
|
||||
|
||||
socket.sendmsg(data)
|
||||
end
|
||||
|
||||
def self.recv_from(socket)
|
||||
msg_ary = socket.recvmsg
|
||||
msg = msg_ary[0]
|
||||
ary = msg.unpack('l<l<l<Z*')
|
||||
|
||||
Packet.new(ary[0], ary[1], ary[2], ary[3])
|
||||
end
|
||||
|
||||
def self.identify_password
|
||||
password = AsaCtrl::StartParamsHelper.get_value(ENV['ASA_START_PARAMS'], 'ServerAdminPassword')
|
||||
return password if password
|
||||
|
||||
config = AsaCtrl::IniConfigHelper.game_user_settings_ini
|
||||
return config['ServerSettings']['ServerAdminPassword'] if config['ServerSettings'] && config['ServerSettings']['ServerAdminPassword']
|
||||
|
||||
raise AsaCtrl::Errors::RconPasswordNotFoundError
|
||||
end
|
||||
|
||||
def self.identify_port
|
||||
port = AsaCtrl::StartParamsHelper.get_value(ENV['ASA_START_PARAMS'], 'RCONPort')
|
||||
return port.to_i if port
|
||||
|
||||
config = AsaCtrl::IniConfigHelper.game_user_settings_ini
|
||||
return config['ServerSettings']['RCONPort'].to_i if config['ServerSettings'] && config['ServerSettings']['RCONPort']
|
||||
|
||||
raise AsaCtrl::Errors::RconPortNotFoundError
|
||||
end
|
||||
end
|
||||
end
|
||||
+1
@@ -0,0 +1 @@
|
||||
d21bd48479ab35213a7bf5b7eb87d0f156da16891cdc6bed3665e268811304c20cc9834ff901b9d4e31abc6d10bf6b8066276d32f7c487607270a3a9f975ae2c /tmp/GE-Proton10-17.tar.gz
|
||||
Reference in New Issue
Block a user