#!/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 source /etc/kf2-srv/kf2-srv.conf ScriptFullname=$(readlink -e "$0") ScriptName=$(echo "$ScriptFullname" | awk -F '/' '{print $NF;}') ScriptVersion="0.6.0" AppServerNum="232130" AppClientNum="232090" StrangeConstUID="17825793" InstallDir="/usr/games/kf2-srv" ActiveBranch="$InstallDir/activebranch.txt" AppBin="$InstallDir/Binaries/Win64/KFGameSteamServer.bin.x86_64" DefaultConfigDir="$InstallDir/KFGame/Config" DownloadMapsDir="$InstallDir/Binaries/Win64/steamapps/workshop/content/$AppClientNum" CacheMapsDir="$InstallDir/KFGame/Cache" InstanceConfigDir="/etc/kf2-srv/instances" InstanceConfigLnk="$DefaultConfigDir/instances" MainConfigTemplate="/etc/kf2-srv/main.conf.template" 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" 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 " -ml, --map-list отображает список карт из SteamWorkshop" echo " -ms, --map-sync синхронизирует списки сторонних карт в" echo " конфигурационных файлах с имеющимися файлами" echo " сторонних карт; синхронизирует списки карт из" echo " SteamWorkshop между всеми экземплярами серверов" echo " -ma, --map-add [MAP_ID] добавляет карту из SteamWorkshop по URL или" echo " WorkshopID" echo " -md, --map-del [MAP_ID] удаляет карту SteamWorkshop по URL или WorkshopID" 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 $(groups "$USER" | grep -Fo 'wheel') ]]; then sudo "$ScriptFullname" $* 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@$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 [[ -n $(ls "$InstallDir") ]]; then return 0 else return 1 fi } function updates_aviable () { # TODO: implementation return 0 } 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/$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 "$BranchName" ]]; then local BetaArg="-beta $BranchName" fi stop_instance steamcmd +login $SteamLogin +force_install_dir $InstallDir +app_update $AppServerNum $BetaArg validate +exit echo "$BranchName" > "$ActiveBranch" 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/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 update_kf2 () { if [[ -n "$BranchName" ]]; then local BetaArg="-beta $BranchName" fi if ! server_exists; then # First install echo "$BranchName" > "$ActiveBranch" 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 set_ini_permissions "$DefaultConfigDir" install -d -g "$SteamUser" -o "$SteamUser" -m 775 "$CacheMapsDir" fix_steamclient_so ln -s "$InstanceConfigDir" "$InstanceConfigLnk" make_default_instance echo "KF2 succesfully installed" elif updates_aviable; then # Update if [[ "$BranchName" == $(cat "$ActiveBranch") ]]; then stop_instance steamcmd +login $SteamLogin +force_install_dir $InstallDir +app_update $AppServerNum $BetaArg +exit start_instance else validate fi 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 map_list_body () # $1: MaplistFile { while read WorkshopID do local Cache="$CacheMapsDir/$WorkshopID" local Downl="$DownloadMapsDir/$WorkshopID" local Url="https://steamcommunity.com/sharedfiles/filedetails/?id=$WorkshopID" if [[ -d "$Cache" ]]; then local MapName=$(find "$Cache" -type f -name '*.kfm' -printf '%f\n' | head -n 1) else local MapName="" fi if [[ -n "$MapName" ]]; then local MapSize=$(du -sch "$Downl" "$Cache" | tail -n 1 | grep -Po '^[^\s]+') else local MapSize="-"; MapName="-" fi echo "$MapName $MapSize $WorkshopID $Url" done < "$1" } function map_list_full () # $1: MaplistFile { echo "NAME SIZE WORKSHOP_ID WORKSHOP_URL" map_list_body "$1" | sort -k 1 } function map_list () # $1: [--human-readable] { local MapList=$(mktemp) for Instance in $(show_instances) do local Config="$InstanceConfigDir/$Instance/LinuxServer-KFEngine.ini" grep -F 'ServerSubscribedWorkshopItems=' "$Config" | sed -r 's/^.+=([0-9]+)$/\1/' >> "$MapList" done sort -u "$MapList" -o "$MapList" if [[ -n "$1" ]]; then map_list_full "$MapList" | column -t else cat "$MapList" fi rm -f "$MapList" } 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 map_add () # $*: WorkshopID[s] { for Instance in $(show_instances) do local Config="$InstanceConfigDir/$Instance/LinuxServer-KFEngine.ini" if ! grep -qF '[OnlineSubsystemSteamworks.KFWorkshopSteamworks]' "$Config"; then echo -e ' [OnlineSubsystemSteamworks.KFWorkshopSteamworks]' >> "$Config" fi if ! grep -qF 'DownloadManagers=OnlineSubsystemSteamworks.SteamWorkshopDownload' "$Config"; then sed -i --follow-symlinks -r '0,/DownloadManagers=/ s/^(DownloadManagers=.+)$/DownloadManagers=OnlineSubsystemSteamworks.SteamWorkshopDownload \1/' "$Config" fi for Map in $* do local WorkshopID=$(any_to_workshopID "$Map") local MapStr="ServerSubscribedWorkshopItems=$WorkshopID" if ! grep -qF "$MapStr" "$Config"; then echo "Add map $WorkshopID to $Instance" sed -i --follow-symlinks "/^\[OnlineSubsystemSteamworks\.KFWorkshopSteamworks\]/a $MapStr" "$Config" fi done done } function map_del () # $*: WorkshopID[s] { # TODO: Remove lines from LinuxServer-KFGame.ini # I need "crudini" with duplicate keys support >_< for Map in $* do local WorkshopID=$(any_to_workshopID "$Map") local Cache="$CacheMapsDir/$WorkshopID" local Downl="$DownloadMapsDir/$WorkshopID" echo -e "Clear cache: $Cache $Downl" rm -rf "$Cache" "$Downl" for Instance in $(show_instances) do local Config="$InstanceConfigDir/$Instance/LinuxServer-KFEngine.ini" local MapStr="ServerSubscribedWorkshopItems=$WorkshopID" if grep -qF "$MapStr" "$Config"; then echo "Remove map $WorkshopID from $Instance" sed -i --follow-symlinks "/$MapStr/d" "$Config" fi done done } function map_sync () { map_add $(map_list) for Instance in $(show_instances) do if instance_exists "$Instance"; then local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini" for MapFile in $(find "$CacheMapsDir" -type f -name '*.kfm' -printf "%f\n") do MapName=$(echo "$MapFile" | sed -r 's|.kfm$||g') if [[ ! -f "$Config" ]]; then echo "$Config does not exist!" elif ! grep -qP "MapName=$MapName[ ]*$" "$Config"; then echo "Adding $MapName to $Instance." echo -e " [$MapName KFMapSummary] MapName=$MapName bPlayableInSurvival=True bPlayableInWeekly=True bPlayableInVsSurvival=True bPlayableInEndless=True bPlayableInObjective=False" >> "$Config" 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" grep -P 'BannedIDs=' "$Config" | sed -r 's/.+A=([0-9]+),.+/\1/' >> "$BanList" 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="BannedIDs=(Uid=(A=$ID3,B=$StrangeConstUID))" if ! grep -qF "$BanStr" "$Config"; then echo "Add ban $ID3 to $Instance" sed -i --follow-symlinks "/^\[Engine\.AccessControl\]/a $BanStr" "$Config" fi done } function unban_ID3 () # $1: ID3 { ID3="$1" for Instance in $(show_instances) do local Config="$InstanceConfigDir/$Instance/LinuxServer-KFGame.ini" local BanStr="BannedIDs=(Uid=(A=$ID3,B=$StrangeConstUID))" if grep -qF "$BanStr" "$Config"; then echo "Remove ban $ID3 from $Instance" sed -i --follow-symlinks "/$BanStr/d" "$Config" 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 [[ $# -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 ;; -ml|--map-list ) if [[ "$EUID" -eq 0 ]]; then shift; map_list "-h" ; else run_as_root $*; fi ;; -ms|--map-sync ) if [[ "$EUID" -eq 0 ]]; then shift; map_sync ; else run_as_root $*; fi ;; -ma|--map-add ) if [[ "$EUID" -eq 0 ]]; then shift; map_add $*; else run_as_root $*; fi ;; -md|--map-del ) if [[ "$EUID" -eq 0 ]]; then shift; map_del $*; 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