#!/bin/bash
# panel-native VEIN entrypoint.
#
# Contract:
#   /game       — SteamCMD-installed VEIN Dedicated Server (app 2131400),
#                 populated by the panel's SteamCMD updater. If the server
#                 binary is missing, exit EX_CONFIG (78) with a helpful hint.
#   /game-saves — Vein/Saved/ tree (savegame + Config/LinuxServer/Game.ini).
#
# Stage 1 (root): chown volumes (SteamCMD wrote as root), wire the Steam
#   client SDK (~/.steam/sdk64/steamclient.so) so the Steamworks game-server
#   API can register on the master server, then drop to the `panel` user.
# Stage 2 (panel): stamp config_values env into the CORRECT UE sections of
#   Game.ini, redirect saves into the volume, then exec the server with the
#   panel-allocated ports.

set -euo pipefail
log() { printf '[panel-vein] %s\n' "$*"; }

GAME_DIR=/game
SAVE_DIR=/game-saves

# ----- stage 1: root-only bootstrap -----
if [[ "$(id -u)" = "0" ]]; then
  chown -R panel:panel /game /game-saves /home/panel 2>/dev/null || true

  log "dropping privileges to panel (UID 1000)"
  exec setpriv --reuid=panel --regid=panel --clear-groups \
       --inh-caps=-all --bounding-set=-all \
       env \
         HOME=/home/panel \
         PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
         STEAM_APP_ID="${STEAM_APP_ID:-1857950}" \
         SteamAppId="${STEAM_APP_ID:-1857950}" \
         GAME_PORT="${GAME_PORT:-7777}" \
         QUERY_PORT="${QUERY_PORT:-27015}" \
         RCON_PORT="${RCON_PORT:-7878}" \
         SERVER_NAME="${SERVER_NAME:-}" \
         SERVER_DESCRIPTION="${SERVER_DESCRIPTION:-}" \
         SERVER_PASSWORD="${SERVER_PASSWORD:-}" \
         MAX_PLAYERS="${MAX_PLAYERS:-}" \
         SERVER_PUBLIC="${SERVER_PUBLIC:-}" \
         HEARTBEAT_INTERVAL="${HEARTBEAT_INTERVAL:-}" \
         ENABLE_VAC="${ENABLE_VAC:-}" \
         SHOW_SCOREBOARD_BADGES="${SHOW_SCOREBOARD_BADGES:-}" \
         /entrypoint.sh "$@"
fi

# ----- stage 2: panel user -----
EXE="${GAME_DIR}/Vein/Binaries/Linux/VeinServer-Linux-Test"
# Fallbacks in case the depot ships a differently-suffixed binary
# (Shipping / DebugGame). The smoke test confirms the real name.
if [[ ! -x "${EXE}" ]]; then
  for alt in VeinServer-Linux-Shipping VeinServer-Linux-DebugGame VeinServer-Linux; do
    if [[ -x "${GAME_DIR}/Vein/Binaries/Linux/${alt}" ]]; then
      EXE="${GAME_DIR}/Vein/Binaries/Linux/${alt}"
      break
    fi
  done
fi

if [[ ! -x "${EXE}" ]]; then
  log "ERROR: VEIN server binary not found under ${GAME_DIR}/Vein/Binaries/Linux/."
  log "       Run 'Update' in the panel to install VEIN via SteamCMD (app 2131400)."
  exit 78
fi

# --- Steam client SDK: the Steamworks game-server API dlopen's steamclient.so
# from ~/.steam/sdk64/ (64-bit) and ~/.steam/sdk32/ (32-bit). VEIN's depot does
# NOT ship steamclient.so, and the panel's SteamCMD sidecar (which has it) is
# torn down after install — so we bake it into the image at /opt/steamsdk and
# symlink it here. Without this: "Steam Dedicated Server API failed to
# initialize" + endless "Failed to heartbeat" + the ready-regex never fires.
mkdir -p "${HOME}/.steam/sdk64" "${HOME}/.steam/sdk32"
for pair in "linux64:sdk64" "linux32:sdk32"; do
  src="/opt/steamsdk/${pair%%:*}/steamclient.so"
  dst="${HOME}/.steam/${pair##*:}/steamclient.so"
  if [[ -f "${src}" ]]; then
    ln -sf "${src}" "${dst}"
  fi
done
# Fallback: if the baked SDK is somehow absent, scan known runtime locations.
if [[ ! -e "${HOME}/.steam/sdk64/steamclient.so" ]]; then
  alt="$(find /opt /game "${HOME}/.steam" "${HOME}/.local/share/Steam" -name steamclient.so 2>/dev/null | grep -i 'linux64\|sdk64' | head -1)"
  [[ -z "${alt}" ]] && alt="$(find /opt /game "${HOME}/.steam" -name steamclient.so 2>/dev/null | head -1)"
  [[ -n "${alt}" ]] && ln -sf "${alt}" "${HOME}/.steam/sdk64/steamclient.so"
fi
if [[ -e "${HOME}/.steam/sdk64/steamclient.so" ]]; then
  log "steamclient.so wired → $(readlink -f "${HOME}/.steam/sdk64/steamclient.so")"
else
  log "WARNING: steamclient.so not found — Steam server registration will fail (server still LAN-joinable)."
fi
export LD_LIBRARY_PATH="${HOME}/.steam/sdk64:${GAME_DIR}/linux64:${GAME_DIR}/Vein/Binaries/Linux:${LD_LIBRARY_PATH:-}"

