chore: align ark-suac app and refresh appstore zip

This commit is contained in:
Joachim Friberg
2026-03-23 21:23:16 +01:00
parent 9c4265b429
commit 9c5ea400fb
36 changed files with 1611 additions and 3 deletions
@@ -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
@@ -0,0 +1,4 @@
source 'https://rubygems.org'
gem 'slop', '= 4.10.1'
gem 'iniparse', '= 1.5.0'
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
module AsaCtrl
module Errors
class BaseError < StandardError; end
end
end
@@ -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'
@@ -0,0 +1,5 @@
module AsaCtrl
module Errors
class ModAlreadyEnabledError < BaseError; end
end
end
@@ -0,0 +1,5 @@
module AsaCtrl
module Errors
class RconAuthenticationError < BaseError; end
end
end
@@ -0,0 +1,5 @@
module AsaCtrl
module Errors
class RconPasswordNotFoundError < BaseError; end
end
end
@@ -0,0 +1,5 @@
module AsaCtrl
module Errors
class RconPortNotFoundError < BaseError; end
end
end
@@ -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
@@ -0,0 +1,2 @@
require_relative './start_params_helper.rb'
require_relative './ini_config_helper.rb'
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
d21bd48479ab35213a7bf5b7eb87d0f156da16891cdc6bed3665e268811304c20cc9834ff901b9d4e31abc6d10bf6b8066276d32f7c487607270a3a9f975ae2c /tmp/GE-Proton10-17.tar.gz