#!/bin/bash
# panel-native Empyrion entrypoint.
#
# Contract:
#   /game       — SteamCMD-installed Windows build of Empyrion (volume,
#                 populated by the panel's SteamCMD updater sidecar).
#                 If missing, exit with EX_CONFIG and a helpful message.
#   /game-saves — dedicated.yaml + Saves/ tree (volume).
#
# Runs as root for its opening stage (fix up ownership after the SteamCMD
# sidecar wrote as root, nuke any stale X lock from a previous crash),
# then drops to the `panel` user for Wine + Xvfb + the game. Wine refuses
# to run as root, so the drop is load-bearing.
#
# On every start we verify the binary is there, start Xvfb, boot a minimal
# Wine prefix if needed, then exec EmpyrionDedicated.exe under Wine with
# stdout/stderr line-buffered so docker logs → agent → panel console
# delivers live output.

set -euo pipefail

log() { printf '[panel-empyrion] %s\n' "$*"; }

# ----- stage 1: root-only bootstrap (runs once per container start) -----
if [[ "$(id -u)" = "0" ]]; then
  # SteamCMD writes as root in its sidecar; chown once so the panel user
  # (UID 1000) can read/write the game dir without fiddling with perms.
  chown -R panel:panel /game /game-saves /home/panel 2>/dev/null || true

  # Nuke any stale Xvfb state from a previous boot. The container's /tmp
  # persists across restarts (same writable layer), so /tmp/.X99-lock plus
  # /tmp/.X11-unix/X99 left by a SIGKILL'd Xvfb make the next start die
  # with "_XSERVTransMakeAllCOTSServerListeners: server already running".
  # Single rm wasn't catching every variant (verified live 2026-04-29);
  # nuking the whole dir + re-creating with the right mode is bulletproof.
  pkill -9 Xvfb 2>/dev/null || true
  rm -rf /tmp/.X11-unix /tmp/.X99-lock 2>/dev/null || true
  mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix && chown root:root /tmp/.X11-unix 2>/dev/null || true

  # Empyrion writes build-stamped logs to Z:\Logs\<buildnum> (= /Logs/...
  # because Wine maps Z:\ to /). Without this symlink panel gets an
  # UnauthorizedAccessException on first boot. Pointing it into the save
  # volume keeps logs persistent + visible in the Files tab.
  mkdir -p /game-saves/EmpyrionLogs
  chown -R panel:panel /game-saves/EmpyrionLogs
  if [[ ! -e /Logs || -L /Logs ]]; then
    ln -sfn /game-saves/EmpyrionLogs /Logs
  fi

  # Re-exec as `panel` user. setpriv comes with util-linux in debian-slim.
  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 \
         WINEPREFIX=/home/panel/.wine \
         WINEDEBUG=-all \
         SRV_PORT="${SRV_PORT:-30000}" \
         EAC_PORT="${EAC_PORT:-30003}" \
         CSW_PORT="${CSW_PORT:-30004}" \
         EAH_API_PORT="${EAH_API_PORT:-30007}" \
         SCENARIO="${SCENARIO:-}" \
         PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
       /entrypoint.sh "$@"
fi

GAME_DIR=/game
SAVE_DIR=/game-saves
DEDI_DIR="${GAME_DIR}/DedicatedServer"
DEDI_EXE="${DEDI_DIR}/EmpyrionDedicated.exe"
CONFIG="${SAVE_DIR}/dedicated.yaml"
# Empyrion ships its default dedicated.yaml at the game root.
SHIPPED_CONFIG="${GAME_DIR}/dedicated.yaml"

# --- sanity: Windows build installed? ---
if [[ ! -f "${DEDI_EXE}" ]]; then
  log "ERROR: ${DEDI_EXE} not found."
  log "       Game files haven't been downloaded yet."
  log "       Run 'Update' on this server in the panel — that launches a"
  log "       SteamCMD sidecar which installs Empyrion (Windows build) into"
  log "       this volume via +@sSteamCmdForcePlatformType windows."
  exit 78   # EX_CONFIG