# --- Game.ini: section-aware env → INI stamping -------------------------
# VEIN's Game.ini is a multi-section UE config. Each tunable lives under a
# SPECIFIC [/Script/...] header — MaxPlayers under [/Script/Engine.GameSession],
# ServerName/Password under [/Script/Vein.VeinGameSession], etc. We stamp
# only NON-EMPTY env values, in place, into the right section — preserving
# operator hand-edits to other keys/sections via the Files tab.
CFG_DIR="${SAVE_DIR}/Vein/Saved/Config/LinuxServer"
INI="${CFG_DIR}/Game.ini"
mkdir -p "${CFG_DIR}"
[[ -f "${INI}" ]] || : > "${INI}"

# set_ini_kv "[Section]" KEY VALUE
#   Replace KEY=... if it already exists *within* its section; else insert it
#   immediately after the section header; create the section at EOF if absent.
#   Skips on empty VALUE. Section-aware: a same-named key in another section
#   is left untouched. Idempotent. Pure awk — no extra runtime deps.
set_ini_kv() {
  local section="$1" key="$2" value="$3"
  [[ -z "${value}" ]] && return 0
  local tmp="${INI}.tmp.$$"
  awk -v sec="${section}" -v key="${key}" -v val="${value}" '
    function isheader(l) { return (l ~ /^[ \t]*\[.*\][ \t]*$/) }
    function trim(l) { gsub(/^[ \t]+|[ \t]+$/, "", l); return l }
    BEGIN { cur=""; replaced=0; hdr_line=0 }
    {
      lines[NR]=$0
      t=trim($0)
      if (isheader(t)) cur=t
      section_of[NR]=cur
      if (cur==sec && hdr_line==0 && t==sec) hdr_line=NR
      # replace in-place when key matches inside the target section
      if (cur==sec && (t ~ ("^" key "[ \t]*=") )) {
        lines[NR]=key "=" val
        replaced=1
      }
    }
    END {
      if (replaced) {
        for (i=1;i<=NR;i++) print lines[i]
      } else if (hdr_line>0) {
        for (i=1;i<=NR;i++) {
          print lines[i]
          if (i==hdr_line) print key "=" val
        }
      } else {
        for (i=1;i<=NR;i++) print lines[i]
        if (NR>0 && trim(lines[NR])!="") print ""
        print sec
        print key "=" val
      }
    }
  ' "${INI}" > "${tmp}" && mv "${tmp}" "${INI}"
}

GS='[/Script/Engine.GameSession]'
VS='[/Script/Vein.VeinGameSession]'
OSS='[OnlineSubsystemSteam]'
SS='[/Script/Vein.ServerSettings]'

set_ini_kv "$GS"  "MaxPlayers"            "${MAX_PLAYERS}"
set_ini_kv "$VS"  "ServerName"            "${SERVER_NAME}"
set_ini_kv "$VS"  "ServerDescription"     "${SERVER_DESCRIPTION}"
set_ini_kv "$VS"  "Password"              "${SERVER_PASSWORD}"
set_ini_kv "$VS"  "bPublic"               "${SERVER_PUBLIC}"
set_ini_kv "$VS"  "HeartbeatInterval"     "${HEARTBEAT_INTERVAL}"
set_ini_kv "$OSS" "bVACEnabled"           "${ENABLE_VAC}"
set_ini_kv "$SS"  "GS_ShowScoreboardBadges" "${SHOW_SCOREBOARD_BADGES}"

# --- Saves: redirect Vein/Saved into the save volume via symlink so world
# data + configs survive container recreate and show in the Files tab.
mkdir -p "${SAVE_DIR}/Vein/Saved" "${SAVE_DIR}/logs"
if [[ -d "${GAME_DIR}/Vein/Saved" && ! -L "${GAME_DIR}/Vein/Saved" ]]; then
  if [[ -z "$(ls -A "${SAVE_DIR}/Vein/Saved" 2>/dev/null)" ]]; then
    cp -a "${GAME_DIR}/Vein/Saved/." "${SAVE_DIR}/Vein/Saved/" 2>/dev/null || true
  fi
  rm -rf "${GAME_DIR}/Vein/Saved"
fi
mkdir -p "${GAME_DIR}/Vein"
ln -sfn "${SAVE_DIR}/Vein/Saved" "${GAME_DIR}/Vein/Saved"

cd "${GAME_DIR}"
log "starting VEIN dedicated server"
log "  name:   ${SERVER_NAME:-<unset, Game.ini default>}"
log "  slots:  ${MAX_PLAYERS:-<unset, Game.ini default>}"
log "  ports:  game=${GAME_PORT} query=${QUERY_PORT}"
log "  binary: ${EXE}"

# Launch args mirror the AMP template:
#   Vein -Port=N -QueryPort=N -UseGameSaveDir -stdout -FullStdOutLogOutput
# -stdout + -FullStdOutLogOutput are LOAD-BEARING: they make the server log
# to stdout so the panel's Console live-tail (and the ready-regex) work, and
# so the stdio admin adapter's command echoes are visible.
exec stdbuf -oL -eL "${EXE}" Vein \
    -Port="${GAME_PORT}" \
    -QueryPort="${QUERY_PORT}" \
    -UseGameSaveDir \
    -stdout \
    -FullStdOutLogOutput
