scraketools

administrative aid for hosting multiple Killing Floor 1 dedicated servers
git clone git://git.boymiasma.net/scraketools
Log | Files | Refs | README

commit 689edbdb1b14c52258557715642545583b3f626e
parent 039cf19830aa3932b57084b6eefd19309ff16ea3
Author: giygas <giygas@boymiasma.net>
Date:   Tue, 28 Oct 2025 00:31:33 +0100

Default configuration fix, apply steam library fix, interface changes, add READMEREADME

Diffstat:
MREADME.md | 176++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mscrake.ini | 23++++++++++++-----------
Mscrake.sh | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
3 files changed, 257 insertions(+), 31 deletions(-)

diff --git a/README.md b/README.md @@ -1,2 +1,174 @@ -under construction -docs soon +# This software is experimental! +As is currently, scraketools suffers from several limitations that require manual intervention. +Numerous helper scripts and helpful behaviours have yet to be added before the software reaches +a usable Beta state. Nevertheless, the scraketools is programmed as a user-facing application, +and contains safeguards against accidental data deletion. Still; never run scraketools +as root, and consider creating a backup of your server installation before migrating it to a +scrake instance. + +As of 2025-10-27, boymiasma.net's Killing Floor 1 servers are administrated through scrake.sh + +# Intro +scraketools is a growing suite of bourne-again shell scripts designed to ease the burden of administrating multiple +Killing Floor 1 dedicated servers in tandem. + +Amongst its features are +- Creation and management of individual server instances derived from a single server base install +- Per-instance map/mutator installation +- Global and per-instance base game and mutator configuration through a single file, `scrake.ini` +- Automatic installation of the base server software through `steamcmd` +- Automatic application of the linux server discoverablity prefix + +# Installation +``` +git clone git://git.boymiasma.net/scraketools +chmod +x scraketools/scrake.sh +``` + +Furthermore, scraketools requires `steamcmd` to exist within your path. + +# Quickstart (fresh server installation) +Never copy-paste commands. This is but a reference. Keep reading. +``` +# install the base server software. You will be prompted to log in by steamcmd. +# NOTE: if you plan to migrate an existing server, skip this step and consult the "Existing server migration" header below +scraketools/scrake.sh init <steam_username> + +# NOTE: because of a limitation in Killing Floor's default config generation, the rest of this +# quickstart guide will leave you without map or kick-voting. If you need these functionalities, +# enable the web server for the base server installation (~/.steam/SteamApps/common/Killing Floor Dedicated Server - Linux/System/KillingFloor.ini by default on ArchLinux) +# and set an admin password, and enable WebAdmin. Run the server as `./ucc-bin server KF-Wyre.rom?game=KFmod.KFGameType -nohomedir`, and enable Map Voting through the interface. +# You might also want to define your maplist here, as there is no way to accomplish this through standard scraketools configuration (yet). +# Save the configuration, shut down the server, proceed as normal, and heed the warning under 'Configuration' down below. + +# create three server instances; which can then be found in ~/Scrake/Servers +scraketools/scrake.sh create Normal Hard Suicidal + +# (Optionally) Install maps/mutators (see below) +for INSTANCE in Normal Hard Suicidal; do + scraketools/scrake.sh mod $INSTANCE mutator add <mut1> <mut2> ... + scraketools/scrake.sh mod $INSTANCE map add <mut1> <mut2> ... +done + +# Edit the default configuration in Scrake/scrake.ini (see below) +cp scraketools/scrake.ini Scrake/scrake.ini + +# Apply scraketools/scrake.ini to each instance +scraketools/scrake.sh sync Normal Hard Suicidal + +# Run servers +for INSTANCE in Normal Hard Suicidal; do + scraketools/scrake.sh run $INSTANCE KF-Wyre.rom & +done +``` +From here, you may make changes to Scrake/scrake.ini and re-sync them to each instance. +Note that the server must not be running while you run the `sync` command; it has been known +to overwrite the active configuration file while up, undoing the changes applied by `sync`. + +# Configuration +What follows is an incomplete copy of the example configuration file bundled with scraketools +``` +[Scrake] +Engine.AccessControl/AdminPassword=Admin123 ++Engine.GameInfo/bVACSecured=True +-Engine.GameEngine/ServerActors=IpDrv.MasterServerUplink + +[Scrake.Normal] +URL/Port=7707 +IpDrv.UdpGamespyQuery/OldQueryPortNumber=7717 +Engine.GameInfo/GameDifficulty=2 + +[Scrake.Hard] +URL/Port=7807 +IpDrv.UdpGamespyQuery/OldQueryPortNumber=7817 +Engine.GameInfo/GameDifficulty=4 + +[Scrake.Suicidal] +URL/Port=7907 +IpDrv.UdpGamespyQuery/OldQueryPortNumber=7917 +Engine.GameInfo/GameDifficulty=5 +``` +scrake.ini defines overrides applied to an individual instance's System/*.ini files. +Each override takes the form: +[modifier]Section/Key=Value +Where modifier may be one of '+', '-', or unspecified. +scrake.sh alters the value of any matching configuration key in the specified section to +the new value specified in scrake.ini. The overrides are applied to each System/*.ini file. +If the override specifies a section/key that does not appear in the configuration file, it is silently ignored. + +The '+' modifier instructs scrake.sh to add a new key-value pair to the specified section +The '-' modifier makes scrake.sh remove a key-value pair from the specified action if the given value matches exactly. + +Overrides from the [Scrake] section are applied to any and all instances supplied to the `sync` command. +Overrides from any section that takes the form [Scrake.<instance>] are only applied to the corresponding instance. + +LIMITATION: the order of the sections in scrake.ini matters; the last encountered override will be applied last. +LIMITATION: there MUST be a blank line between the any given final key-value pair and next section. + +CONFIGURATION NOTE (if you followed the extra steps in the Quickstart): + By default, some values like Engine.GameInfo/bVACSecured and Engine.GameInfo/GameDifficulty do not appear in the default + configuration, and so they must possess the '+' modifier in scrake.ini in order to be added. + However, if you ran the webadmin console on the base installation and pressed 'Save', Killing Floor will have + added these values to KillingFloor.ini itself, which leads to the '+' modifier creating duplicate values, + most likely ignoring your overrides. You must instead override these values with no modifier, so that + scrake.sh alters the default, instead of adding a duplicate. I am working on fixing this. + +# Migrating from an existing setup (Untested) +scrake.sh's behaviour may be modified by changing various configuration variables. Amongst these is SCRAKE_BASE, which +points to the default server installation path out of the box (~/.steam/SteamApps/common/Killing Floor Dedicated Server - Linux) + +After changing SCRAKE_BASE to the root directory of your existing server installation, scrake.sh will create new instances +using the specified installation as the base. + +In testing, scrake.sh has never corrupted or altered the base server installation (besides alterations made by `init`, which, +if you're consulting this section, you wouldn't execute). Regardless, consider making a copy of your server installation while +testing out this experimental software. + +# Adding or removing maps and mutators +Add mutators or maps to Scrake/Mutators/<Mutname> or Scrake/Maps/<Mapname>. The contents of <Mutname> and <Mapname> should correspond +to the directory structure of the base installation. For example: + Scrake/Maps/ + Affairs/ + Maps/ + KF-Affairs.rom + Arcade-Hardcore/ + Maps/ + KF-Arcade-Hardcore.rom +or: + Scrake/Mutators/ + CustomServerDetails/ + System/ + CustomServerDetails.ini + CustomServerDetails.u +etc. + +TODO: write a script to 'install' maps and mutators to the corresponding directories with the correct directory structure. + +Now the maps and mutators can be installed by executing: +scraketools/scrake.sh mod Normal mutator add CustomServerDetails +scraketools/scrake.sh mod Normal map add Affairs Arcade-Hardcore + +# Finally... how does it work? +scrake.sh creates instances by re-creating the directory structure of the base server software, and then symbolically linking +every single file over. The links to configuration files in System are renamed from *.ini to *.ini.defaults. The `sync` command +then applies the configuration overrides from scrake.ini to each *.ini.defaults file, writing them to the 'real' configuration file, +KillingFloor.ini . This allows for several server instances to run in tandem from only a single base server installation. + +One might imagine that, through the help of a tool such as `sshfs`, one could run an arbitrary number of Killing Floor servers +spread across a variety of machines on a DMZ, all from one installation. + +# Next on the chopping block +- There needs to be a more robust solution for applying overrides from scrake.ini to the individual server configurations. Currently, + defining map lists or custom sections is just straight up out of the question. +- I'm currently tearing my hair out about whether to ship a sane default KillingFloor.ini with scraketools, or whether to continue + trying to programmatically wrangle the server binary to just cough up the "full" configuration during setup. I'm not quite + decided on that yet. +- It should not be difficult at all to write a script to install maps/mutators to Scrake and 'guess' the correct directory structure + from the contents of the zip/rar/7z/whatever. + +# Features beyond the horizon +- Automatic synchronisation of banlists +- systemd unit file generation +- FastDL manager +- Status watchdog & log analyser + diff --git a/scrake.ini b/scrake.ini @@ -1,40 +1,41 @@ [Scrake] -Engine.AccessControl/AdminPassword=AdminPassword +Engine.AccessControl/AdminPassword= Engine.AccessControl/GamePassword= -Engine.GameInfo/bVACSecured=True -Engine.GameReplicationInfo/ServerName=My Server ++Engine.GameInfo/bVACSecured=True Engine.GameReplicationInfo/ShortName= Engine.GameReplicationInfo/AdminName= Engine.GameReplicationInfo/AdminEmail= Engine.GameReplicationInfo/MessageOfTheDay= IpDrv.MasterServerUplink/DoUplink=True -IpDrv.MasterServerUplink/UplinkToGamespy=False +IpDrv.MasterServerUplink/UplinkToGamespy=True IpDrv.MasterServerUplink/SendStats=True IpDrv.MasterServerUplink/ServerBehindNAT=False IpDrv.MasterServerUplink/DoLANBroadcast=False IpDrv.HTTPDownload/RedirectToURL= --Engine.GameEngine/ServerActors=IpDrv.MasterServerUplink -+Engine.GameEngine/ServerActors=CustomServerDetails.CSDMasterServerUplink -+Engine.GameEngine/ServerActors=MutKillMessage.MutKillMessage -+Engine.GameEngine/ServerActors=ReloadOptionsMut.ReloadOptionsMut [Scrake.Normal] URL/Port=7707 IpDrv.UdpGamespyQuery/OldQueryPortNumber=7717 UWeb.WebServer/bEnabled=True UWeb.WebServer/ListenPort=8075 -Engine.GameInfo/GameDifficulty=2 ++Engine.GameInfo/GameDifficulty=2 +Engine.GameReplicationInfo/ServerName=My Killing Floor Server (Normal) +KFmod.KFGameType/KFGameLength=2 [Scrake.Hard] URL/Port=7807 IpDrv.UdpGamespyQuery/OldQueryPortNumber=7817 UWeb.WebServer/bEnabled=True UWeb.WebServer/ListenPort=8175 -Engine.GameInfo/GameDifficulty=4 ++Engine.GameInfo/GameDifficulty=4 +Engine.GameReplicationInfo/ServerName=My Killing Floor Server (Hard) +KFmod.KFGameType/KFGameLength=2 [Scrake.Suicidal] URL/Port=7907 IpDrv.UdpGamespyQuery/OldQueryPortNumber=7917 UWeb.WebServer/bEnabled=True UWeb.WebServer/ListenPort=8275 -Engine.GameInfo/GameDifficulty=5 ++Engine.GameInfo/GameDifficulty=5 +Engine.GameReplicationInfo/ServerName=My Killing Floor Server (Suicidal) +KFmod.KFGameType/KFGameLength=2 diff --git a/scrake.sh b/scrake.sh @@ -77,13 +77,14 @@ init_base() { done rm "$FIRSTRUN_FIFO" notify "init_base: first run successful" + + tr -d '\r' <"$SCRAKE_BASE/System/Default.ini" | sed -n '/^\[KFmod.KFGameType\]$/,/^ *$/p' >> "$SCRAKE_BASE/System/KillingFloor.ini" } -# Usage: skeleton_link -i src dst +# Usage: skeleton_link src dst skeleton_link() { - typeset SRC DST INI_REPLACE DIR DIRNAME - if [ "$1" == "-i" ]; then INI_REPLACE=1; shift; fi + typeset SRC DST DIR DIRNAME if [ $# -ne 2 ]; then fatal "usage: skeleton_link source destination"; fi SRC="$(realpath "$1")" if [ ! -d "$SRC" ]; then fatal "skeleton_link: no such directory '$SRC'"; fi @@ -95,9 +96,6 @@ skeleton_link() [ -d "$DST/$DIRNAME" ] || mkdir $VERBOSE "$DST/$DIRNAME" find "$DIR" -type f -exec ln $VERBOSE -s {} "$DST/$DIRNAME" \; done - if [ ! -z "$INI_REPLACE" ]; then - find "$DST" -type l -name '*.ini' -exec mv $VERBOSE {} {}.defaults \; - fi } # Usage: skeleton_unlink src dst @@ -115,20 +113,72 @@ skeleton_unlink() done } +# Usage: fix_steam dst +fix_steam() { + typeset SRC DST FILE + if [ $# -ne 1 ]; then fatal "usage: fix_steam dst"; fi + SRC="$(realpath "$STEAM_ROOT/steamcmd/linux32")" + if [ ! -d "$SRC" ]; then warn "fix_steam: could not find '$SRC'. Fix will fail."; fi + DST="$(realpath "$1")" + if [ ! -d "$DST" ]; then fatal "fix_steam: no such directory '$DST'"; exit 1; fi + + notify "applying steam library fix to restore master server list discoverability." + for FILE in steamclient.so libtier0_s.so libvstdlib_s.so; do + if [ ! -f "$SRC/$FILE" ]; then warn "library not found: '$SRC/FILE'. Skipping."; continue; fi + ln $VERBOSE -fs "$SRC/$FILE" "$DST/$FILE" + done +} + +# Usage: ini_rename dst +ini_rename() { + typeset DST + if [ $# -ne 1 ]; then fatal "usage: ini_rename dst"; exit 1; fi + DST="$(realpath "$1")" + if [ ! -d "$DST" ]; then fatal "ini_rename: no such directory '$DST'"; exit 1; fi + + notify "changing the .ini extension on instance symlinks to .ini.defaults" + find "$DST" -type l -name '*.ini' -exec mv $VERBOSE {} {}.defaults \; +} + +# Usage: meta_remove dst +meta_remove() { + typeset DST FILE + if [ $# -ne 1 ]; then fatal "usage: meta_remove dst"; exit 1; fi + DST="$(realpath "$1")" + if [ ! -d "$DST" ]; then fatal "meta_remove: no such directory '$DST'"; exit 1; fi -# Usage: new_instance name [name [...]] + notify "removing meta maps" + for FILE in KFintro.rom KF-Menu.rom Entry.rom; do + rm $VERBOSE "$DST/$FILE" + done +} + +usage_new_instance() { echo "new_instance [-DLM] name [name [...]]"; } new_instance() { - typeset SRC DST - if [ $# -lt 1 ]; then fatal "usage: new_instance name [name [...]]" >&2; fi + typeset SRC DST FILE OPT OPTIND STEAMFIX INIRENAME METAREMOVE + if [ $# -lt 1 ]; then fatal "$(usage_new_instance)" >&2; exit 1; fi + + STEAMFIX=1; INIRENAME=1; METAREMOVE=1; + while getopts "DLM" OPT; do + case "$OPT" in + D) INIRENAME=0;; + L) STEAMFIX=0;; + M) METAREMOVE=0;; + *) fatal "$(usage_new_instance)"; exit 1;; + esac + done + shift $((OPTIND-1)) while [ $# -ge 1 ]; do notify "creating instance '$1'" SRC="$(realpath "$SCRAKE_BASE")" DST="$(realpath "$SCRAKE_ROOT/Servers/$1")" - if [ -d "$DST" ]; then notify "new_instance: instance '$1' already exists. Skipping..."; fi - - mkdir $VERBOSE "$DST" && skeleton_link -i "$SRC" "$DST" + if [ -d "$DST" ]; then notify "new_instance: instance '$1' already exists."; + else mkdir $VERBOSE "$DST" && skeleton_link "$SRC" "$DST"; fi + if [ $STEAMFIX -eq 1 ]; then fix_steam "$DST/System"; fi + if [ $INIRENAME -eq 1 ]; then ini_rename "$DST/System"; fi + if [ $METAREMOVE -eq 1 ]; then meta_remove "$DST/Maps"; fi shift done } @@ -159,7 +209,8 @@ mod_instance() { continue; fi notify "mod_mutator: installing $WHAT '$1' to $TARGET" - skeleton_link -i "$SRC/$1" "$DST" + skeleton_link "$SRC/$1" "$DST" + ini_rename "$DST/System" shift done;; remove) @@ -224,11 +275,10 @@ usage_scrake() { cat <<EOF usage: $PROGNAME [global-options] init [-N] steam_username [alt_install_dir] - $PROGNAME [global-options] create instance_name ... + $PROGNAME [global-options] create [-DLM] instance_name ... $PROGNAME [global-options] mod instance_name mutator add|remove mutname ... $PROGNAME [global-options] mod instance_name map add|remove mapname ... - $PROGNAME [global-options] sync cfg instance_name ... - $PROGNAME [global-options] sync bans instance_name ... + $PROGNAME [global-options] sync instance_name ... $PROGNAME [global-options] run instance_name map_name global options: -h show this help message @@ -238,7 +288,11 @@ global options: -i alternative configuration override file (overrides SCRAKE_CFG) -v verbose flag init options: - -N do not perform the automatic server first-run + -N do not perform the automatic server first-run (not recommended) +create options: + -D do not rename .ini symlinks to .defaults (not recommended) + -L do not apply master server list discoverability fix + -M do not remove meta-maps (KFintro.rom, Entry.rom, KF-Menu.rom) EOF } while getopts "b:i:Nhr:s:v" OPT; do @@ -246,7 +300,6 @@ while getopts "b:i:Nhr:s:v" OPT; do b) SCRAKE_BASE="${OPTARG}";; h) usage_scrake; exit;; i) SCRAKE_CFG="${OPTARG}";; - N) NO_FIRSTRUN=1;; r) SCRAKE_ROOT="${OPTARG}";; s) STEAM_ROOT="${OPTARG}";; v) VERBOSE="-v";;