#!/usr/bin/env bash
# ============================================================================
# OpenFUT FIFA-17 FUT backend — in-CONTAINER orchestrator.
#
# Runs the 5 network responders that the game dials. Unlike the host-based
# openfut-fut.sh, this does NO host arming (no pkexec / iptables / /etc/hosts /
# ptrace) — those are client-side concerns handled on the game machine (105).
# autopatch.py is NOT run here: it patches the FIFA17.exe process memory and must
# run on the box the game runs on.
#
# Address behaviour is driven by two env vars (see each responder):
#   OPENFUT_BIND       bind address for every listener   (container: 0.0.0.0)
#   OPENFUT_ADVERTISE  address handed to the client for the next hop
#                      (the server's LAN IP, e.g. 203.0.113.10)
# ============================================================================
set -uo pipefail
cd "$(dirname "$(readlink -f "$0")")/tools"

BIND="${OPENFUT_BIND:-0.0.0.0}"
ADV="${OPENFUT_ADVERTISE:?OPENFUT_ADVERTISE must be set to the server LAN IP (e.g. 203.0.113.10)}"
export OPENFUT_BIND="$BIND"
export OPENFUT_ADVERTISE="$ADV"
# POW keys advertised by blaze must also point at the server, not loopback.
export POW_HOST="${POW_HOST:-$ADV:8094}"
export POW_CONTENT_HOST="${POW_CONTENT_HOST:-$ADV:8080}"
export POW_ADDR="${POW_ADDR:-$BIND:8094}"
export POW_CONTENT_ADDR="${POW_CONTENT_ADDR:-$BIND:8080}"

echo "[openfut] bind=$BIND advertise=$ADV"

# The TLS cert every responder serves must carry the ADVERTISED IP in its SAN.
# The client dials the roster (:8081) and redirector by that IP, and that path
# validates the cert's SAN against the dialed address — it is NOT covered by the
# two client-side ProtoSSL gates autopatch patches, so a cert lacking IP:$ADV is
# rejected with fatal certificate_unknown and the FUT hub fails with "An error
# occurred downloading the FUT Squad Update" (docs/FIFA17_FUT_SQUAD_UPDATE_TLS.md).
# The advertised IP is unknown at image-build time, so reconcile it here: reissue
# only when the current cert does not already carry it, so a restart reuses the
# same cert (no per-start fingerprint churn) and this self-heals if $ADV changes.
CERT=redir_cert.pem KEY=redir_key.pem
if ! openssl x509 -in "$CERT" -noout -ext subjectAltName 2>/dev/null | grep -qF "IP Address:$ADV"; then
    echo "[openfut] reissuing TLS cert with SAN IP:$ADV (was missing it)"
    openssl req -x509 -newkey rsa:2048 -nodes -keyout "$KEY" -out "$CERT" -days 3650 \
        -subj "/CN=winter15.gosredirector.ea.com" \
        -addext "subjectAltName=DNS:winter15.gosredirector.ea.com,DNS:*.gosredirector.ea.com,DNS:*.ea.com,IP:$ADV,IP:127.0.0.1" \
        >/dev/null 2>&1 \
        && echo "[openfut] cert SAN now: $(openssl x509 -in "$CERT" -noout -ext subjectAltName 2>/dev/null | tail -1 | tr -s ' ')" \
        || { echo "[openfut] FATAL: could not reissue TLS cert" >&2; exit 1; }
fi

# name   script                  extra-env
declare -a SERVERS=(
  "lsx|lsx_responder_v2.py|OPENFUT_LSX_EVENT_COUNT=100000"
  "blaze|blaze_responder_v3b.py|-"
  "roster|roster_server.py|-"
  "utas|utas_server.py|FUT_TRADING=1 FUT_PILESIZES=1 FUT_TRADEABLE=1 FUT_DISCARD_TABLE=1 FUT_DISCARD_SEND=1"
  "pow|pow_server.py|-"
)

# ── Component selection ──────────────────────────────────────────────────────
# OPENFUT_SERVERS picks which Python responders run (space- or comma-separated).
# This container is the SERVER side (.120). It serves ONLY components that have
# NOT been migrated to a Rust host — as each moves to Rust (which runs OUTSIDE
# Docker during migration), drop its name so the two never serve the same role.
#   blaze   Blaze redirector + main + nucleus (bundled)   :42127 :42130 :42131
#   roster  FUT roster-update XML                         :8081
#   utas    FUT/UTAS RS4 API                              :8099
#           (the Rust utas-host currently reverse-proxies its non-/club routes
#            back here, so keep this enabled until UTAS is fully migrated)
#   pow     POW / EASFC                                   :8094 (+ content :8080)
#   lsx     Origin LSX bootstrap                          :4216
#           CLIENT-SIDE — LSX runs on the game machine (.105) with autopatch,
#           NOT on the server. Excluded by default; enable ONLY for an
#           all-on-one-box dev setup where client and server share a host.
OPENFUT_SERVERS="${OPENFUT_SERVERS:-blaze roster utas pow}"
want=" ${OPENFUT_SERVERS//,/ } "
known=" lsx blaze roster utas pow "
for w in $want; do
  case "$known" in
    *" $w "*) ;;
    *) echo "[openfut] unknown component '$w' in OPENFUT_SERVERS (valid: lsx blaze roster utas pow)" >&2; exit 2 ;;
  esac
done
echo "[openfut] servers=$OPENFUT_SERVERS"

pids=()
names=()
for entry in "${SERVERS[@]}"; do
  IFS='|' read -r name script env <<<"$entry"
  case "$want" in
    *" $name "*) ;;
    *) echo "[openfut] skipping $name (not in OPENFUT_SERVERS)"; continue ;;
  esac
  envprefix=""; [ "$env" != "-" ] && envprefix="env $env"
  echo "[openfut] starting $name ($script)"
  # shellcheck disable=SC2086
  $envprefix python3 -u "$script" &
  pids+=($!)
  names+=("$name")
done

if [ "${#pids[@]}" -eq 0 ]; then
  echo "[openfut] OPENFUT_SERVERS selected no components; nothing to run" >&2
  exit 2
fi

# Propagate SIGTERM/SIGINT to children so `docker stop` is clean.
term() {
  echo "[openfut] shutting down…"
  for p in "${pids[@]}"; do kill "$p" 2>/dev/null || true; done
  wait
  exit 0
}
trap term TERM INT

# If ANY responder dies, take the whole container down so the failure is visible
# (they all bind ports the game needs — a partial stack is a broken stack).
while true; do
  for i in "${!pids[@]}"; do
    if ! kill -0 "${pids[$i]}" 2>/dev/null; then
      echo "[openfut] responder ${names[$i]} (pid ${pids[$i]}) exited - bringing container down"
      term
    fi
  done
  sleep 2
done
