#!/usr/bin/env bash # ============================================================================ # OpenFUT — FIFA 17 offline FUT backend orchestrator # # ./openfut-fut.sh start bring up the whole harness (arm host + all servers) # ./openfut-fut.sh stop shut the servers down # ./openfut-fut.sh restart stop + start # ./openfut-fut.sh status show what's up # # After `start`, launch FIFA 17 and select Ultimate Team. See RUNBOOK below (status). # Volatile host state (sysctls/iptables/cert) does NOT survive a reboot; `start` # re-arms everything, so just re-run it after booting. /etc/hosts persists. # ============================================================================ set -uo pipefail cd "$(dirname "$(readlink -f "$0")")" # tools/ CERT=redir_cert.pem; KEY=redir_key.pem # name script "port[,port...]" extra-env SERVERS=( "lsx lsx_responder_v2.py 4216 OPENFUT_LSX_EVENT_COUNT=100000" "blaze blaze_responder_v3b.py 42127,42130,42131 -" "roster roster_server.py 8081 -" "utas utas_server.py 8099 FUT_TRADING=1 FUT_PILESIZES=1 FUT_TRADEABLE=1 FUT_DISCARD_TABLE=1 FUT_DISCARD_SEND=1" "autopatch autopatch.py - -" # POW/EASFC — the "EA FC servers unreachable" layer. Harmless when idle: it just # binds 8094/8080 and nothing points at it unless FUT_POW=1 makes blaze serve the # FIFA_POW_URL redirect keys. See pow_server.py for the powdll evidence. "pow pow_server.py 8094,8080 -" ) c() { printf ' %s\n' "$*"; } up() { ss -tlnp 2>/dev/null | grep -q ":$1 "; } ensure_cert() { # The cert MUST carry the address the client dials in its SAN, or the roster # HTTPS handshake is rejected with fatal certificate_unknown and the FUT hub # fails to load (docs/FIFA17_FUT_SQUAD_UPDATE_TLS.md): the client dials the # roster/redirector by IP and that path validates the SAN against it. Default to # this host's primary LAN IP so a client on another machine works; # OPENFUT_ADVERTISE overrides. Reissue when absent OR when the current cert lacks # that IP, so this self-heals rather than serving a stale DNS-only cert. local adv ip_sans regen=0 adv="${OPENFUT_ADVERTISE:-$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')}" ip_sans="IP:127.0.0.1"; [ -n "$adv" ] && ip_sans="IP:$adv,IP:127.0.0.1" if [ ! -s "$CERT" ] || [ ! -s "$KEY" ]; then regen=1 elif [ -n "$adv" ] && ! openssl x509 -in "$CERT" -noout -ext subjectAltName 2>/dev/null | grep -qF "IP Address:$adv"; then regen=1 fi [ "$regen" = 0 ] && return 0 echo "[*] issuing self-signed TLS cert (SAN includes $ip_sans; redirector MITM; ProtoSSL verify is patched)" 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_sans" \ >/dev/null 2>&1 } armed() { [ "$(cat /proc/sys/kernel/yama/ptrace_scope 2>/dev/null)" = 0 ] \ && [ "$(cat /proc/sys/net/ipv4/conf/lo/route_localnet 2>/dev/null)" = 1 ] \ && grep -q '[[:space:]]easw\.easports\.com\b' /etc/hosts 2>/dev/null } ensure_armed() { if armed; then c "host already armed (ptrace_scope=0, route_localnet=1, /etc/hosts ok)"; return 0; fi echo "[*] arming host state (needs root — a password dialog will appear)" pkexec sh "$PWD/root_arm.sh" || { echo "!! root_arm failed. Run manually: pkexec sh $PWD/root_arm.sh"; return 1; } } start() { ensure_cert ensure_armed || exit 1 echo "[*] starting servers (detached)…" for s in "${SERVERS[@]}"; do read -r name script ports env <<<"$s" pkill -9 -f "$script" 2>/dev/null; : done sleep 1 for s in "${SERVERS[@]}"; do read -r name script ports env <<<"$s" local envprefix=""; [ "$env" != "-" ] && envprefix="env $env" : > "/tmp/${name}.log" 2>/dev/null || true setsid bash -c "exec $envprefix python3 -u $script" "/tmp/${name}.log" 2>&1 & disown done sleep 2 status } stop() { echo "[*] stopping servers…" for s in "${SERVERS[@]}"; do read -r name script _ <<<"$s"; pkill -9 -f "$script" 2>/dev/null; :; done sleep 1; c "servers stopped (host arm + /etc/hosts left intact)" } status() { echo "== OpenFUT FIFA17 FUT backend ==" armed && c "host: ARMED ✓" || c "host: NOT armed (run: pkexec sh $PWD/root_arm.sh)" for s in "${SERVERS[@]}"; do read -r name script ports env <<<"$s" if pgrep -f "$script" >/dev/null; then if [ "$ports" = "-" ]; then c "$name ✓ (running)" else local ok=1; IFS=',' read -ra ps <<<"$ports" for p in "${ps[@]}"; do up "$p" || ok=0; done [ $ok = 1 ] && c "$name ✓ ($ports)" || c "$name ⚠ running but a port is down ($ports)" fi else c "$name ✗ DOWN"; fi done echo "-- RUNBOOK --" c "1. (this must be done BEFORE launching FIFA — servers bind the ports the game needs)" c "2. Launch FIFA 17 fresh: ~/Desktop/launch-fifa17.sh (a FRESH launch avoids the live-DB error)" c "3. In-game: select Ultimate Team. At the 'security question', type ANY answer -> Continue -> OK." c "4. -> the FUT hub. Logs: /tmp/{lsx,blaze,roster,utas,autopatch}.log" } case "${1:-status}" in start) start ;; stop) stop ;; restart) stop; start ;; status) status ;; *) echo "usage: $0 {start|stop|restart|status}"; exit 1 ;; esac