Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
dbus libpulse0 alsa-utils openbox \
libxcursor1 libxrandr2 libxinerama1 libxi6 libxss1 libxtst6 \
libfontconfig1 libxrender1 libsdl2-2.0-0 \
curl netcat-openbsd procps ca-certificates \
curl netcat-openbsd procps ca-certificates socat \
&& rm -rf /var/lib/apt/lists/*

RUN printf 'pcm.!default {\n type null\n}\nctl.!default {\n type null\n}\n' > /etc/asound.conf
Expand All @@ -31,13 +31,14 @@ RUN curl -fsSL \
&& rm -rf /var/lib/apt/lists/*

COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
RUN sed -i 's/\r$//' /entrypoint.sh && chmod +x /entrypoint.sh

USER rimworld
# Keep root for entrypoint (drops to rimworld after setup)
# USER rimworld

EXPOSE 8765

HEALTHCHECK --interval=10s --timeout=5s --retries=12 --start-period=60s \
CMD curl -sf http://localhost:8765/api/v1/game/state || exit 1
HEALTHCHECK --interval=30s --timeout=10s --retries=60 --start-period=120s \
CMD curl -sf --max-time 5 http://localhost:8765/api/v1/game/state || exit 1

ENTRYPOINT ["/entrypoint.sh"]
44 changes: 42 additions & 2 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,50 @@ python scripts/run_benchmark.py --docker --provider openai \
--no-think --runs 4 --output results/docker/
```

## Validated State

- Image builds on Docker Desktop (Windows + WSL2) and Docker CE (Linux)
- Linux game files via SteamCMD (includes Workshop mods: Harmony, RIMAPI)
- HeadlessRimPatch v1.0.0 patches texture atlas + UI + audio for headless mode
- Entrypoint seeds `ModsConfig.xml` (Harmony → Core → Royalty → HeadlessRim → RIMAPI)
- Entrypoint seeds RIMAPI config to bind `0.0.0.0:8765` (fixes IPv6 loopback)
- Game `Mods/` dir replaced with symlink to merged mods at runtime
- RIMAPI responds on :8765 within ~60s (with patched HeadlessRimPatch, see PR #6)
- CRLF line endings stripped from `entrypoint.sh` during build

### SteamCMD Download (Docker volume approach)

```bash
# Create volume and download Linux RimWorld (~860 MB)
docker volume create rimworld-linux
docker run -it --rm -v rimworld-linux:/opt/rimworld \
steamcmd/steamcmd:latest \
+@sSteamCmdForcePlatformType linux \
+force_install_dir /opt/rimworld \
+login YOUR_STEAM_USERNAME \
+app_update 294100 validate +quit
# Prompts for password + Steam Guard code
```

SteamCMD also downloads Workshop mods (Harmony, RIMAPI) into the volume.

### Running with Docker volumes

```bash
docker run -d --name rle-rimworld \
-p 8765:8765 --shm-size=1g \
-v rimworld-linux:/opt/game \
-v rimapi-mod:/opt/mods/RIMAPI:ro \
-v ./saves:/opt/saves:ro \
rle-headless:test
# RIMAPI should respond within ~60s:
curl http://localhost:8765/api/v1/game/state
```

## Known Issues

- **RIMAPI IPv6 binding**: RIMAPI binds to `[::1]:8765` inside the container. Docker port forwarding maps `0.0.0.0:8765` → container. If RIMAPI only listens on IPv6 loopback, the port forward won't reach it. May need RIMAPI config change.
- **Startup time**: HeadlessRimPatch generates a throwaway map before we load our save. Feature request filed ([HeadlessRimPatch#5](https://github.com/IlyaChichkov/HeadlessRimPatch/issues/5)) for direct save loading via env var.
- **IPv6 loopback binding**: RIMAPI's Mono HttpListener binds to `[::1]:8765` inside the container despite `serverIP=0.0.0.0` config. Docker port forwarding can't reach `::1`. Workaround: access RIMAPI from inside the container or fix the HttpListener binding in RIMAPI fork.
- **HeadlessRimPatch autoplay removed**: [PR #6](https://github.com/IlyaChichkov/HeadlessRimPatch/pull/6) removes `SetupForQuickTestPlay()` so RIMAPI serves immediately. Until merged, use our fork's `remove-autoplay` branch.

## References

Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
ports:
- "${RIMAPI_PORT:-8765}:8765"
volumes:
- ${RIMWORLD_PATH:?Set RIMWORLD_PATH to Linux RimWorld install}:/opt/game:ro
- ${RIMWORLD_PATH:?Set RIMWORLD_PATH to Linux RimWorld install}:/opt/game
- ${RIMAPI_MOD_PATH:?Set RIMAPI_MOD_PATH to RIMAPI mod directory}:/opt/mods/RIMAPI:ro
- ./saves:/opt/saves:ro
shm_size: 1gb
72 changes: 62 additions & 10 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
set -e

export HOME=/home/rimworld

# Clean stale X11 locks
rm -f /tmp/.X99-lock /tmp/.X11-unix/X99

Expand Down Expand Up @@ -33,6 +35,8 @@ if [ ! -f "$CONFIG_DIR/Mod_RedEyeDev.RIMAPI_Settings.xml" ]; then
<Mod_RedEyeDev.RIMAPI_Settings>
<serverIP>0.0.0.0</serverIP>
<serverPort>8765</serverPort>
<enableLogging>True</enableLogging>
<loggingLevel>0</loggingLevel>
</Mod_RedEyeDev.RIMAPI_Settings>
XMLEOF
fi
Expand All @@ -55,35 +59,83 @@ if [ -d /opt/game/Mods ]; then
[ -d "$mod" ] && ln -sf "$mod" "$MODS_DIR/$(basename "$mod")"
done
fi
# Link our additional mods on top
# Link Workshop mods from SteamCMD download (if present in game volume)
if [ -d /opt/game/steamapps/workshop/content/294100 ]; then
for mod in /opt/game/steamapps/workshop/content/294100/*/; do
[ -d "$mod" ] && ln -sf "$mod" "$MODS_DIR/$(basename "$mod")"
done
fi
# Link our additional mods on top (overrides Workshop versions)
for mod_dir in /opt/mods/*/; do
[ -d "$mod_dir" ] && ln -sf "$mod_dir" "$MODS_DIR/$(basename "$mod_dir")"
done
echo "Mods available in $MODS_DIR:"
ls "$MODS_DIR"

# Pre-seed ModsConfig.xml to activate required mods
# Load order: Harmony → Core → Royalty → HeadlessRimPatch → RIMAPI
if [ ! -f "$CONFIG_DIR/ModsConfig.xml" ]; then
cat > "$CONFIG_DIR/ModsConfig.xml" << 'XMLEOF'
<?xml version="1.0" encoding="utf-8"?>
<ModsConfigData>
<version>1.6</version>
<activeMods>
<li>brrainz.harmony</li>
<li>ludeon.rimworld</li>
<li>ludeon.rimworld.royalty</li>
<li>RedEyeDev.HeadlessRim</li>
<li>RedEyeDev.RIMAPI</li>
</activeMods>
<knownExpansions>
<li>ludeon.rimworld.royalty</li>
</knownExpansions>
</ModsConfigData>
XMLEOF
echo "ModsConfig.xml seeded with HeadlessRimPatch + RIMAPI"
fi

# Replace the game's Mods dir with our merged mods (needs write access to game volume)
if [ -d /opt/game/Mods ] && [ ! -L /opt/game/Mods ]; then
mv /opt/game/Mods /opt/game/Mods.orig
fi
ln -sfn "$MODS_DIR" /opt/game/Mods
echo "Game Mods -> $MODS_DIR"

# Ensure rimworld user can write to needed dirs
chown -R rimworld:rimworld /home/rimworld /opt/mods-merged /opt/saves 2>/dev/null || true
chmod +x /opt/game/RimWorldLinux 2>/dev/null || true

# Launch RimWorld
# Launch RimWorld as rimworld user
echo "Starting RimWorld headless..."
/opt/game/RimWorldLinux \
su rimworld -c '/opt/game/RimWorldLinux \
-batchmode \
-nographics \
-noshaders \
-force-opengl \
-startServer \
-logFile /tmp/rimworld_log.txt &
-logFile /tmp/rimworld_log.txt' &
GAME_PID=$!

# Wait for RIMAPI to become responsive
echo "Waiting for RIMAPI on :8765..."
# Wait for RIMAPI to become responsive.
# Wait for RIMAPI to become responsive on loopback
RIMAPI_TIMEOUT=${RIMAPI_TIMEOUT:-120}
echo "Waiting for RIMAPI on :8765 (timeout: ${RIMAPI_TIMEOUT}s)..."
ELAPSED=0
while ! curl -sf http://localhost:8765/api/v1/game/state > /dev/null 2>&1; do
while ! curl -sf --max-time 5 http://localhost:8765/api/v1/game/state > /dev/null 2>&1; do
sleep 5
ELAPSED=$((ELAPSED + 5))
if [ "$ELAPSED" -ge 120 ]; then
echo "ERROR: RIMAPI not responsive after 120s"
if [ "$ELAPSED" -ge "$RIMAPI_TIMEOUT" ]; then
echo "ERROR: RIMAPI not responsive after ${RIMAPI_TIMEOUT}s"
tail -30 /tmp/rimworld_log.txt 2>/dev/null
exit 1
fi
done
echo "RIMAPI ready"
echo "RIMAPI ready on loopback"

# Mono HttpListener binds to [::1] (IPv6 loopback) regardless of config.
# Bridge all interfaces on port 8765 so Docker port forwarding can reach it.
socat TCP4-LISTEN:8765,fork,reuseaddr TCP6:[::1]:8765 &
echo "socat bridge: 0.0.0.0:8765 -> [::1]:8765"

# Keep container alive
tail -f /tmp/rimworld_log.txt
Loading
Loading