#!/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|-" ) pids=() names=() for entry in "${SERVERS[@]}"; do IFS='|' read -r name script env <<<"$entry" envprefix=""; [ "$env" != "-" ] && envprefix="env $env" echo "[openfut] starting $name ($script)" # shellcheck disable=SC2086 $envprefix python3 -u "$script" & pids+=($!) names+=("$name") done # 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