Files
OpenFUT/setup.sh
T
funman300 acd9efed95 fix: scope iptables NAT rule to loopback only to avoid breaking internet
The broad OUTPUT 443→8443 redirect caught all HTTPS traffic system-wide.
Scoping to -d 127.0.0.1 means only EA domains (resolved to 127.0.0.1 via
/etc/hosts) are redirected; all real internet traffic is unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 09:13:00 -07:00

428 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
# OpenFUT Setup Script
# Builds, configures, and starts the OpenFUT offline FUT emulator for FIFA 23.
# Run as a regular user; the script will sudo only for hosts/cert/iptables steps.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CORE_DIR="$SCRIPT_DIR/openfut-core"
BRIDGE_DIR="$SCRIPT_DIR/openfut-bridge"
CORE_PORT="${CORE_PORT:-8080}"
BRIDGE_PORT="${BRIDGE_PORT:-8443}" # internal port; iptables forwards 443 → this
TLS_ENABLED="${TLS_ENABLED:-true}"
CORE_PID_FILE="/tmp/openfut-core.pid"
BRIDGE_PID_FILE="/tmp/openfut-bridge.pid"
CORE_LOG="/tmp/openfut-core.log"
BRIDGE_LOG="/tmp/openfut-bridge.log"
# EA FUT domains to redirect.
# Run './setup.sh watch-domains' while starting FIFA 23 to discover additional domains.
EA_HOSTS=(
# Core FUT API
"fut.ea.com"
"utas.mob.v4.fut.ea.com"
# EA account / auth
"accounts.ea.com"
"signin.ea.com"
"gateway.ea.com"
# Origin / EA Desktop API
"api1.origin.com"
"api2.origin.com"
"api3.origin.com"
# Telemetry / assets (can safely be silenced)
"eas.ea.com"
"eaassets-a.akamaihd.net"
"eaassets-b.akamaihd.net"
)
HOSTS_MARKER="# OpenFUT — managed block (do not edit manually)"
# ── Colours ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
info() { echo -e "${CYAN}[INFO]${RESET} $*"; }
ok() { echo -e "${GREEN}[OK]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
error() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
heading() { echo -e "\n${BOLD}$*${RESET}"; }
# ── Helpers ────────────────────────────────────────────────────────────────────
require_cmd() {
command -v "$1" &>/dev/null || { error "Required command not found: $1"; exit 1; }
}
is_running() {
local pid_file="$1"
[[ -f "$pid_file" ]] && kill -0 "$(cat "$pid_file")" 2>/dev/null
}
stop_service() {
local pid_file="$1" name="$2"
if is_running "$pid_file"; then
kill "$(cat "$pid_file")" 2>/dev/null && ok "Stopped $name"
rm -f "$pid_file"
else
warn "$name is not running"
fi
}
# ── Commands ───────────────────────────────────────────────────────────────────
cmd_build() {
heading "Building OpenFUT Core and Bridge…"
require_cmd cargo
info "Building Core (release)…"
cargo build --release --manifest-path "$CORE_DIR/Cargo.toml" \
2>&1 | grep -E "^(error|warning\[)" || true
ok "Core built"
info "Building Bridge (release)…"
cargo build --release --manifest-path "$BRIDGE_DIR/Cargo.toml" \
2>&1 | grep -E "^(error|warning\[)" || true
ok "Bridge built"
}
cmd_start() {
heading "Starting OpenFUT services…"
# ── Core ──
if is_running "$CORE_PID_FILE"; then
warn "Core is already running (PID $(cat "$CORE_PID_FILE"))"
else
# Run from CORE_DIR so the default sqlite://openfut.db relative path works
(
cd "$CORE_DIR"
LISTEN_ADDR="127.0.0.1:$CORE_PORT" \
DATA_DIR="$CORE_DIR/data" \
"$CORE_DIR/target/release/openfut-core" \
>"$CORE_LOG" 2>&1
) &
echo $! > "$CORE_PID_FILE"
ok "Core started (PID $!) → http://127.0.0.1:$CORE_PORT log: $CORE_LOG"
fi
# Give Core a moment to bind before Bridge starts
sleep 1
# ── Bridge ──
if is_running "$BRIDGE_PID_FILE"; then
warn "Bridge is already running (PID $(cat "$BRIDGE_PID_FILE"))"
else
BRIDGE_LISTEN_ADDR="0.0.0.0:$BRIDGE_PORT" \
CORE_URL="http://127.0.0.1:$CORE_PORT" \
TLS_ENABLED="$TLS_ENABLED" \
PLACEHOLDER_MODE="true" \
CAPTURES_DIR="$BRIDGE_DIR/captures" \
"$BRIDGE_DIR/target/release/openfut-bridge" \
>"$BRIDGE_LOG" 2>&1 &
echo $! > "$BRIDGE_PID_FILE"
local scheme="http"; [[ "$TLS_ENABLED" == "true" ]] && scheme="https"
ok "Bridge started (PID $!) → ${scheme}://0.0.0.0:$BRIDGE_PORT log: $BRIDGE_LOG"
fi
echo
info "Dashboard: http://127.0.0.1:$CORE_PORT/_bridge/dashboard (via bridge: https://127.0.0.1:$BRIDGE_PORT/_bridge/dashboard)"
info "Admin: https://127.0.0.1:$BRIDGE_PORT/_bridge/admin"
info "Setup guide: https://127.0.0.1:$BRIDGE_PORT/_bridge/guide"
}
cmd_stop() {
heading "Stopping OpenFUT services…"
stop_service "$BRIDGE_PID_FILE" "Bridge"
stop_service "$CORE_PID_FILE" "Core"
cmd_unhosts
cmd_unnat
}
cmd_status() {
heading "OpenFUT service status"
if is_running "$CORE_PID_FILE"; then
ok "Core is RUNNING (PID $(cat "$CORE_PID_FILE"))"
else
warn "Core is STOPPED"
fi
if is_running "$BRIDGE_PID_FILE"; then
ok "Bridge is RUNNING (PID $(cat "$BRIDGE_PID_FILE"))"
else
warn "Bridge is STOPPED"
fi
echo
if grep -q "$HOSTS_MARKER" /etc/hosts 2>/dev/null; then
ok "Hosts entries are IN PLACE"
else
warn "Hosts entries are NOT configured (run: ./setup.sh hosts)"
fi
if sudo iptables -t nat -L OUTPUT -n -v 2>/dev/null | grep -q "redir ports $BRIDGE_PORT"; then
ok "NAT redirect 127.0.0.1:443→$BRIDGE_PORT is ACTIVE"
else
warn "NAT redirect is NOT active (run: ./setup.sh nat)"
fi
}
cmd_logs() {
heading "Tailing logs (Ctrl-C to stop)…"
tail -f "$CORE_LOG" "$BRIDGE_LOG" 2>/dev/null || warn "No log files yet — start the services first."
}
cmd_hosts() {
heading "Adding EA FUT hosts entries…"
if grep -q "$HOSTS_MARKER" /etc/hosts 2>/dev/null; then
ok "Hosts entries already present — skipping"
return
fi
local block
block="\n$HOSTS_MARKER\n"
for host in "${EA_HOSTS[@]}"; do
block+="127.0.0.1 $host\n"
done
block+="# end OpenFUT\n"
echo -e "$block" | sudo tee -a /etc/hosts > /dev/null
ok "Added to /etc/hosts:"
for host in "${EA_HOSTS[@]}"; do
echo " 127.0.0.1 $host"
done
info "Flush DNS if needed: sudo resolvectl flush-caches (or: sudo systemd-resolve --flush-caches)"
}
cmd_unhosts() {
if grep -q "$HOSTS_MARKER" /etc/hosts 2>/dev/null; then
sudo sed -i "/^$HOSTS_MARKER/,/^# end OpenFUT/d" /etc/hosts
ok "Removed OpenFUT hosts entries"
fi
}
cmd_nat() {
heading "Setting up iptables redirect 443 → $BRIDGE_PORT"
if sudo iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "redir ports $BRIDGE_PORT"; then
ok "NAT rule already active"
return
fi
# Scope to 127.0.0.1 only — /etc/hosts resolves EA domains to loopback, so
# only those connections match. All real internet traffic goes to non-loopback
# IPs and is never redirected.
sudo iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-port "$BRIDGE_PORT"
ok "iptables: OUTPUT 127.0.0.1:443 → $BRIDGE_PORT"
warn "This rule is NOT persistent across reboots. Re-run './setup.sh nat' after reboot or use iptables-save."
}
cmd_unnat() {
if sudo iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "redir ports $BRIDGE_PORT"; then
sudo iptables -t nat -D OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-port "$BRIDGE_PORT"
ok "Removed iptables NAT rule"
fi
}
cmd_cert() {
heading "Installing TLS certificate…"
local scheme="https"
local cert_url="${scheme}://127.0.0.1:$BRIDGE_PORT/_bridge/cert.pem"
local cert_dest="/tmp/openfut-ca.pem"
if ! is_running "$BRIDGE_PID_FILE"; then
error "Bridge is not running. Start it first with './setup.sh start'"
exit 1
fi
info "Downloading certificate from $cert_url"
curl -sk "$cert_url" -o "$cert_dest" || { error "Failed to download cert. Is the Bridge running with TLS_ENABLED=true?"; exit 1; }
if [[ ! -s "$cert_dest" ]]; then
error "Downloaded cert is empty."
exit 1
fi
ok "Certificate saved to $cert_dest"
# Detect distro cert store
if command -v update-ca-trust &>/dev/null; then
# Arch / Fedora / RHEL
sudo cp "$cert_dest" /etc/ca-certificates/trust-source/anchors/openfut-ca.pem
sudo update-ca-trust
ok "Certificate installed (update-ca-trust)"
elif command -v update-ca-certificates &>/dev/null; then
# Debian / Ubuntu
sudo cp "$cert_dest" /usr/local/share/ca-certificates/openfut-ca.crt
sudo update-ca-certificates
ok "Certificate installed (update-ca-certificates)"
else
warn "Cannot auto-install cert — manual steps:"
echo " Copy $cert_dest to your system's trusted CA store."
fi
info "If using Steam/Proton, you may also need to add it to the game's certificate store."
info "Wine cert path (Proton): WINEDEBUG=-all wine certutil -addstore Root $cert_dest"
}
cmd_proton_cert() {
heading "Installing certificate into Proton / Wine (for Steam FIFA 23)…"
local cert_dest="/tmp/openfut-ca.pem"
if [[ ! -f "$cert_dest" ]]; then
error "No cert found at $cert_dest — run './setup.sh cert' first"
exit 1
fi
# Find Proton wine binary
local proton_wine
proton_wine=$(find "$HOME/.steam/steam/steamapps/common" -name "wine" -path "*/Proton*/wine" 2>/dev/null | head -1)
if [[ -z "$proton_wine" ]]; then
warn "Could not auto-detect Proton wine binary."
echo " Manually run: wine certutil -addstore Root $cert_dest"
echo " with the correct WINEPREFIX for FIFA 23."
return
fi
# Find FIFA 23 Proton prefix
local steam_id_fifa23="1811260"
local prefix="$HOME/.steam/steam/steamapps/compatdata/$steam_id_fifa23/pfx"
if [[ ! -d "$prefix" ]]; then
warn "FIFA 23 Proton prefix not found at $prefix"
echo " Launch FIFA 23 at least once to create the prefix, then re-run this step."
return
fi
WINEPREFIX="$prefix" WINEDEBUG=-all "$proton_wine" certutil -addstore Root "$cert_dest" \
&& ok "Certificate added to FIFA 23 Proton prefix" \
|| warn "certutil failed — you may need to add it manually."
}
cmd_quickstart() {
heading "OpenFUT Quick Start"
echo "This will: build → start services → add hosts entries → set up NAT → install cert"
echo
read -rp "Continue? [y/N] " confirm
[[ "${confirm,,}" == "y" ]] || { info "Aborted."; exit 0; }
cmd_build
cmd_start
info "Waiting for services to initialise…"
sleep 3
# Verify Core is actually up before proceeding
if ! is_running "$CORE_PID_FILE"; then
error "Core failed to start. Check the log:"
tail -20 "$CORE_LOG"
exit 1
fi
if ! is_running "$BRIDGE_PID_FILE"; then
error "Bridge failed to start. Check the log:"
tail -20 "$BRIDGE_LOG"
exit 1
fi
# sudo-gated steps — try them, print manual instructions if sudo not available
echo
heading "System configuration (requires sudo)…"
if sudo -n true 2>/dev/null; then
cmd_hosts
cmd_nat
sleep 1
cmd_cert
cmd_proton_cert
else
warn "Cannot acquire sudo non-interactively. Run these steps yourself:"
echo
echo " 1. sudo ./setup.sh hosts"
echo " 2. sudo ./setup.sh nat"
echo " 3. ./setup.sh cert (after the above two)"
echo " 4. ./setup.sh proton-cert"
fi
echo
heading "Setup complete!"
ok "FIFA 23 FUT traffic will now route to OpenFUT."
echo
echo -e " Dashboard: ${CYAN}http://127.0.0.1:$CORE_PORT/_bridge/dashboard${RESET}"
echo -e " Admin panel: ${CYAN}https://127.0.0.1:$BRIDGE_PORT/_bridge/admin${RESET}"
echo -e " Logs: ${CYAN}./setup.sh logs${RESET}"
echo -e " Stop all: ${CYAN}./setup.sh stop${RESET}"
}
cmd_watch_domains() {
heading "Watching for unknown EA domains (Ctrl-C to stop)…"
if ! is_running "$BRIDGE_PID_FILE"; then
warn "Bridge is not running. Start it first with './setup.sh start'"
exit 1
fi
info "Launch FIFA 23 now. Any domain that reaches the real EA servers will appear here."
echo
# Watch the bridge log for lines that contain host/domain info not already in our list
tail -f "$BRIDGE_LOG" 2>/dev/null | grep --line-buffered -i "unknown\|placeholder\|host\|domain\|CONNECT" \
| grep -v --line-buffered "$(IFS='|'; echo "${EA_HOSTS[*]}")" \
| while IFS= read -r line; do
echo -e "${YELLOW}$(date +'%H:%M:%S')${RESET} $line"
done
}
cmd_help() {
cat <<EOF
${BOLD}OpenFUT Setup Script${RESET}
Offline FIFA 23 FUT emulator — for legitimate game owners only.
${BOLD}Usage:${RESET} ./setup.sh <command>
${BOLD}Commands:${RESET}
quickstart Full setup in one step (build + start + hosts + nat + cert)
build Compile Core and Bridge in release mode
start Start Core and Bridge in the background
stop Stop both services and clean up hosts/NAT
restart Stop then start
status Show running state, hosts, and NAT status
logs Tail live logs from both services
watch-domains Show unknown EA domains hitting the bridge (add them to hosts)
hosts Add EA FUT domains to /etc/hosts (requires sudo)
unhosts Remove OpenFUT entries from /etc/hosts
nat Add iptables redirect 443 → $BRIDGE_PORT (requires sudo)
unnat Remove iptables NAT rule
cert Download TLS cert from Bridge and install system-wide
proton-cert Install TLS cert into the FIFA 23 Proton/Wine prefix
${BOLD}Environment overrides:${RESET}
CORE_PORT Core listen port (default: 8080)
BRIDGE_PORT Bridge internal port (default: 8443)
TLS_ENABLED Enable HTTPS on Bridge (default: true)
${BOLD}Example:${RESET}
./setup.sh quickstart # first-time full setup
./setup.sh start # start after a reboot
./setup.sh stop # clean shutdown
EOF
}
# ── Dispatch ───────────────────────────────────────────────────────────────────
case "${1:-help}" in
quickstart) cmd_quickstart ;;
build) cmd_build ;;
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_stop; cmd_start ;;
status) cmd_status ;;
logs) cmd_logs ;;
hosts) cmd_hosts ;;
unhosts) cmd_unhosts ;;
nat) cmd_nat ;;
unnat) cmd_unnat ;;
cert) cmd_cert ;;
proton-cert) cmd_proton_cert ;;
watch-domains) cmd_watch_domains ;;
help|--help|-h) cmd_help ;;
*) error "Unknown command: $1"; cmd_help; exit 1 ;;
esac