fi

# --- dedicated.yaml: copy shipped default on first run ---
if [[ ! -s "${CONFIG}" && -s "${SHIPPED_CONFIG}" ]]; then
  log "no panel config found — seeding ${CONFIG} from ${SHIPPED_CONFIG}"
  cp -f "${SHIPPED_CONFIG}" "${CONFIG}"
fi
if [[ ! -s "${CONFIG}" ]]; then
  log "WARN: no dedicated.yaml found in game install. Empyrion will create"
  log "      one with defaults on first boot. Edit via the Files tab."
fi

# Symlink so the binary finds the config at its expected relative path.
# Empyrion reads dedicated.yaml from the game root (same dir as the .exe's
# -dedicated argument resolves).
ln -sf "${CONFIG}" "${GAME_DIR}/dedicated.yaml"

# --- patch dedicated.yaml ports from env on every boot ---
# Empyrion reads Srv_Port / EAC_Port / Tel_Port from dedicated.yaml. Without
# this, multiple instances on the same agent all bind 30000-30004 and only
# one is reachable. SRV_PORT comes from the panel allocator via the
# manifest's port→env mapping (`env: SRV_PORT` in module.yaml). yaml is
# whitespace-sensitive but these keys are a flat top-level "Key: NNN" so
# sed is safe.
if [[ -s "${CONFIG}" ]]; then
  log "patching dedicated.yaml (Srv_Port=${SRV_PORT}, EAC_Port=${EAC_PORT})"
  sed -i -E \
    -e "s|^([[:space:]]*Srv_Port:[[:space:]]*)[0-9]+|\1${SRV_PORT}|" \
    -e "s|^([[:space:]]*EAC_Port:[[:space:]]*)[0-9]+|\1${EAC_PORT}|" \
    "${CONFIG}"
fi

# --- scenario selection ---
# SCENARIO env names a folder under /game/Content/Scenarios/. The empyrion-
# shipped default is "Default Random". Reforged Eden / Reforged Eden 2 /
# Project Eden need to be present in /game/Content/Scenarios/<name>/ first
# (drop via the panel's Files tab, or future workshop-subscribe feature).
# This block keeps dedicated.yaml's CustomScenario in sync with the env.
SCENARIO="${SCENARIO:-}"
if [[ -n "${SCENARIO}" && -s "${CONFIG}" ]]; then
  if [[ ! -d "${GAME_DIR}/Content/Scenarios/${SCENARIO}" ]]; then
    log "WARN: scenario folder '${SCENARIO}' not found at ${GAME_DIR}/Content/Scenarios/${SCENARIO}"
    log "      The panel UI Files tab can drop scenario contents there. For"
    log "      Reforged Eden 2 (Workshop ID 3041847672), unzip into:"
    log "        /game/Content/Scenarios/Reforged Eden 2/"
    log "      Booting with Default for now."
  else
    log "selecting scenario: ${SCENARIO}"
    if grep -qE '^[[:space:]]*CustomScenario:' "${CONFIG}"; then
      sed -i -E "s|^([[:space:]]*CustomScenario:[[:space:]]*).*|\1${SCENARIO}|" "${CONFIG}"
    else
      # Insert under GameConfig: at the top, ahead of dedicated game block.
      printf '\nGameConfig:\n  CustomScenario: %s\n' "${SCENARIO}" >> "${CONFIG}"
    fi
  fi
fi

