#!/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 program 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 . source /etc/steamcmd/steamcmd.conf ScriptFullname=$(readlink -e "$0") ScriptName=$(echo "$ScriptFullname" | awk -F '/' '{print $NF;}') ScriptVersion="0.8.0" AppServerNum="232130" AppClientNum="232090" StrangeConstUID="17825793" DiffArray=('Normal' 'Hard' 'Suicide' 'Hell') WaveArray=('4' '7' '10') declare -A ModeArray ModeArray['KFGameContent.KFGameInfo_Survival']='Survival' ModeArray['KFGameContent.KFGameInfo_WeeklySurvival']='Weekly' ModeArray['KFGameContent.KFGameInfo_Endless']='Endless' ModeArray['KFGameContent.KFGameInfo_Objective']='Objective' ModeArray['KFGameContent.KFGameInfo_VersusSurvival']='Versus' 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 " -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 " -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=$(grep -Po '"-port=([0-9]+)' "$Config" | grep -Po '[0-9]+$') local WebAdminPort=$(grep -Po '"-webadminport=([0-9]+)' "$Config" | grep -Po '[0-9]+$') local QueryPort=$(grep -Po '"-queryport=([0-9]+)' "$Config" | grep -Po '[0-9]+$') 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/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++)) sed -i -r --follow-symlinks "s/-port=[0-9]+/-port=$MaxGamePort/g" "$InstanceDir/main.conf" sed -i -r --follow-symlinks "s/-queryport=[0-9]+/-queryport=$MaxQueryPort/g" "$InstanceDir/main.conf" sed -i -r --follow-symlinks "s/-webadminport=[0-9]+/-webadminport=$MaxWebAdminPort/g" "$InstanceDir/main.conf" 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 show_status_implementation_body () # $*: [InstanceName[s]] { for Instance in $InstanceList do 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 Description=$(grep -P 'Description=' "$InstanceConfigDir/$Instance/main.conf" | sed -r 's/(Description=|")//g') local GamePort=$(grep -Po '"-port=([0-9]+)' "$InstanceConfigDir/$Instance/main.conf" | grep -Po '[0-9]+$') local WebAdminPort=$(grep -Po '"-webadminport=([0-9]+)' "$InstanceConfigDir/$Instance/main.conf" | grep -Po '[0-9]+$') local QueryPort=$(grep -Po '"-queryport=([0-9]+)' "$InstanceConfigDir/$Instance/main.conf" | grep -Po '[0-9]+$') local GameType=$(grep -Po 'Game=([^\?]+)' "$InstanceConfigDir/$Instance/main.conf" | sed -r 's/Game=([^?]+)/\1/g' ) local GameLength=$(grep -Po 'GameLength=([0-9]+)' "$InstanceConfigDir/$Instance/main.conf" | grep -Po '[0-9]+$') local GameDifficulty=$(grep -Po 'Difficulty=([0-9]+)' "$InstanceConfigDir/$Instance/main.conf" | grep -Po '[0-9]+$') local DisplayGameType=${ModeArray[$GameType]} local DisplayGameLength=${WaveArray[$GameLength]} local DisplayDifficulty=${DiffArray[$GameDifficulty]} if [[ "$DisplayGameType" == 'Weekly' || \ "$DisplayGameType" == 'Endless' || \ "$DisplayGameType" == 'Versus' || \ "$DisplayGameType" == 'Objective' ]]; then DisplayGameLength='-' fi if [[ "$DisplayGameType" == 'Weekly' || \ "$DisplayGameType" == 'Versus' ]]; then DisplayDifficulty='-' fi echo -e "$Instance:$IsEnabled:$IsRuning:$GamePort:$QueryPort:$WebAdminPort:$DisplayGameType:$DisplayGameLength:$DisplayDifficulty:$Description" done } function show_status_implementation_full () # $*: [InstanceName[s]] { local InstanceList="" if [[ -z "$*" ]] ; then InstanceList=$(show_instances) else for Instance in $* do if instance_exists "$Instance"; then InstanceList+=" $Instance" fi done fi echo -e "INSTANCE:AUTORUN:STATE:P_GAME:P_QUERY:P_WEB:TYPE:LEN:DIFF:DESCRIPTION" show_status_implementation_body "$InstanceList" | sort -t : -k 4 } function show_status () # $*: [InstanceName[s]] { show_status_implementation_full $* | 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 () { local InstanceDir="$InstanceConfigDir/default" chmod 664 \ "$DefaultConfigDir/KFAI.ini" \ "$DefaultConfigDir/KFWeb.ini" \ "$DefaultConfigDir/LinuxServer-KFEngine.ini" \ "$DefaultConfigDir/LinuxServer-KFGame.ini" \ "$DefaultConfigDir/LinuxServer-KFInput.ini" \ "$DefaultConfigDir/LinuxServer-KFSystemSettings.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerEngine.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerGame.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerInput.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerSystemSettings.ini" dos2unix \ "$DefaultConfigDir/KFAI.ini" \ "$DefaultConfigDir/KFWeb.ini" \ "$DefaultConfigDir/LinuxServer-KFEngine.ini" \ "$DefaultConfigDir/LinuxServer-KFGame.ini" \ "$DefaultConfigDir/LinuxServer-KFInput.ini" \ "$DefaultConfigDir/LinuxServer-KFSystemSettings.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerEngine.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerGame.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerInput.ini" \ "$DefaultConfigDir/LinuxServer/LinuxServerSystemSettings.ini" 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/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" } 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 update_kf2 () { if [[ -n "$BetaPostfix" ]]; then local BetaArg="-beta preview" fi if ! server_exists; then # 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 sudo -u "$SteamUser" $AppBin & 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 multini -s "$DefaultConfigDir/KFWeb.ini" "IpDrv.WebServer" "bEnabled" "true" create_map_dirs fix_steamclient_so ln -s "$InstanceConfigDir" "$InstanceConfigLnk" make_default_instance echo "KF2 succesfully installed" elif updates_aviable; then # Update stop_instance steamcmd +login $SteamLogin +force_install_dir $InstallDir +app_update $AppServerNum $BetaArg +exit start_instance 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_body () # $1: WorkshopListFile { while read WorkshopID 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 < "$1" } function workshop_list_full () # $1: WorkshoplistFile { echo "WORKSHOP_ID NAME SIZE WORKSHOP_URL" workshop_list_body "$1" | sort -k 2 } function workshop_list () # $1: [--human-readable] { local WsList=$(mktemp) for Instance in $(show_instances) do local Config="$InstanceConfigDir/$Instance/LinuxServer-KFEngine.ini" if multini -gq "$Config" "OnlineSubsystemSteamworks.KFWorkshopSteamworks" "ServerSubscribedWorkshopItems"; then multini -g "$Config" "OnlineSubsystemSteamworks.KFWorkshopSteamworks" "ServerSubscribedWorkshopItems" >> "$WsList" fi done sort -u "$WsList" -o "$WsList" if [[ -n "$1" ]]; then workshop_list_full "$WsList" | column -t else cat "$WsList" fi rm -f "$WsList" } 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) for Instance in $(show_instances) do if instance_exists "$Instance"; then 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 else echo "Instance $Instance not exitst" 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_load () # $*: 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" if [[ -e "$MapRotate" ]]; then sed -i --follow-symlinks -r "/(ActiveMapCycle=|GameMapCycles=)/d" "$Config" sed -i --follow-symlinks "/\[KFGame\.KFGameInfo\]/ r $MapRotate" "$Config" else echo "$MapRotate not found - skip" fi else echo "Instance $Instance not exitst" 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_ext () # $1: BanlistFile { local Num=1 echo "NUM STEAM_ID3 STEAM_ID64 PROFILE_URL" while read ID3 do local ID64=$(steamID3_to_steamID64 "$ID3") local Url=$(curl "https://steamcommunity.com/profiles/$ID64" -s -L -I -o /dev/null -w '%{url_effective}') echo "$Num $ID3 $ID64 $Url" ((Num++)) done < "$1" } function ban_list () # $1: [--human-readable] { local BanList=$(mktemp) for Instance in $(show_instances) do local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini" if multini -gq "$Config" "Engine.AccessControl" "BannedIDs"; then multini -g "$Config" "Engine.AccessControl" "BannedIDs" | sed -r 's/.+A=([0-9]+),.+/\1/' >> "$BanList" fi done sort -u "$BanList" -o "$BanList" if [[ -n "$1" ]]; then ban_list_ext "$BanList" | column -t else cat "$BanList" fi rm -f "$BanList" } 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))" if ! multini -gq "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr"; then echo "Add ban $ID3 to $Instance" multini -a "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr" 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))" if multini -gq "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr"; then echo "Remove ban $ID3 from $Instance" multini -d "$Config" "Engine.AccessControl" "BannedIDs" "$BanStr" 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 | \ while read ID3 do ban_ID3 "$ID3" 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 $*; ;; -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 "-h" ; 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 "-h" ; 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 ;; * ) echo "Command not recognized: $1"; exit 1 ;; esac