KF2-SRV/SOURCES/kf2-srv
2020-07-09 02:03:57 +03:00

1259 lines
43 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# kf2-srv is a command line tool for managing a set of Killing Floor 2 servers.
# Copyright (C) 2019, 2020 GenZmeY
# mailto: genzmey@gmail.com
#
# This file is part of kf2-srv.
#
# kf2-srv is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
declare -a DiffNames
declare -a WaveNames
declare -A ModeNames
declare -A MutNames
source /etc/steamcmd/steamcmd.conf
source /etc/kf2-srv/kf2-srv.conf
ScriptFullname=$(readlink -e "$0")
ScriptName=$(echo "$ScriptFullname" | awk -F '/' '{print $NF;}')
readonly ScriptVersion="0.10.1"
# Constants. Don't change.
readonly AppServerNum="232130"
readonly AppClientNum="232090"
readonly StrangeConstUID="17825793"
readonly ServerBotLogin="srvbot"
function show_help ()
{
# echo "TODO: English description"
echo "$ScriptName v$ScriptVersion"
echo "Централизование управление серверами Killing Floor 2"
echo "Usage:"
echo "${ScriptName} OPTIONS stable branch"
echo "${ScriptName}-beta OPTIONS beta branch"
echo ""
echo "Mandatory arguments to long options are mandatory for short options too."
echo " -n, --new INSTANCE создает новый ЭКЗЕМПЛЯР сервера"
echo " -d, --delete [INSTANCE] удаляет указанный ЭКЗМПЛЯР сервера; если"
echo " ЭКЗЕМПЛЯР не указан, удаляет все сервера"
echo " -s, --status [INSTANCE] отображает состояние указанного ЭКЗЕМПЛЯРА"
echo " сервера; если ЭКЗЕМПЛЯР не указан,"
echo " отображает состояние всех экземпляров сервера"
echo " -u, --update при первом запуске производит установку KF2;"
echo " в дальнейшем устанавливает обновления при их"
echo " наличии"
echo " -v, --validate проверяет целостность файлов, при"
echo " необходимости перекачивает их."
echo " -r, --run [OPTIONS] запускает экземпляр сервера с указанными"
echo " ПАРАМЕТРАМИ"
echo " -c, --chat MSG [INSTANCE] отправляет сообщение в чат указанных экземпляров"
echo " -st, --start [INSTANCE] запускает указанный ЭКЗЕМПЛЯР сервера; если"
echo " ЭКЗЕМПЛЯР не указан, запускает все"
echo " автозапускаемые экземпляры сервера"
echo " -sp, --stop [INSTANCE] останавливает указанный ЭКЗЕМПЛЯР сервера;"
echo " если ЭКЗЕМПЛЯР не указан, останавливает все"
echo " экземпляры сервера"
echo " -rs, --restart [INSTANCE] перезапускает указанный ЭКЗЕМПЛЯР сервера;"
echo " если ЭКЗЕМПЛЯР не указан, перезапускает"
echo " все автозапускаемые экземпляры сервера"
echo " -en, --enable [INSTANCE] добавляет указанный ЭКЗЕМПЛЯР сервера в"
echo " автозапуск; если ЭКЗЕМПЛЯР не указан,"
echo " добавляет все экземпляры сервера в автозапуск"
echo " -di, --disable [INSTANCE] удаляет указанный ЭКЗЕМПЛЯР сервера из"
echo " автозапуска; если ЭКЗЕМПЛЯР не указан,"
echo " удаляет все экземпляры сервера из автозапуска"
echo " -wl, --workshop-list отображает список ресурсов из SteamWorkshop"
echo " -wa, --workshop-add [MAP_ID] добавляет ресурс из SteamWorkshop по URL или"
echo " WorkshopID"
echo " -wd, --workshop-del [MAP_ID] удаляет ресурс SteamWorkshop по URL или WorkshopID"
echo " -ws, --workshop-sync синхронизирует списки сторонних карт в"
echo " конфигурационных файлах с имеющимися файлами"
echo " сторонних карт; синхронизирует списки карт из"
echo " SteamWorkshop между всеми экземплярами серверов"
echo "-mrs, --map-rotate-save [INSTANCE] сохраняет текущий порядок карт для"
echo " указанного ЭКЗЕМПЛЯРА сервера; если ЭКЗЕМПЛЯР"
echo " не указан, сохраняет порядок для всех ЭКЗЕМПЛЯРОВ"
echo "-mrl, --map-rotate-load [INSTANCE] применяет ранее сохраненный порядок карт"
echo " для указанного ЭКЗЕМПЛЯРА сервера; если ЭКЗЕМПЛЯР"
echo " не указан, применяет сохраненные порядки для"
echo " всех ЭКЗЕМПЛЯРОВ сервера"
echo " -bl, --ban-list отображает список заблокированных пользователей"
echo " -bs, --ban-sync синхронизирует список заблокированных"
echo " пользователей между всеми экземплярами сервера"
echo " -ba, --ban-add [BAN_ID] добавляет пользователя в список заблокированных"
echo " допустимо использовать ID3, SteamID, а также"
echo " ссылку на профиль пользователя"
echo " -bd, --ban-del [BAN_ID] удаляет пользователя из списка заблокированных"
echo " допустимо использовать ID3, SteamID, а также"
echo " ссылку на профиль пользователя"
echo " -fp, --fix-permissions [INSTANCE] поправить права на ini файлы"
echo " -as, --admin-sync синхронизировать всех админов (заглушка)"
echo " -pg, --password-game PASSWORD [INSTANCE] установить пароль игры для экземпляров"
echo " -pa, --password-admin PASSWORD [INSTANCE] установить пароль админа для экземпляров"
echo " -h, --help display this help and exit"
}
# Use this function with non-root user only!!!
function run_as_root () # $*: Args
{
if [[ -n "$BetaPostfix" ]]; then
BetaPostfix="beta"
fi
if [[ -n $(groups "$USER" | grep -Fo 'wheel') ]]; then
sudo "$ScriptFullname" $BetaPostfix $*
else
echo "You must be root or sudo-user to run this command."
return 1
fi
}
function service_name () # $*: Instance[s]
{
local Services=""
for Instance in $*
do
Services+=" kf2-srv$BetaPostfix@$Instance.service"
done
echo "$Services"
}
function show_instances ()
{
find "$InstanceConfigDir" -maxdepth 1 -mindepth 1 -type d -printf "%f\n"
}
function show_enabled_instances ()
{
local EnabledInstances=""
for Instance in $(show_instances)
do
if systemctl -q is-enabled $(service_name "$Instance") ; then
EnabledInstances+=" $Instance"
fi
done
echo "$EnabledInstances"
}
function instance_exists () # $1: Instance
{
if [[ -d "$InstanceConfigDir/$1" ]]; then
return 0
else
return 1
fi
}
function server_exists ()
{
if [[ -x "$AppBin" ]]; then
return 0
else
return 1
fi
}
function updates_aviable ()
{
return 0 # steamcmd does not show updates even if they are :(
# TODO: check updates correctly (but how?)
if [[ -n "$BetaPostfix" ]]; then
local BetaArg="-beta preview"
fi
if steamcmd +login anonymous \
+force_install_dir $InstallDir \
+app_info_update 1 \
+app_status $AppServerNum $BetaArg \
+quit | \
grep 'install state:' | \
grep -qiF 'Update Required'; then
return 0
else
return 1
fi
}
function new_instance () # $*: InstanceName[s]
{
if [[ -z "$*" ]]; then
echo "Name of instance[s] must be set"
exit 1
elif ! server_exists; then
echo "You must install server first"
echo "Run \"$ScriptName --update\" to install it"
exit 1
fi
local MaxGamePort='7777'
local MaxQueryPort='27015'
local MaxWebAdminPort='8080'
for Instance in $(show_instances)
do
local Config="$InstanceConfigDir/$Instance/main.conf"
local GamePort=$(multini --get "$Config" '' 'PortGame')
local WebAdminPort=$(multini --get "$Config" '' 'PortWeb')
local QueryPort=$(multini --get "$Config" '' 'PortQuery')
if [[ "$GamePort" -gt "$MaxGamePort" ]]; then MaxGamePort="$GamePort"; fi
if [[ "$QueryPort" -gt "$MaxQueryPort" ]]; then MaxQueryPort="$QueryPort"; fi
if [[ "$WebAdminPort" -gt "$MaxWebAdminPort" ]]; then MaxWebAdminPort="$WebAdminPort"; fi
done
for Instance in $*
do
if instance_exists "$Instance"; then
echo "Instance $Instance already exists - skip"
continue
fi
local InstanceDir="$InstanceConfigDir/$Instance"
local DirMode="-d -g $SteamUser -o $SteamUser -m 775"
local FileMode=" -g $SteamUser -o $SteamUser -m 664"
install $DirMode "$InstanceDir"
install $DirMode "$InstanceDir/LinuxServer"
install $FileMode "$MainConfigTemplate" "$InstanceDir/main.conf"
install $FileMode "$DefaultConfigDir/KFAI.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/KFWeb.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/KFWebAdmin.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/KFMultiAdmin.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/LinuxServer-KFEngine.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/LinuxServer-KFGame.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/LinuxServer-KFInput.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/LinuxServer-KFSystemSettings.ini" "$InstanceDir"
install $FileMode "$DefaultConfigDir/LinuxServer/LinuxServerEngine.ini" "$InstanceDir/LinuxServer"
install $FileMode "$DefaultConfigDir/LinuxServer/LinuxServerGame.ini" "$InstanceDir/LinuxServer"
install $FileMode "$DefaultConfigDir/LinuxServer/LinuxServerInput.ini" "$InstanceDir/LinuxServer"
install $FileMode "$DefaultConfigDir/LinuxServer/LinuxServerSystemSettings.ini" "$InstanceDir/LinuxServer"
((MaxGamePort++)); ((MaxQueryPort++)); ((MaxWebAdminPort++))
multini -s "$InstanceDir/main.conf" '' 'PortGame' "$MaxGamePort"
multini -s "$InstanceDir/main.conf" '' 'PortQuery' "$MaxQueryPort"
multini -s "$InstanceDir/main.conf" '' 'PortWeb' "$MaxWebAdminPort"
echo "Instance $Instance created. See /etc/$ScriptName/instances$BetaPostfix/$Instance for edit configuration"
done
}
function delete_instance () # $*: [InstanceName[s]]
{
if [[ -z "$*" ]]; then
echo "Are you sure you want to delete all instances? [y/N]"
local Answ="N"
read Answ
if [[ "$Answ" == "y" || "$Answ" == "Y" ]]; then
for Instance in $(show_instances)
do
stop_instance "$Instance"
delete_instance "$Instance"
done
fi
else
for Instance in $*
do
if instance_exists "$Instance"; then
local InstanceDir="$InstanceConfigDir/$Instance"
stop_instance "$Instance"
rm -rf "$InstanceDir"
echo "Instance $Instance removed"
else
echo "Instance $Instance not exists"
fi
done
fi
}
function instance_status () # $1: InstanceName
{
local Instance="$1"
if ! instance_exists "$Instance"; then return 1; fi
if systemctl -q is-enabled $(service_name "$Instance"); then
local IsEnabled="enabled"
else
local IsEnabled="disabled"
fi
if systemctl | grep $(service_name "$Instance") | grep -q 'running' ; then
local IsRuning="running"
else
local IsRuning="stopped"
fi
local Comment=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "Comment")
local GamePort=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "PortGame")
local WebAdminPort=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "PortWeb")
local QueryPort=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "PortQuery")
local GameType=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "Game")
local GameLength=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "Length")
local GameDifficulty=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "Difficulty")
local Map=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "Map")
local Mutators=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "Mutators")
local Args=$( multini -g "$InstanceConfigDir/$Instance/main.conf" '' "Args")
local DisplayGameType=''
local DisplayGameLength=''
local DisplayDifficulty=''
local DisplayMutators=''
for Mutator in ${Mutators//,/ }
do
local MutName=${MutNames[$Mutator]}
if [[ -z "$MutName" ]]; then
MutName="$Mutator"
fi
if [[ -z "$DisplayMutators" ]]; then
DisplayMutators="$MutName"
else
DisplayMutators="$DisplayMutators, $MutName"
fi
done
if [[ -n "$GameType" ]]; then DisplayGameType=${ModeNames[$GameType]} ; fi
if [[ -n "$GameLength" ]]; then DisplayGameLength=${WaveNames[$GameLength]} ; fi
if [[ -n "$GameDifficulty" ]]; then DisplayDifficulty=${DiffNames[$GameDifficulty]} ; fi
if [[ -z "$DisplayMutators" ]] && [[ -z "$Mutators" ]]; then
DisplayMutators='-'
fi
if [[ -z "$DisplayGameType" ]]; then
DisplayGameType="$GameType"
fi
if [[ -z "$Args" ]]; then
Args='-'
fi
if [[ "$GameType" == 'KFGameContent.KFGameInfo_WeeklySurvival' || \
"$GameType" == 'KFGameContent.KFGameInfo_Endless' || \
"$GameType" == 'KFGameContent.KFGameInfo_VersusSurvival' || \
"$GameType" == 'KFGameContent.KFGameInfo_Objective' ]]; then
DisplayGameLength='-'
fi
if [[ "$GameType" == 'KFGameContent.KFGameInfo_WeeklySurvival' || \
"$DisplayGameType" == 'KFGameContent.KFGameInfo_VersusSurvival' ]]; then
DisplayDifficulty='-'
fi
echo -e "$Instance:$IsEnabled:$IsRuning:$GamePort:$QueryPort:$WebAdminPort:$DisplayGameType:$DisplayGameLength:$DisplayDifficulty:$DisplayMutators:$Args:$Comment"
}
function show_status () # $*: [InstanceName[s]]
{
{
echo -e "INSTANCE:AUTORUN:STATE:P_GAME:P_QUERY:P_WEB:TYPE:LEN:DIFF:MUTATORS:ARGS:COMMENT"
{
local InstanceList="$*"
if [[ -z "$*" ]] ; then
InstanceList=$(show_instances)
fi
for Instance in $InstanceList
do
instance_status "$Instance"
done
} | sort -t : -k 4
} | column -t -s :
}
function validate ()
{
if [[ -n "$BetaPostfix" ]]; then
local BetaArg="-beta preview"
fi
stop_instance
steamcmd +login $SteamLogin +force_install_dir $InstallDir +app_update $AppServerNum $BetaArg validate +exit
fix_steamclient_so
start_instance
}
function make_default_instance () # $1: Dir
{
local InstanceDir="$InstanceConfigDir/default"
install -d -g "$SteamUser" -o "$SteamUser" -m 775 "$InstanceDir"
install -d -g "$SteamUser" -o "$SteamUser" -m 775 "$InstanceDir/LinuxServer"
install -g "$SteamUser" -o "$SteamUser" -m 664 "$MainConfigTemplate" "$InstanceDir/main.conf"
ln -s "$DefaultConfigDir/KFAI.ini" "$InstanceDir/KFAI.ini"
ln -s "$DefaultConfigDir/KFWeb.ini" "$InstanceDir/KFWeb.ini"
ln -s "$DefaultConfigDir/KFWebAdmin.ini" "$InstanceDir/KFWebAdmin.ini"
ln -s "$DefaultConfigDir/KFMultiAdmin.ini" "$InstanceDir/KFMultiAdmin.ini"
ln -s "$DefaultConfigDir/LinuxServer-KFEngine.ini" "$InstanceDir/LinuxServer-KFEngine.ini"
ln -s "$DefaultConfigDir/LinuxServer-KFGame.ini" "$InstanceDir/LinuxServer-KFGame.ini"
ln -s "$DefaultConfigDir/LinuxServer-KFInput.ini" "$InstanceDir/LinuxServer-KFInput.ini"
ln -s "$DefaultConfigDir/LinuxServer-KFSystemSettings.ini" "$InstanceDir/LinuxServer-KFSystemSettings.ini"
ln -s "$DefaultConfigDir/LinuxServer/LinuxServerEngine.ini" "$InstanceDir/LinuxServer/LinuxServerEngine.ini"
ln -s "$DefaultConfigDir/LinuxServer/LinuxServerGame.ini" "$InstanceDir/LinuxServer/LinuxServerGame.ini"
ln -s "$DefaultConfigDir/LinuxServer/LinuxServerInput.ini" "$InstanceDir/LinuxServer/LinuxServerInput.ini"
ln -s "$DefaultConfigDir/LinuxServer/LinuxServerSystemSettings.ini" "$InstanceDir/LinuxServer/LinuxServerSystemSettings.ini"
fix_ini_permissions "$InstanceDir"
fix_ini_eol "$InstanceDir"
}
function fix_permissions () # $*: Instance[s]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
fix_ini_permissions "$InstanceConfigDir"
else
for Instance in $InstanceList
do
fix_ini_permissions "$InstanceConfigDir/$Instance"
done
fi
}
function fix_ini_permissions () # $1: Dir
{
find "$1" \( -type l -o -type f \) -name '*.ini' | \
xargs --max-procs=$(nproc) -I {} \
sh -c "chmod 664 {}; chown $SteamUser:$SteamUser {}"
}
function fix_ini_eol () # $1: Dir
{
find "$1" \( -type l -o -type f \) -name '*.ini' | \
xargs --max-procs=$(nproc) -I {} \
sh -c "dos2unix -F {}"
}
function fix_steamclient_so ()
{
rm -f "$InstallDir/linux64/steamclient.so"
rm -f "$InstallDir/steamclient.so"
rm -f "$InstallDir/Binaries/Win64/lib64/steamclient.so"
ln -s "/usr/share/steamcmd/linux64/steamclient.so" "$InstallDir/linux64/steamclient.so"
ln -s "/usr/share/steamcmd/linux64/steamclient.so" "$InstallDir/steamclient.so"
ln -s "/usr/share/steamcmd/linux64/steamclient.so" "$InstallDir/Binaries/Win64/lib64/steamclient.so"
}
function create_map_dirs ()
{
# space saving
local InstallDirOrig="/usr/games/kf2-srv"
local InstallDirBeta="/usr/games/kf2-srv-beta"
local DownloadDirOrig="$InstallDirOrig/Binaries/Win64/steamapps/workshop/content/$AppClientNum"
local CacheDirOrig="$InstallDirOrig/KFGame/Cache"
local DownloadDirBeta="$InstallDirBeta/Binaries/Win64/steamapps/workshop/content/$AppClientNum"
local CacheDirBeta="$InstallDirBeta/KFGame/Cache"
if [[ -z "$BetaPostfix" ]]; then # Orig
sudo -u "$SteamUser" install -d -m 775 "$DownloadDirOrig"
if [[ -d "$CacheDirBeta" ]]; then
ln -s "$CacheDirBeta" "$CacheDirOrig"
rm -rf "$DownloadDirOrig"
ln -s "$DownloadDirBeta" "$DownloadDirOrig"
else
sudo -u "$SteamUser" install -d -m 775 "$CacheDirOrig"
fi
else # Beta
sudo -u "$SteamUser" install -d -m 775 "$DownloadDirBeta"
if [[ -d "$CacheDirOrig" ]]; then
ln -s "$CacheDirOrig" "$CacheDirBeta"
rm -rf "$DownloadDirBeta"
ln -s "$DownloadDirOrig" "$DownloadDirBeta"
else
sudo -u "$SteamUser" install -d -m 775 "$CacheDirBeta"
fi
fi
}
function first_install ()
{
if ! steamcmd +login $SteamLogin +force_install_dir $InstallDir +app_update $AppServerNum $BetaArg validate +exit; then
echo "Errors during installation - exit"
exit 1
fi
echo "Creating base ini files"
sudo -u "$SteamUser" $AppBin &> /dev/null &
while true
do
if [[ -e "$DefaultConfigDir/KFAI.ini" ]] &&
[[ -e "$DefaultConfigDir/KFWeb.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer-KFEngine.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer-KFGame.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer-KFInput.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer-KFSystemSettings.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer/LinuxServerEngine.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer/LinuxServerGame.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer/LinuxServerInput.ini" ]] &&
[[ -e "$DefaultConfigDir/LinuxServer/LinuxServerSystemSettings.ini" ]]; then
break
fi
sleep 2
done
killall -KILL KFGameSteamServer.bin.x86_64; sleep 1
echo "Setting up WebAdmin"
multini -s "$DefaultConfigDir/KFWeb.ini" "IpDrv.WebServer" "bEnabled" "true"
sudo -u "$SteamUser" $AppBin &> /dev/null &
while true
do
if [[ -e "$DefaultConfigDir/KFWebAdmin.ini" ]]; then
break
fi
sleep 2
done
killall -KILL KFGameSteamServer.bin.x86_64; sleep 1
multini -s "$DefaultConfigDir/KFWebAdmin.ini" "WebAdmin.WebAdmin" "AuthenticationClass" "WebAdmin.MultiWebAdminAuth"
multini -s "$DefaultConfigDir/KFWebAdmin.ini" "WebAdmin.WebAdmin" "bHttpAuth" "True"
echo "Wait while WebAdmin up"
sudo -u "$SteamUser" $AppBin &> /dev/null &
while ! curl -s -o "/dev/null" -u "Admin:Admin" "localhost:8080"
do
sleep 2
done
echo "Setting up server bot"
while ! curl -s -o "/dev/null" \
-u "Admin:Admin" \
"localhost:8080/ServerAdmin/multiadmin" \
--request POST \
--data adminid="$ServerBotLogin" \
--data action="create"
do sleep 2; done
while ! multini -gq \
"$DefaultConfigDir/KFMultiAdmin.ini" \
"$ServerBotLogin MultiAdminData" \
"Password"
do sleep 2; done
while ! curl -s -o "/dev/null" \
-u "Admin:Admin" \
"localhost:8080/ServerAdmin/multiadmin" \
--request POST \
--data adminid="$ServerBotLogin" \
--data displayname="ServerBot" \
--data enabled=1 \
--data password1="$ServerBotPassword" \
--data password2="$ServerBotPassword" \
--data order="DenyAllow" \
--data deny= \
--data allow= \
--data action="save"
do sleep 2; done
while [[ -z $(multini -g \
"$DefaultConfigDir/KFMultiAdmin.ini" \
"$ServerBotLogin MultiAdminData" \
"Password") ]]
do sleep 2; done
killall -KILL KFGameSteamServer.bin.x86_64; sleep 1
create_map_dirs
fix_steamclient_so
ln -s "$InstanceConfigDir" "$InstanceConfigLnk"
make_default_instance
echo "KF2 succesfully installed"
}
function update_kf2 ()
{
if [[ -n "$BetaPostfix" ]]; then
local BetaArg="-beta preview"
fi
if ! server_exists; then
first_install
elif updates_aviable; then
stop_instance
steamcmd +login $SteamLogin +force_install_dir $InstallDir +app_update $AppServerNum $BetaArg +exit
start_instance
else
echo "Server is up to date"
fi
}
function start_instance () # $*: [InstanceName[s]]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_enabled_instances)
fi
local InactiveServiceList=""
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
local Service=$(service_name "$Instance")
if systemctl -q is-active $Service ; then
echo "Instance $Instance already running - skip"
else
InactiveServiceList+=" $Service"
fi
else
echo "Instance $Instance not exitst"
fi
done
if [[ -n "$InactiveServiceList" ]]; then
systemctl start $InactiveServiceList
else
echo "Nothing to do"
fi
}
function stop_instance () # $*: [InstanceName[s]]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
local ToStopInstanceList=""
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
ToStopInstanceList+=" $Instance"
else
echo "Instance $Instance not exitst"
fi
done
if [[ -n "$ToStopInstanceList" ]]; then
systemctl stop $(service_name "$ToStopInstanceList")
else
echo "Nothing to do"
fi
}
function restart_instance () # $*: [InstanceName[s]]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_enabled_instances)
fi
local ToRestartInstancesList=""
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
ToRestartInstancesList+=" $Instance"
else
echo "Instance $Instance not exitst"
fi
done
if [[ -n "$ToRestartInstancesList" ]]; then
systemctl restart $(service_name "$ToRestartInstancesList")
else
echo "Nothing to do"
fi
}
function enable_instance () # $1*: [InstanceName[s]]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
local ToEnableInstanceList=""
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
ToEnableInstanceList+=" $Instance"
else
echo "Instance $Instance not exist"
fi
done
if [[ -n "$ToEnableInstanceList" ]]; then
systemctl enable $(service_name "$ToEnableInstanceList")
else
echo "Nothing to do"
fi
}
function disable_instance () # $*: [InstanceName[s]]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
local ToDisableInstanceList=""
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
ToDisableInstanceList+=" $Instance"
else
echo "Instance $Instance not exitst"
fi
done
if [[ -n "$ToDisableInstanceList" ]]; then
systemctl disable $(service_name "$ToDisableInstanceList")
else
echo "Nothing to do"
fi
}
function run ()
{
if [[ "$USER" == "$SteamUser" ]]; then
"$AppBin" $*
elif [[ -n $(groups "$USER" | grep -Fo 'wheel') ]] || [[ "$EUID" -eq 0 ]]; then
sudo -u "$SteamUser" "$AppBin" $*
else
echo "You must be a $SteamUser, root or sudo-user to run this command."
fi
}
function name_by_workshopID () # $1: WorkshopID
{
local WorkshopID="$1"
local Cache="$CacheDir/$WorkshopID"
local Result=""
if [[ -d "$Cache" ]]; then
Result=$(find "$Cache" -type f -name '*.kfm' -printf '%f\n' | head -n 1)
if [[ -z "$Result" ]]; then
Result=$(find "$Cache" -type f -name '*.u' -printf '%f\n' | head -n 1)
fi
fi
echo "$Result"
}
function workshop_list_ids ()
{
local WsList=''
for Instance in $(show_instances)
do
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFEngine.ini"
if multini -gq "$Config" "OnlineSubsystemSteamworks.KFWorkshopSteamworks" "ServerSubscribedWorkshopItems"; then
if [[ -n "$WsList" ]]; then
WsList+=$'\n'
fi
WsList+=$(multini -g "$Config" "OnlineSubsystemSteamworks.KFWorkshopSteamworks" "ServerSubscribedWorkshopItems")
fi
done
echo "$WsList" | sort -V -u
}
function workshop_list ()
{
# TODO: Multiple *.kfm/*u in folder
{
echo "WORKSHOP_ID NAME SIZE WORKSHOP_URL"
{
for WorkshopID in $(workshop_list_ids)
do
local Cache="$CacheDir/$WorkshopID"
local Downl="$DownloadDir/$WorkshopID"
local Url="https://steamcommunity.com/sharedfiles/filedetails/?id=$WorkshopID"
local WsName=$(name_by_workshopID "$WorkshopID")
if [[ -n "$WsName" ]]; then
local WsSize=$(du -sch "$Downl" "$Cache" | tail -n 1 | grep -Po '^[^\s]+')
else
local WsSize="-"; WsName="-"
fi
echo "$WorkshopID $WsName $WsSize $Url"
done
} | sort -k 2
} | column -t
}
function any_to_workshopID () # $1: WorkshopID/URL
{
if echo "$1" | grep -qP '^http.+'; then
local WorkshopID=$(echo "$1" | sed -r 's/.+=([0-9]+)$/\1/')
else
local WorkshopID="$1"
fi
echo "$WorkshopID"
}
function workshop_add () # $*: WorkshopID[s]
{
for Instance in $(show_instances)
do
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFEngine.ini"
multini -ar "$Config" "IpDrv.TcpNetDriver" "DownloadManagers" "OnlineSubsystemSteamworks.SteamWorkshopDownload"
for Map in $*
do
local WorkshopID=$(any_to_workshopID "$Map")
if ! multini -gq "$Config" "OnlineSubsystemSteamworks.KFWorkshopSteamworks" "ServerSubscribedWorkshopItems" "$WorkshopID"; then
echo "Add workshop $WorkshopID to $Instance"
multini -ar "$Config" "OnlineSubsystemSteamworks.KFWorkshopSteamworks" "ServerSubscribedWorkshopItems" "$WorkshopID"
fi
done
done
}
function workshop_del () # $*: WorkshopID[s]
{
for Map in $*
do
local WorkshopID=$(any_to_workshopID "$Map")
local WsName=$(name_by_workshopID "$WorkshopID")
local Cache="$CacheDir/$WorkshopID"
local Downl="$DownloadDir/$WorkshopID"
echo -e "Clear cache:
$Cache
$Downl"
rm -rf "$Cache" "$Downl"
for Instance in $(show_instances)
do
local ConfigEngine="$InstanceConfigDir/$Instance/LinuxServer-KFEngine.ini"
multini -d "$ConfigEngine" "OnlineSubsystemSteamworks.KFWorkshopSteamworks" "ServerSubscribedWorkshopItems" "$WorkshopID"
if echo "$WsName" | grep -qP '\.kfm$' ; then
echo "Remove map $WorkshopID ($WsName) from $Instance"
local WsNameShort=$(echo "$WsName" | sed 's/\.kfm$//')
local ConfigGame="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
multini -d "$ConfigGame" "$WsNameShort KFMapSummary"
fi
done
done
}
function workshop_sync ()
{
workshop_add $(workshop_list_ids)
# TODO: Make it faster
for Instance in $(show_instances)
do
local Service=$(service_name "$Instance")
if ! instance_exists "$Instance"; then
echo "Instance $Instance not exitst"
elif systemctl -q is-active $Service ; then
echo "Instance $Instance is running - skip."
else
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
for MapFile in $(find -L "$CacheDir" -type f -name '*.kfm' -printf "%f\n")
do
MapName=$(echo "$MapFile" | sed -r 's|.kfm$||')
if ! multini -gq "$Config" "$MapName KFMapSummary"; then
echo "Adding $MapName to $Instance."
multini -s "$Config" "$MapName KFMapSummary" "MapName" "$MapName"
multini -s "$Config" "$MapName KFMapSummary" "bPlayableInSurvival" "True"
multini -s "$Config" "$MapName KFMapSummary" "bPlayableInWeekly" "True"
multini -s "$Config" "$MapName KFMapSummary" "bPlayableInVsSurvival" "True"
multini -s "$Config" "$MapName KFMapSummary" "bPlayableInEndless" "True"
multini -s "$Config" "$MapName KFMapSummary" "bPlayableInObjective" "False"
fi
done
for MutFile in $(find -L "$CacheDir" -type f -name '*.u' -printf "%f\n")
do
MutName=$(echo "$MutFile" | sed -r 's|.u$||')
if ! multini -gq "$Config" "$MutName KFMutatorSummary"; then
echo "Adding $MutName to $Instance."
multini -s "$Config" "$MutName KFMutatorSummary" "ClassName" ""
fi
done
fi
done
}
function map_rotate_save () # $*: Instance[s]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
local MapRotate="$InstanceConfigDir/$Instance/MapRotate.ini"
grep -F 'ActiveMapCycle=' "$Config" > "$MapRotate"
grep -F 'GameMapCycles=' "$Config" >> "$MapRotate"
else
echo "Instance $Instance not exitst"
fi
done
}
function map_rotate_to_webstring () # $1: MapRotate
{
local RN='%0D%0A'
echo "$1" | \
sed -r 's/^\(Maps=\("//' | \
sed -r 's/"\)\)$//' | \
sed 's/ /+/g' | \
sed "s/\",\"/${RN}/g"
}
function map_rotate_load () # $*: Instance[s]
{
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
for Instance in $InstanceList
do
local Service=$(service_name "$Instance")
local MapRotate="$InstanceConfigDir/$Instance/MapRotate.ini"
if ! instance_exists "$Instance"; then
echo "Instance $Instance not exists"
elif ! [[ -e "$MapRotate" ]]; then
echo "$MapRotate not found - skip"
elif systemctl -q is-active $Service ; then
# TODO: Delete other cycles
# Example: maplistidx=1&mapcycle=KF-Airship%0D%0A&delete=doit
local ActiveCycleIndex=$(multini -g "$MapRotate" '' 'ActiveMapCycle')
local ActiveCycleWeb=''
local Index=0
while read MapCycle
do
local MapCycleWeb=$(map_rotate_to_webstring "$MapCycle")
admin_curl "$Instance" "ServerAdmin/settings/maplist" \
--request POST \
--data maplistidx="$Index" \
--data mapcycle="$MapCycleWeb" \
--data action="save"
if [[ "$Index" -eq "$ActiveCycleIndex" ]]; then
ActiveCycleWeb="$MapCycleWeb"
fi
((Index++))
done < <(multini -g "$MapRotate" '' 'GameMapCycles')
if [[ -n "$ActiveCycleWeb" ]]; then
admin_curl "$Instance" "ServerAdmin/settings/maplist" \
--request POST \
--data maplistidx="$ActiveCycleIndex" \
--data mapcycle="$ActiveCycleWeb" \
--data activate="activate"
fi
else
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
sed -i --follow-symlinks -r "/(ActiveMapCycle=|GameMapCycles=)/d" "$Config"
sed -i --follow-symlinks "/\[KFGame\.KFGameInfo\]/ r $MapRotate" "$Config"
fi
done
}
# conversion algorithm taken from here:
# https://github.com/noobient/killinuxfloor/blob/master/share/killinuxfloor
# thank bviktor for that :)
function steamID3_to_steamID64 () # $1: ID3
{
# steamID64 = "7656" + (steamID3 + 1197960265728)
ID64=$1
((ID64+=1197960265728))
ID64="7656${ID64}"
echo "$ID64"
}
function steamID64_to_steamID3 () # $1: ID4
{
# steamID3 = substr(steamID64, 4) - 1197960265728
ID3=${1:4}
((ID3-=1197960265728))
echo "$ID3"
}
function ban_list_id3 ()
{
local BanList=''
for Instance in $(show_instances)
do
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
if multini -gq "$Config" "Engine.AccessControl" "BannedIDs"; then
if [[ -n "$BanList" ]]; then
BanList+=$'\n'
fi
BanList+=$(multini -g "$Config" "Engine.AccessControl" "BannedIDs" | sed -r 's/.+A=([0-9]+),.+/\1/')
fi
done
echo "$BanList" | sort -V -u
}
function ban_list () # $1: [--human-readable]
{
{
local Num=1
echo "NUM STEAM_ID3 STEAM_ID64 URL_CONST URL_EFFECTIVE"
for ID3 in $(ban_list_id3)
do
local ID64=$(steamID3_to_steamID64 "$ID3")
local UrlConst="https://steamcommunity.com/profiles/$ID64"
local UrlEffective=$(curl "$UrlConst" -s -L -I -o /dev/null -w '%{url_effective}')
if [[ "$UrlConst" == "$UrlEffective" ]]; then
UrlEffective="-"
fi
echo "$Num $ID3 $ID64 $UrlConst $UrlEffective"
((Num++))
done
} | column -t
}
function ban_ID3 () # $1: ID3
{
ID3="$1"
for Instance in $(show_instances)
do
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
local BanStr="(Uid=(A=$ID3,B=$StrangeConstUID))"
local Service=$(service_name "$Instance")
if ! multini -gq "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr"; then
echo "Add ban $ID3 to $Instance"
if systemctl -q is-active $Service ; then
admin_curl "$Instance" "ServerAdmin/policy/bans" \
--request POST \
--data action="add" \
--data steamint64=$(steamID3_to_steamID64 $ID3) \
--data uniqueid=
else
multini -a "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr"
fi
fi
done
}
function unban_ID3 () # $1: ID3
{
ID3="$1"
for Instance in $(show_instances)
do
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
local BanStr="(Uid=(A=$ID3,B=$StrangeConstUID))"
local Service=$(service_name "$Instance")
if systemctl -q is-active $Service ; then
local PlainID=0
while read Line
do
if echo "$Line" | grep -qF "A=$ID3,"; then
echo "Remove ban $ID3 from $Instance"
admin_curl "$Instance" "ServerAdmin/policy/bans" \
--request POST \
--data action="delete" \
--data banid="plainid:$PlainID"
break
else
((PlainID++))
fi
done < <(multini -g "$Config" 'Engine.AccessControl' 'BannedIDs')
else
if multini -gq "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr"; then
echo "Remove ban $ID3 from $Instance"
multini -d "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr"
fi
fi
done
}
function any_to_ID3 () # $1: ID3/ID64/Url
{
if echo "$1" | grep -qP '^http.+'; then
local Xml=$(mktemp)
curl -ss "$1/?xml=1" > "$Xml"
local ID64=$(xmllint --xpath 'string(//steamID64/text())' "$Xml")
local ID3=$(steamID64_to_steamID3 "$ID64")
rm -f "$Xml"
elif [[ $(echo "$1" | wc -m) -eq 18 ]] && echo "$1" | grep -qP '^76561[0-9]+' ; then
local ID3=$(steamID64_to_steamID3 "$1")
else
local ID3="$1"
fi
echo "$ID3"
}
function ban_add () # $*: ban list
{
if [[ -z "$*" ]]; then
echo "Nothing to do"
exit 1
fi
for Ban in $*
do
ban_ID3 $(any_to_ID3 "$Ban")
done
}
function ban_del () # $*: ban list
{
if [[ -z "$*" ]]; then
echo "Nothing to do"
exit 1
fi
for Ban in $*
do
unban_ID3 $(any_to_ID3 "$Ban")
done
}
function ban_sync ()
{
ban_list_id3 | \
while read ID3
do
ban_ID3 "$ID3"
done
}
function admin_sync ()
{
# TODO: Implementation
echo "Dummy"
}
function admin_curl () # $1: Instance, $2: URL, $*: Request
{
local Instance="$1"
local URL="$2"
local MainConf="$InstanceConfigDir/$Instance/main.conf"
local WebPort=$(multini --get "$MainConf" '' "PortWeb")
shift; shift
curl -s -o "/dev/null" -u "$ServerBotLogin:$ServerBotPassword" "localhost:$WebPort/$URL" $*
}
function password_game () # $1: Password (if empty, use: ''), $*: Instance[s]
{
if echo "$1" | grep -qP '\s'; then
echo "Password should not contain spaces"
return 1
fi
local Password="$1"; shift
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
local Service=$(service_name "$Instance")
if systemctl -q is-active $Service ; then
admin_curl "$Instance" "ServerAdmin/policy/passwords" \
--request POST \
--data action="gamepassword" \
--data gamepw1="$Password" \
--data gamepw2="$Password"
else
multini -s "$Config" "Engine.AccessControl" "GamePassword" "$Password"
fi
else
echo "Instance $Instance not exitst"
fi
done
}
function password_admin () # $1: Password (if empty, use: ''), $*: Instance[s]
{
if echo "$1" | grep -qP '\s'; then
echo "Password should not contain spaces"
return 1
fi
local Password="$1"; shift
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini"
local Service=$(service_name "$Instance")
if systemctl -q is-active $Service ; then
admin_curl "$Instance" "ServerAdmin/policy/passwords" \
--request POST \
--data action="adminpassword" \
--data adminpw1="$Password" \
--data adminpw2="$Password"
else
multini -s "$Config" "Engine.AccessControl" "AdminPassword" "$Password"
fi
else
echo "Instance $Instance not exitst"
fi
done
}
function chat ()
{
local Message=$(echo "$1" | sed 's/ /+/g')
shift
local InstanceList="$*"
if [[ -z "$InstanceList" ]] ; then
InstanceList=$(show_instances)
fi
for Instance in $InstanceList
do
if instance_exists "$Instance"; then
local Service=$(service_name "$Instance")
if systemctl -q is-active $Service ; then
admin_curl "$Instance" "ServerAdmin/current/chat+frame+data" \
--request POST \
--data ajax=1 \
--data message="$Message" \
--data teamsay=-1
else
echo "Instance $Instance not running - skip"
fi
else
echo "Instance $Instance not exitst"
fi
done
}
if [[ "$1" == "beta" ]]; then
BetaPostfix="-beta"; shift
fi
InstallDir="/usr/games/kf2-srv$BetaPostfix"
AppBin="$InstallDir/Binaries/Win64/KFGameSteamServer.bin.x86_64"
DefaultConfigDir="$InstallDir/KFGame/Config"
DownloadDir="$InstallDir/Binaries/Win64/steamapps/workshop/content/$AppClientNum"
CacheDir="$InstallDir/KFGame/Cache"
InstanceConfigDir="/etc/kf2-srv/instances$BetaPostfix"
InstanceConfigLnk="$DefaultConfigDir/instances"
MainConfigTemplate="/etc/kf2-srv/main.conf.template"
if [[ $# -eq 0 ]]; then show_help; exit 0; fi
case $1 in
-h|--help ) show_help; ;;
-n|--new ) if [[ "$EUID" -eq 0 ]]; then shift; new_instance $*; else run_as_root $*; fi ;;
-d|--delete ) if [[ "$EUID" -eq 0 ]]; then shift; delete_instance $*; else run_as_root $*; fi ;;
-s|--status ) shift; show_status $*; ;;
-u|--update ) if [[ "$EUID" -eq 0 ]]; then update_kf2 ; else run_as_root $*; fi ;;
-v|--validate ) if [[ "$EUID" -eq 0 ]]; then validate ; else run_as_root $*; fi ;;
-r|--run ) shift; run $*; ;;
-c|--chat ) shift; Msg="$1"; shift; chat "$Msg" $*; ;;
-st|--start ) if [[ "$EUID" -eq 0 ]]; then shift; start_instance $*; else run_as_root $*; fi ;;
-sp|--stop ) if [[ "$EUID" -eq 0 ]]; then shift; stop_instance $*; else run_as_root $*; fi ;;
-rs|--restart ) if [[ "$EUID" -eq 0 ]]; then shift; restart_instance $*; else run_as_root $*; fi ;;
-en|--enable ) if [[ "$EUID" -eq 0 ]]; then shift; enable_instance $*; else run_as_root $*; fi ;;
-di|--disable ) if [[ "$EUID" -eq 0 ]]; then shift; disable_instance $*; else run_as_root $*; fi ;;
-wl|--workshop-list ) if [[ "$EUID" -eq 0 ]]; then shift; workshop_list ; else run_as_root $*; fi ;;
-wa|--workshop-add ) if [[ "$EUID" -eq 0 ]]; then shift; workshop_add $*; else run_as_root $*; fi ;;
-wd|--workshop-del ) if [[ "$EUID" -eq 0 ]]; then shift; workshop_del $*; else run_as_root $*; fi ;;
-ws|--workshop-sync ) if [[ "$EUID" -eq 0 ]]; then shift; workshop_sync ; else run_as_root $*; fi ;;
-mrs|--map-rotate-save ) if [[ "$EUID" -eq 0 ]]; then shift; map_rotate_save $*; else run_as_root $*; fi ;;
-mrl|--map-rotate-load ) if [[ "$EUID" -eq 0 ]]; then shift; map_rotate_load $*; else run_as_root $*; fi ;;
-bl|--ban-list ) if [[ "$EUID" -eq 0 ]]; then shift; ban_list ; else run_as_root $*; fi ;;
-bs|--ban-sync ) if [[ "$EUID" -eq 0 ]]; then shift; ban_sync ; else run_as_root $*; fi ;;
-ba|--ban-add ) if [[ "$EUID" -eq 0 ]]; then shift; ban_add $*; else run_as_root $*; fi ;;
-bd|--ban-del ) if [[ "$EUID" -eq 0 ]]; then shift; ban_del $*; else run_as_root $*; fi ;;
-fp|--fix-permissions ) if [[ "$EUID" -eq 0 ]]; then shift; fix_permissions $*; else run_as_root $*; fi ;;
-as|--admin-sync ) if [[ "$EUID" -eq 0 ]]; then shift; admin_sync ; else run_as_root $*; fi ;;
-pg|--password-game ) if [[ "$EUID" -eq 0 ]]; then shift; Pass="$1"; shift ; password_game "$Pass" $*; else run_as_root $*; fi ;;
-pa|--password-admin ) if [[ "$EUID" -eq 0 ]]; then shift; Pass="$1"; shift ; password_admin "$Pass" $*; else run_as_root $*; fi ;;
* ) echo "Command not recognized: $1"; exit 1 ;;
esac