# --- stage EPM mod (TCP relay for Eleon Mod API) on first boot ---
# Pre-staged at /opt/panel-epm in the image. Copy into the live game
# Content/Mods/EPM/ if it isn't there yet. This makes the panel-empyrion-
# bridge sidecar work out-of-the-box: every new empyrion instance comes
# with EPM listening on EAH_API_PORT.
EPM_SRC=/opt/panel-epm
EPM_DST="${GAME_DIR}/Content/Mods/EPM"
if [[ -d "${EPM_SRC}" && ! -f "${EPM_DST}/EmpyrionNetworkRelayMod.dll" ]]; then
  log "staging EPM mod into ${EPM_DST}"
  mkdir -p "${EPM_DST}"
  cp -a "${EPM_SRC}/." "${EPM_DST}/"
  # If a panel-allocated EAH_API_PORT is set, patch EPM's Settings.yaml
  # so its TCP listener binds the right port for this instance.
  if [[ -n "${EAH_API_PORT:-}" && -f "${EPM_DST}/Settings.yaml" ]]; then
    sed -i -E "s|^GameServerApiPort:[[:space:]]*[0-9]+|GameServerApiPort: ${EAH_API_PORT}|" \
      "${EPM_DST}/Settings.yaml"
  fi
fi
# Re-patch port even if EPM was already staged, in case panel reallocated.
if [[ -f "${EPM_DST}/Settings.yaml" && -n "${EAH_API_PORT:-}" ]]; then
  sed -i -E "s|^GameServerApiPort:[[:space:]]*[0-9]+|GameServerApiPort: ${EAH_API_PORT}|" \
    "${EPM_DST}/Settings.yaml"
fi

# --- prep saves dir (Empyrion writes under /game/Saves by default) ---
# Redirect Saves into the save volume via a symlink so steam validate
# can't clobber world data.
mkdir -p "${SAVE_DIR}/Saves"
if [[ -d "${GAME_DIR}/Saves" && ! -L "${GAME_DIR}/Saves" ]]; then
  # Steam shipped an empty Saves dir; replace with symlink.
  rm -rf "${GAME_DIR}/Saves"
fi
ln -sfn "${SAVE_DIR}/Saves" "${GAME_DIR}/Saves"

# --- Wine + Xvfb via xvfb-run (auto-allocates a free display) ---
#
# We previously hand-started Xvfb on :99. That broke once the host had
# its OWN Xvfb on :99 — the empyrion container uses network_mode: host,
# which shares the network namespace, and X server abstract Unix sockets
# (`@/tmp/.X11-unix/X99`) are namespaced by network ns. Result: container
# Xvfb fails with "_XSERVTransMakeAllCOTSServerListeners: server already
# running" and Wine then dies "Authorization required, but no auth
# protocol specified" → Unity exits "Failed to create batch mode window:
# Success." (verified 2026-04-29).
#
# `xvfb-run -a` finds a free display number, starts Xvfb with proper
# xauth set up, exports DISPLAY to its child, and cleans up on exit.
# Bulletproof against host's existing Xvfb instances.

# --- Wine prefix bootstrap (first boot only). xvfb-run handles the X
#     server for the actual run; for prefix init we don't strictly need
#     X but wineboot complains less when one is available. ---
if [[ ! -d "${WINEPREFIX}" || ! -f "${WINEPREFIX}/system.reg" ]]; then
  log "initializing Wine prefix at ${WINEPREFIX}"
  xvfb-run -a wineboot --init 2>&1 | sed 's/^/  /' || true
  wineserver -w || true
fi

# --- go ---
cd "${GAME_DIR}"
log "starting Empyrion Dedicated Server"
log "  game dir: ${GAME_DIR}"
log "  save dir: ${SAVE_DIR}"
log "  config:   ${CONFIG}"
log "  binary:   ${DEDI_EXE}"

# Shutdown handler: forward SIGTERM (xvfb-run forwards to wine; wine's
# Unity hook persists state on quit).
trap 'log "SIGTERM received; stopping server"; wineserver -k 2>/dev/null || true; exit 0' TERM INT

# -batchmode / -nographics make Unity skip the renderer; -logFile - pipes
# the Unity log to our stdout so the panel console sees everything.
# `xvfb-run -a` picks a free display, sets up xauth, and exports DISPLAY.
# `--server-args="-screen 0 1024x768x24"` matches our previous Xvfb args.
exec stdbuf -oL -eL xvfb-run -a --server-args="-screen 0 1024x768x24" \
    wine "${DEDI_EXE}" \
    -batchmode \
    -nographics \
    -logFile - \
    -dedicated dedicated.yaml \
    -startDedicated
