#! /bin/bash set -uxe # Parse command-line flags # By default, skip updates (set to 1) SKIP_CSSDS_UPDATE=1 while [[ $# -gt 0 ]]; do case $1 in --update-cssds) SKIP_CSSDS_UPDATE=0 shift ;; *) echo "Unknown option: $1" echo "Usage: $0 [--update-cssds]" exit 1 ;; esac done CSTRIKE="$CSSDS/cstrike" update_cssds() { # Check if CSS DS is already populated with game files if [ -f "$CSSDS/srcds_run" ]; then # Game files exist, respect the skip flag if [ $SKIP_CSSDS_UPDATE -eq 1 ]; then echo "--------------------------------------------------------------" echo "Skipping CSS DS update (use --update-cssds to enable)" echo "--------------------------------------------------------------" return fi else echo "--------------------------------------------------------------" echo "CSS DS not populated, performing initial installation" echo "--------------------------------------------------------------" fi cd $STEAMCMD ./steamcmd.sh +runscript "$HOME/etc/update_cssds.txt" echo "--------------------------------------------------------------" echo "CSS DS is up to date" echo "--------------------------------------------------------------" } # TF2 DS no longer needed - running in 32-bit mode # update_tf2ds() function removed install_surf() { cd $CSTRIKE # is the metamod folder missing? if [ ! -d "$CSTRIKE/addons/metamod" ]; then LATESTMM=$(wget -qO- https://mms.alliedmods.net/mmsdrop/"${METAMOD_VERSION}"/mmsource-latest-linux) wget -qO- https://mms.alliedmods.net/mmsdrop/"${METAMOD_VERSION}"/"${LATESTMM}" | tar xvzf - -C "$CSTRIKE" echo "--------------------------------------------------------------" echo "Installed MetaMod $METAMOD_VERSION" echo "--------------------------------------------------------------" fi # Are we in a sourcemod container and is the sourcemod folder missing? if [ ! -d "$CSTRIKE/addons/sourcemod" ]; then LATESTSM=$(wget -qO- https://sm.alliedmods.net/smdrop/"${SOURCEMOD_VERSION}"/sourcemod-latest-linux) wget -qO- https://sm.alliedmods.net/smdrop/"${SOURCEMOD_VERSION}"/"${LATESTSM}" | tar xvzf - -C "$CSTRIKE" echo "--------------------------------------------------------------" echo "Installed SourceMod $SOURCEMOD_VERSION" echo "--------------------------------------------------------------" fi # Install sm-json (required for JSON zone loading - pure SourcePawn library) if [ ! -f "$CSTRIKE/addons/sourcemod/scripting/include/json.inc" ]; then cd /tmp wget "https://github.com/clugg/sm-json/archive/refs/tags/v5.0.1.zip" -O sm-json-v5.0.1.zip unzip -o sm-json-v5.0.1.zip cp -r sm-json-5.0.1/addons/* "$CSTRIKE/addons/" 2>/dev/null || true rm -rf sm-json-v5.0.1.zip sm-json-5.0.1/ echo "--------------------------------------------------------------" echo "Installed sm-json v5.0.1" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "sm-json already installed" echo "--------------------------------------------------------------" fi # Install sm_closestpos (required for ghost/replay functionality) if [ ! -f "$CSTRIKE/addons/sourcemod/extensions/closestpos.ext.so" ]; then cd /tmp wget "https://github.com/rtldg/sm_closestpos/releases/download/v1.1.1/sm_closestpos-sm1.10-ubuntu-20.04-b0a9b88.zip" unzip -o "sm_closestpos-sm1.10-ubuntu-20.04-b0a9b88.zip" cp -r addons/* "$CSTRIKE/addons/" 2>/dev/null || true rm -rf "sm_closestpos-sm1.10-ubuntu-20.04-b0a9b88.zip" addons/ echo "--------------------------------------------------------------" echo "Installed sm_closestpos v1.1.1" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "sm_closestpos already installed" echo "--------------------------------------------------------------" fi # Install sm-ripext (required for shavit-wrsh plugin) if [ ! -f "$CSTRIKE/addons/sourcemod/extensions/ripext.ext.so" ]; then cd /tmp wget "https://github.com/ErikMinekus/sm-ripext/releases/download/1.3.2/sm-ripext-1.3.2-linux.zip" unzip -o "sm-ripext-1.3.2-linux.zip" cp -r addons/* "$CSTRIKE/addons/" 2>/dev/null || true rm -rf "sm-ripext-1.3.2-linux.zip" addons/ echo "--------------------------------------------------------------" echo "Installed sm-ripext v1.3.2" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "sm-ripext already installed" echo "--------------------------------------------------------------" fi # Install Shavit Surf Timer (surf-specific fork) if [ ! -f "$CSTRIKE/addons/sourcemod/plugins/shavit-core.smx" ]; then cd /tmp wget "https://github.com/bhopppp/Shavit-Surf-Timer/releases/download/v1.0.6/Shavit-SurfTimer-v1.0.6.zip" unzip -o "Shavit-SurfTimer-v1.0.6.zip" # Copy addons, sounds, and materials cp -r addons/* "$CSTRIKE/addons/" 2>/dev/null || true cp -r sound/* "$CSTRIKE/sound/" 2>/dev/null || true cp -r materials/* "$CSTRIKE/materials/" 2>/dev/null || true rm -rf "Shavit-SurfTimer-v1.0.6.zip" addons/ sound/ materials/ echo "--------------------------------------------------------------" echo "Installed Shavit Surf Timer v1.0.6 (with sounds & materials)" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "Shavit Surf Timer already installed" echo "--------------------------------------------------------------" fi # Temporarily disabled - testing SQL zones instead of JSON # Install shavit-zones-json from original bhoptimer (missing in Surf Timer release) # if [ ! -f "$CSTRIKE/addons/sourcemod/plugins/shavit-zones-json.smx" ]; then # cd /tmp # wget "https://github.com/shavitush/bhoptimer/releases/download/v4.0.1/bhoptimer-v4.0.1.zip" # unzip -o "bhoptimer-v4.0.1.zip" "addons/sourcemod/plugins/shavit-zones-json.smx" # cp addons/sourcemod/plugins/shavit-zones-json.smx "$CSTRIKE/addons/sourcemod/plugins/" # rm -rf "bhoptimer-v4.0.1.zip" addons/ # echo "--------------------------------------------------------------" # echo "Installed shavit-zones-json.smx from bhoptimer v4.0.1" # echo "--------------------------------------------------------------" # else # echo "--------------------------------------------------------------" # echo "shavit-zones-json already installed" # echo "--------------------------------------------------------------" # fi # Disable zones-json plugin if it exists if [ -f "$CSTRIKE/addons/sourcemod/plugins/shavit-zones-json.smx" ]; then mkdir -p "$CSTRIKE/addons/sourcemod/plugins/disabled" mv "$CSTRIKE/addons/sourcemod/plugins/shavit-zones-json.smx" \ "$CSTRIKE/addons/sourcemod/plugins/disabled/" 2>/dev/null || true echo "--------------------------------------------------------------" echo "Disabled shavit-zones-json (using SQL zones instead)" echo "--------------------------------------------------------------" fi # Install MomSurfFix if [ ! -f "$CSTRIKE/addons/sourcemod/plugins/momsurffix2.smx" ]; then cd /tmp wget "https://github.com/GAMMACASE/MomSurfFix/releases/download/1.1.5/MomSurfFix2v1.1.5.zip" unzip -o "MomSurfFix2v1.1.5.zip" cp -r addons/* "$CSTRIKE/addons/" 2>/dev/null || true rm -rf "MomSurfFix2v1.1.5.zip" addons/ echo "--------------------------------------------------------------" echo "Installed MomSurfFix v1.1.5" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "MomSurfFix already installed" echo "--------------------------------------------------------------" fi # PushFix Definitive Edition - appears in KSF plugin list but fails on CSS # The DE version is CS:GO only and will fail with "g_SendTableCRC" error on CSS # Disabled by default - if KSF uses a different version, it's not publicly available # if [ ! -f "$CSTRIKE/addons/sourcemod/plugins/pushfix_de.smx" ]; then # cd /tmp # wget "https://github.com/GAMMACASE/PushFixDE/releases/download/1.0.0/pushfix_de_1.0.0.zip" # unzip -o "pushfix_de_1.0.0.zip" # cp -r addons/* "$CSTRIKE/addons/" 2>/dev/null || true # rm -rf "pushfix_de_1.0.0.zip" addons/ # fi # Install EventQueue Fix if [ ! -f "$CSTRIKE/addons/sourcemod/plugins/eventqueuefix.smx" ]; then cd /tmp wget "https://github.com/hermansimensen/eventqueue-fix/releases/download/1.3.2/eventqueuefix-1.3.2.zip" unzip -o "eventqueuefix-1.3.2.zip" cp -r addons/* "$CSTRIKE/addons/" 2>/dev/null || true rm -rf "eventqueuefix-1.3.2.zip" addons/ echo "--------------------------------------------------------------" echo "Installed EventQueue Fix v1.3.2" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "EventQueue Fix already installed" echo "--------------------------------------------------------------" fi # Install RNGFix if [ ! -f "$CSTRIKE/addons/sourcemod/plugins/rngfix.smx" ]; then cd /tmp wget "https://github.com/jason-e/rngfix/releases/download/v1.1.3/rngfix_1.1.3.zip" unzip -o "rngfix_1.1.3.zip" cp -r addons/* "$CSTRIKE/addons/" 2>/dev/null || true rm -rf "rngfix_1.1.3.zip" addons/ echo "--------------------------------------------------------------" echo "Installed RNGFix v1.1.3" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "RNGFix already installed" echo "--------------------------------------------------------------" fi } configure_shavit_zones() { cd $CSTRIKE # Check if already configured if [ -f "$CSTRIKE/addons/sourcemod/configs/shavit-mapfixes.cfg" ]; then echo "--------------------------------------------------------------" echo "Shavit zones already configured" echo "--------------------------------------------------------------" return fi echo "--------------------------------------------------------------" echo "Configuring Shavit to use JSON zones from surf-zones repo" echo "--------------------------------------------------------------" # Download surf-zones repository cd /tmp wget "https://github.com/wrldspawn/surf-zones/archive/refs/heads/main.zip" -O surf-zones.zip unzip -o surf-zones.zip # Copy stripper directory if [ -d "surf-zones-main/addons/stripper" ]; then mkdir -p "$CSTRIKE/addons/stripper" cp -r surf-zones-main/addons/stripper/* "$CSTRIKE/addons/stripper/" echo "Copied stripper configurations" fi # Copy mapfixes configuration if [ -f "surf-zones-main/addons/sourcemod/configs/shavit-mapfixes.cfg" ]; then mkdir -p "$CSTRIKE/addons/sourcemod/configs" cp surf-zones-main/addons/sourcemod/configs/shavit-mapfixes.cfg "$CSTRIKE/addons/sourcemod/configs/" echo "Copied shavit-mapfixes.cfg" fi # Copy styles configuration (optional - removes irrelevant styles, matches KSF behavior) if [ -f "surf-zones-main/addons/sourcemod/configs/shavit-styles.cfg" ]; then cp surf-zones-main/addons/sourcemod/configs/shavit-styles.cfg "$CSTRIKE/addons/sourcemod/configs/" echo "Copied shavit-styles.cfg" fi # Cleanup rm -rf surf-zones-main surf-zones.zip # Create cfg directory for plugin configs if it doesn't exist mkdir -p "$CSTRIKE/cfg/sourcemod" # Configure zones plugin to use SQL database cat > "$CSTRIKE/cfg/sourcemod/plugin.shavit-zones.cfg" <<'EOF' // Shavit Zones Plugin Configuration // Set to 1 to use SQL zones (default for Surf Timer fork) shavit_zones_usesql "1" EOF # JSON zones temporarily disabled for testing # cat > "$CSTRIKE/cfg/sourcemod/plugin.shavit-zones-json.cfg" <<'EOF' # // Shavit JSON Zones Configuration # // URL template for loading zone data - {map} will be replaced with map name # shavit_zones_json_url "https://wrldspawn.github.io/surf-zones/z/{map}.json" # EOF echo "--------------------------------------------------------------" echo "Configured Shavit to use SQL zones" echo "Use !zones in-game to create start/end zones for maps" echo "--------------------------------------------------------------" } configure_shavit_database() { cd $CSTRIKE echo "--------------------------------------------------------------" echo "Writing Shavit database configuration (SQLite)" echo "--------------------------------------------------------------" # Always recreate databases.cfg mkdir -p "$CSTRIKE/addons/sourcemod/configs" cat > "$CSTRIKE/addons/sourcemod/configs/databases.cfg" <<'EOF' "Databases" { "driver_default" "sqlite" // Shavit BHopTimer Database (using local SQLite) "shavit" { "driver" "sqlite" "database" "shavit-local" } // Default SourceMod database (local SQLite) "default" { "driver" "sqlite" "database" "sourcemod-local" } // Storage database (local SQLite) "storage-local" { "driver" "sqlite" "database" "sourcemod-local" } } EOF echo "--------------------------------------------------------------" echo "Configured Shavit to use local SQLite database" echo "Database: shavit-local.sq3" echo "--------------------------------------------------------------" } import_zone_data() { echo "--------------------------------------------------------------" echo "Importing zone data from Surf Timer repository" echo "--------------------------------------------------------------" local DB_FILE="$CSTRIKE/addons/sourcemod/data/sqlite/shavit-local.sq3" # Check if database file exists if [ ! -f "$DB_FILE" ]; then echo "Database file not created yet - skipping zone import" echo "Zones will be imported on next restart after Shavit creates tables" echo "--------------------------------------------------------------" return fi # Check if Shavit migrations have run (mapzones table must exist) local table_exists=$(sqlite3 "$DB_FILE" "SELECT name FROM sqlite_master WHERE type='table' AND name='mapzones';" 2>/dev/null) if [ -z "$table_exists" ]; then echo "Shavit has not created tables yet - skipping zone import" echo "Zones will be imported on next restart after Shavit runs migrations" echo "--------------------------------------------------------------" return fi # Check if zones already imported local zone_count=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM mapzones;" 2>/dev/null || echo "0") if [ "$zone_count" -gt "0" ]; then echo "Zone data already imported ($zone_count zones)" echo "--------------------------------------------------------------" return fi # Download and import surfzones.sql cd /tmp wget -q "https://raw.githubusercontent.com/bhopppp/Shavit-Surf-Timer/master/sql/surfzones.sql" -O surfzones.sql sqlite3 "$DB_FILE" < surfzones.sql 2>/dev/null local imported_count=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM mapzones;" 2>/dev/null || echo "unknown") echo "Imported $imported_count zones from surfzones.sql" rm -f surfzones.sql echo "--------------------------------------------------------------" } copy_64bit() { cp -a "$TF2DS/bin/linux64/libsteam_api.so" \ "$CSSDS/bin/linux64/" cp -a "$TF2DS/srcds_linux64" "$TF2DS/srcds_run_64" \ "$CSSDS/" } symlink_binaries() { cd "$CSSDS/bin/linux64" for file in *_srv.so; do echo "Symlinking \"$file\" to \"${file/_srv/}" ln -nfs "$file" "${file/_srv/}" done } steamclient_binary() { echo "Looking for steamclient.so and symlinking it..." # For 32-bit mode, look for 32-bit steamclient (linux32 or default) local _steamclient32bit=$(find "$HOME" -type f -name 'steamclient.so' | grep -E "linux32|steamcmd/linux32" | head -n 1) if [ ! -n "$_steamclient32bit" ]; then # Fallback to any steamclient.so if linux32 not found _steamclient32bit=$(find "$HOME" -type f -name 'steamclient.so' | head -n 1) fi if [ ! -n "$_steamclient32bit" ]; then echo "Could not locate steamclient.so binary. Exiting..." exit 1 fi mkdir -p "$HOME/.steam/sdk32/" # srcds_run looks for steamclient.so in this directory ln -nfs "$_steamclient32bit" "$HOME/.steam/sdk32/steamclient.so" ln -nfs "$_steamclient32bit" "$CSSDS/steamclient.so" } configure_admins() { echo "--------------------------------------------------------------" echo "Configuring SourceMod admins" echo "--------------------------------------------------------------" # Create admins_simple.ini with header cat > "$CSTRIKE/addons/sourcemod/configs/admins_simple.ini" <<'EOF' // // SourceMod Admins Simple Configuration // Auto-generated by run.sh from SRCDS_ADMINS environment variable // // Format: "STEAM_ID" "immunity:flags" // optional comment // Flags: z = root (full access) // EOF # Add admins from environment variable (comma-separated Steam IDs) if [ -n "$SRCDS_ADMINS" ]; then IFS=',' read -ra ADMIN_IDS <<< "$SRCDS_ADMINS" for steamid in "${ADMIN_IDS[@]}"; do # Trim whitespace and quotes steamid=$(echo "$steamid" | xargs | tr -d '"' | tr -d "'") if [ -n "$steamid" ]; then echo "\"$steamid\" \"99:z\" // Root admin" >> "$CSTRIKE/addons/sourcemod/configs/admins_simple.ini" echo "Added admin: $steamid" fi done echo "--------------------------------------------------------------" echo "Configured ${#ADMIN_IDS[@]} admin(s)" echo "--------------------------------------------------------------" else echo "--------------------------------------------------------------" echo "No admins configured (set SRCDS_ADMINS env var)" echo "--------------------------------------------------------------" fi } cfg() { cd cp etc/cfg/server.cfg cssds/cstrike/cfg/ cp etc/cfg/mapcycle.txt cssds/cstrike/cfg/ sed -i "s|sv_downloadurl .*|sv_downloadurl \"${FASTDL_URL:-}/cstrike/\"|" "$CSTRIKE/cfg/server.cfg" } populate_maps() { local BZ2_DIR="/fastdl/maps" local MAPS_DIR="$CSTRIKE/maps" local MAPCYCLE="$CSTRIKE/cfg/mapcycle.txt" echo "--------------------------------------------------------------" echo "Populating maps from compressed archive" echo "--------------------------------------------------------------" if [ ! -f "$MAPCYCLE" ]; then echo "No mapcycle.txt found, skipping map population" echo "--------------------------------------------------------------" return fi mkdir -p "$MAPS_DIR" local total=0 skipped=0 extracted=0 missing=0 while IFS= read -r line || [ -n "$line" ]; do # skip empty lines and comments line=$(echo "$line" | sed 's|//.*||' | xargs) [ -z "$line" ] && continue total=$((total + 1)) local bz2="$BZ2_DIR/${line}.bsp.bz2" local bsp="$MAPS_DIR/${line}.bsp" if [ -f "$bsp" ]; then skipped=$((skipped + 1)) continue fi if [ ! -f "$bz2" ]; then missing=$((missing + 1)) echo " [!] ${line}: not in fastdl volume" continue fi bunzip2 -c "$bz2" > "$bsp" extracted=$((extracted + 1)) done < "$MAPCYCLE" echo "Maps: $total in mapcycle, $extracted extracted, $skipped already present, $missing not available" echo "--------------------------------------------------------------" } run_cssds() { echo "ready to run" cd "$CSSDS" ./srcds_run -game cstrike \ -port "${SRCDS_PORT}" \ +maxplayers "${SRCDS_MAXPLAYERS}" \ +map "${SRCDS_STARTMAP}" \ -ip "${SRCDS_IP}" } main() { update_cssds # TF2 DS not needed - running in 32-bit mode steamclient_binary install_surf configure_shavit_zones configure_shavit_database configure_admins cfg # Import zone data after server config is ready # Note: Database might not exist yet on first run # Zones will be available after server starts and creates tables import_zone_data populate_maps run_cssds } main # sm plugins # "vipmodel.smx" vipmodel.smx # "Reserved Slots" (1.7.2) by AlliedModders LLC # "ColoredText" (2.0) by unt0uch4bl3 for KSF use ONLY # "Basic Ban Commands" (1.7.2) by AlliedModders LLC # "Server Hop" (0.8.1) by [GRAVE] rig0r # "PlayerTransmit" (1.0) by unt0uch # "strippermodels.smx" strippermodels.smx # "Whitelist" (1.0.0) by unt0uch4bl3 # "SMAC Anti-Speedhack" (0.8.0.9) by GoD-Tony # "Player Commands" (1.4.0-dev) by AlliedModders LLC # "sm_super_cmds_unt0uch.smx" sm_super_cmds_unt0uch.smx # "Basic Chat" (1.7.2) by AlliedModders LLC # "Admin File Reader" (1.7.2) by AlliedModders LLC # "Name Change Punisher" (1.1) by Powerlord # "Map Nominations" (1.7.2) by AlliedModders LLC # "Stipper" (1.0) by unt0uch4bl3 # "No Block" (1.0.0.0) by sslice # "Basic Votes" (1.7.2) by AlliedModders LLC # "CMD Logger" (1.0.0) by unt0uch4bl3 # "Momentum surf fix '2" (1.1.5) by GAMMA CASE # "Fun Commands" (1.4.0-dev) by AlliedModders LLC # "Custom Votes" (0.5.6) by chundo # "SurfTimer" (7.8.0) by unt0uch4bl3 # "Anti-Flood" (1.7.2) by AlliedModders LLC # "EventQueue fix" (1.3.3) by carnifex # "antibhop.smx" antibhop.smx # "Basic Info Triggers" (1.4.2) by AlliedModders LLC # "MapChooser" (1.7.2) by AlliedModders LLC # "Admin Help" (1.4.0-dev) by AlliedModders LLC # "Basic Respawn Plugin" (0.05) by Spyder # "Rock The Vote" (1.7.2) by AlliedModders LLC # "UrlOpen" (1.0) by unt0uch4bl3 # "Message Admin" by evolv # "RandomCycle" (1.7.2) by AlliedModders LLC # "PushFix - Definitive Edition" (1.1.0) by Original idea xutaxkamay | Implementation GAMMACASE # "No-Jump Boost Fix" (1.0.0) by rio # "Admin Menu" (1.7.2) by AlliedModders LLC # "Client Preferences" (1.7.2) by AlliedModders LLC # "Map configs" (1.1.1) by Berni # "Nextmap" (1.5.1) by AlliedModders LLC # "draw.smx" draw.smx # "Player Trails" (3.0.2) by Twisted|Panda # "Fun Votes" (1.7.2) by AlliedModders LLC # "RNGFix" (1.1.3) by rio # "SQL Admins (Threaded)" (1.7.2) by AlliedModders LLC # "Surftimer Announcer" (1.0) by unt0uch4bl3 # "Basic Commands" (1.7.2) by AlliedModders LLC # "DEMO Uploader" (1.0) by unt0uch4bl3 # "Players Votes" (1.5.0) by The Resident, pZv! # "SMAC Command Monitor" (0.8.0.9) by GoD-Tony, psychonic, Kigen # "Dissolve" (1.0.0.2) by L. Duke # "SourceMod Anti-Cheat" (0.8.0.9) by GoD-Tony, psychonic