Merge pull request #152 from outis1one/claude/blissful-carson-4dckso
kyber-server: fix Wine crash + map rotation format
This commit is contained in:
+46
-77
@@ -4,21 +4,25 @@
|
||||
#
|
||||
# Kyber is a community multiplayer replacement for Star Wars Battlefront II (2017)
|
||||
# after EA shut down official servers in 2022. This installs the headless dedicated
|
||||
# server via the official Armchair Developers Docker image — no GPU required for the
|
||||
# server itself (GPU passthrough is added automatically if detected, for future use).
|
||||
# server via the official Armchair Developers Docker image.
|
||||
#
|
||||
# The container runs Wine Proton GE + Xvfb headlessly. Three requirements:
|
||||
# - seccomp=unconfined (Docker default blocks Wine/Proton system calls → page fault)
|
||||
# - shm_size ≥ 256m (Xvfb needs /dev/shm for MIT-SHM extension)
|
||||
# - network_mode: host (game traffic uses dynamic UDP ports)
|
||||
#
|
||||
# Prerequisites:
|
||||
# - SWBF2 (Steam AppID 1237950) installed via Steam on this machine
|
||||
# - Docker CE + Compose plugin
|
||||
# - EA account that owns SWBF2
|
||||
# - EA account that owns SWBF2 (alphanumeric password — special chars break auth)
|
||||
# - Internet access to pull the Docker image and authenticate with Kyber
|
||||
#
|
||||
# The Kyber token is obtained via kyber_cli (extracted from the Linux port AppImage)
|
||||
# which opens a browser for EA OAuth. One-time step — the token never expires.
|
||||
#
|
||||
# Map rotation is left blank by default; edit ~/docker/kyber-server/.env after install
|
||||
# and set KYBER_MAP_ROTATION to a base64-encoded rotation string (use the Kyber client
|
||||
# HOST tab to build a rotation, then base64-encode it).
|
||||
# Map rotation format: KYBER_MAP_ROTATION = base64 of newline-separated lines,
|
||||
# each line: MODE;MAP_PATH e.g. GalacticAssault;Levels/MP/Geonosis_01/Geonosis_01
|
||||
# The interactive builder below produces the correct format automatically.
|
||||
# ── Registration ──────────────────────────────────────────────────────────────
|
||||
command -v register_service &>/dev/null && \
|
||||
register_service kyber-server gaming "Kyber dedicated server for SWBF2 (2017) — headless, no GPU required"
|
||||
@@ -67,21 +71,6 @@ _kyber_find_swbf2() {
|
||||
return 1
|
||||
}
|
||||
|
||||
_kyber_detect_gpu() {
|
||||
# Returns: nvidia | intel | amd | none
|
||||
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null 2>&1; then
|
||||
echo "nvidia"
|
||||
elif [[ -e /dev/dri/renderD128 ]]; then
|
||||
if lspci 2>/dev/null | grep -qi "Intel.*VGA\|VGA.*Intel"; then
|
||||
echo "intel"
|
||||
else
|
||||
echo "amd"
|
||||
fi
|
||||
else
|
||||
echo "none"
|
||||
fi
|
||||
}
|
||||
|
||||
_kyber_get_token() {
|
||||
local auth_toml="$ACTUAL_HOME/.local/share/maxima/auth.toml"
|
||||
|
||||
@@ -115,10 +104,10 @@ install_kyber_server() {
|
||||
# ── DRY RUN ───────────────────────────────────────────────────────────────
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "[DRY-RUN] Would auto-detect SWBF2 install path"
|
||||
echo "[DRY-RUN] Would install libgamemode0:i386 if missing"
|
||||
echo "[DRY-RUN] Would download Kyber AppImage and extract kyber_cli"
|
||||
echo "[DRY-RUN] Would run kyber_cli get_token (opens browser)"
|
||||
echo "[DRY-RUN] Would prompt for EA credentials and server name"
|
||||
echo "[DRY-RUN] Would detect GPU and add passthrough if found"
|
||||
echo "[DRY-RUN] Would write ~/docker/kyber-server/docker-compose.yml"
|
||||
echo "[DRY-RUN] Would write ~/docker/kyber-server/.env (chmod 600)"
|
||||
return 0
|
||||
@@ -177,15 +166,16 @@ install_kyber_server() {
|
||||
fi
|
||||
log_success "Found SWBF2 at: $SWBF2_PATH"
|
||||
|
||||
# ── Detect GPU ────────────────────────────────────────────────────────────
|
||||
local GPU_TYPE
|
||||
GPU_TYPE=$(_kyber_detect_gpu)
|
||||
case "$GPU_TYPE" in
|
||||
nvidia) log_info "GPU detected: NVIDIA (will add GPU passthrough)" ;;
|
||||
intel) log_info "GPU detected: Intel (will add DRI device passthrough)" ;;
|
||||
amd) log_info "GPU detected: AMD (will add DRI device passthrough)" ;;
|
||||
none) log_info "No GPU detected — running CPU-only (fine for dedicated server)" ;;
|
||||
esac
|
||||
# ── libgamemode0:i386 ─────────────────────────────────────────────────────
|
||||
# Wine Proton inside the container links against this; without it on the host
|
||||
# the bind-mount of /proc may surface warnings. Install once on the host.
|
||||
if ! dpkg -l libgamemode0:i386 &>/dev/null 2>&1; then
|
||||
log_info "Installing libgamemode0:i386 (Wine/Proton dependency)..."
|
||||
dpkg --add-architecture i386 2>/dev/null || true
|
||||
apt-get update -qq 2>/dev/null || true
|
||||
apt-get install -y libgamemode0:i386 2>/dev/null \
|
||||
|| log_warning "libgamemode0:i386 install failed — server may still work"
|
||||
fi
|
||||
|
||||
# ── Kyber AppImage / CLI ──────────────────────────────────────────────────
|
||||
local KYBER_REPO="simonlinuxcraft/kyber-linuxport-unofficial"
|
||||
@@ -283,6 +273,9 @@ for a in data.get('assets', []):
|
||||
|
||||
# ── EA credentials ────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo " ⚠ EA password must contain only letters and numbers."
|
||||
echo " Special characters (!, @, #, \$, %, ^, &, *) cause auth failures."
|
||||
echo ""
|
||||
local EA_EMAIL EA_PASSWORD
|
||||
prompt_text "EA account email:" "" EA_EMAIL
|
||||
if [[ -z "$EA_EMAIL" ]]; then
|
||||
@@ -311,14 +304,11 @@ for a in data.get('assets', []):
|
||||
prompt_text "Server password (leave blank for public):" "" SERVER_PASSWORD
|
||||
|
||||
# ── Map rotation ──────────────────────────────────────────────────────────
|
||||
# Known map IDs decoded from maprotation.hive (the binary the Kyber client writes).
|
||||
# Format: JSON array → base64 → KYBER_MAP_ROTATION env var.
|
||||
# Format: base64 of "MODE;MAP_PATH\n" lines (parsed by Kyber CLI).
|
||||
local MAP_ROTATION_B64=""
|
||||
local _pick_maps=""
|
||||
prompt_yn "Configure map rotation now? (y/n):" "y" _pick_maps
|
||||
if [[ "$_pick_maps" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
echo " Available maps (Space = toggle, Enter when done):"
|
||||
echo ""
|
||||
# Map definitions: "Display Name|level_path|mode"
|
||||
local -a MAP_DEFS=(
|
||||
@@ -368,25 +358,24 @@ for a in data.get('assets', []):
|
||||
done
|
||||
fi
|
||||
|
||||
# Build JSON array
|
||||
local _json="["
|
||||
local _first=1
|
||||
# Build rotation: one "MODE;MAP_PATH" line per entry (newline-separated).
|
||||
# The CLI base64-decodes this and splits on newlines, then on ';'.
|
||||
local _lines=""
|
||||
local _count=0
|
||||
for i in "${!MAP_DEFS[@]}"; do
|
||||
[[ "${SELECTED_FLAGS[$i]}" == "0" ]] && continue
|
||||
IFS='|' read -r _name _level _mode <<< "${MAP_DEFS[$i]}"
|
||||
[[ "$_first" == "0" ]] && _json+=","
|
||||
_json+="{\"map\":\"${_level}\",\"mode\":\"${_mode}\"}"
|
||||
_first=0
|
||||
_lines+="${_mode};${_level}"$'\n'
|
||||
(( _count++ )) || true
|
||||
SELECTED_MAPS+=("$_name")
|
||||
done
|
||||
_json+="]"
|
||||
|
||||
if [[ "$_first" == "1" ]]; then
|
||||
if [[ $_count -eq 0 ]]; then
|
||||
log_warning "No maps selected — server will use its default rotation."
|
||||
else
|
||||
MAP_ROTATION_B64=$(echo -n "$_json" | base64 -w 0)
|
||||
MAP_ROTATION_B64=$(printf '%s' "$_lines" | base64 -w 0)
|
||||
echo ""
|
||||
log_success "Map rotation configured (${#SELECTED_MAPS[@]} maps):"
|
||||
log_success "Map rotation configured ($_count maps):"
|
||||
for m in "${SELECTED_MAPS[@]}"; do
|
||||
echo " • $m"
|
||||
done
|
||||
@@ -399,33 +388,6 @@ for a in data.get('assets', []):
|
||||
mkdir -p "$DIR"
|
||||
ensure_docker_dir_ownership "$DIR"
|
||||
|
||||
# Build GPU section for docker-compose
|
||||
local GPU_SECTION=""
|
||||
local DEVICES_SECTION=""
|
||||
local GROUP_ADD_SECTION=""
|
||||
|
||||
case "$GPU_TYPE" in
|
||||
nvidia)
|
||||
GPU_SECTION="
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]"
|
||||
;;
|
||||
intel|amd)
|
||||
DEVICES_SECTION="
|
||||
devices:
|
||||
- /dev/dri/renderD128:/dev/dri/renderD128"
|
||||
GROUP_ADD_SECTION="
|
||||
group_add:
|
||||
- video
|
||||
- render"
|
||||
;;
|
||||
esac
|
||||
|
||||
cat > "$DIR/docker-compose.yml" << EOF
|
||||
name: kyber-server
|
||||
services:
|
||||
@@ -433,11 +395,17 @@ services:
|
||||
image: ghcr.io/armchairdevelopers/kyber-server:latest
|
||||
container_name: kyber-swbf2
|
||||
restart: unless-stopped
|
||||
# host networking: game traffic uses dynamic UDP ports
|
||||
network_mode: host
|
||||
# seccomp=unconfined: Wine/Proton needs system calls blocked by Docker default
|
||||
# shm_size: Xvfb requires /dev/shm (MIT-SHM extension) — 64 MB default is too small
|
||||
security_opt:
|
||||
- seccomp=unconfined
|
||||
shm_size: '256m'
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ${SWBF2_PATH}:/mnt/battlefront
|
||||
- ./logs:/logs
|
||||
${GPU_SECTION}${DEVICES_SECTION}${GROUP_ADD_SECTION}
|
||||
EOF
|
||||
|
||||
# Write .env with restricted permissions
|
||||
@@ -450,9 +418,9 @@ KYBER_SERVER_NAME='${SERVER_NAME}'
|
||||
KYBER_SERVER_MAX_PLAYERS=${MAX_PLAYERS}
|
||||
# Leave blank for a public server, or set a password to make it private
|
||||
KYBER_SERVER_PASSWORD='${SERVER_PASSWORD}'
|
||||
# Map rotation: base64-encoded JSON array of {"map":"...","mode":"..."} objects.
|
||||
# Map rotation: base64 of newline-separated "MODE;MAP_PATH" lines.
|
||||
# Re-run the installer to rebuild, or encode manually:
|
||||
# echo -n '[{"map":"Levels/MP/Geonosis_01/Geonosis_01","mode":"GalacticAssault"}]' | base64 -w0
|
||||
# printf 'GalacticAssault;Levels/MP/Geonosis_01/Geonosis_01\nGalacticAssault;Levels/MP/Hoth_01/Hoth_01\n' | base64 -w0
|
||||
KYBER_MAP_ROTATION=${MAP_ROTATION_B64}
|
||||
EOF
|
||||
chmod 600 "$DIR/.env"
|
||||
@@ -476,10 +444,11 @@ docker compose pull && docker compose up -d # update
|
||||
|
||||
## Map rotation
|
||||
Edit `.env` and set `KYBER_MAP_ROTATION` to a base64-encoded rotation string.
|
||||
Build the rotation JSON in the Kyber client (HOST tab), then:
|
||||
Format: one `MODE;MAP_PATH` per line, e.g.:
|
||||
```bash
|
||||
echo -n '<rotation-json>' | base64
|
||||
printf 'GalacticAssault;Levels/MP/Geonosis_01/Geonosis_01\nGalacticAssault;Levels/MP/Hoth_01/Hoth_01\n' | base64 -w0
|
||||
```
|
||||
Paste the output as `KYBER_MAP_ROTATION=<value>` in `.env`, then restart.
|
||||
|
||||
## Token refresh
|
||||
If the Kyber token stops working, re-run the installer or:
|
||||
|
||||
Reference in New Issue
Block a user