Reviewed-on: #18
rust-link
The sidecar half of the Runic Gateway bridge for Rust. It terminates the loopback link from a Rust server's Oxide bridge plugin and exposes the WebSocket + REST surface the website consumes.
It is the mirror of RunicGateway/link, which
does the same job for Ultima Online, and it keeps that bridge's central invariant unchanged:
The game server is never reachable from the website. The plugin dials out to this process; this process owns the listener. Only the sidecar is exposed, and only the website's backend talks to it.
Rust server + Oxide (RunicGateway/Rust-Plugins, C#)
│ loopback TCP 127.0.0.1:7799, newline-delimited JSON, bidirectional
│ the PLUGIN dials out (the game opens no listening port for us)
▼
rust-link sidecar (this repo, Rust) ← the only network-facing bridge component
│ WebSocket (live feed) + REST (point-in-time reads), bearer-token auth
▼
website backend + module-rust (RunicGateway/Module-Rust, Node)
One server, one sidecar
This binary serves exactly one Rust game server. A community running six servers runs six
pairs, each with its own port, database and token; module-rust holds six clients and the website
core never learns there is more than one. Nothing here is multiplexed, and nothing here should
become multiplexed.
Build and run
cd sidecar
cargo build --release # → target/release/rust-link-sidecar
cargo run # info logging; writes sidecar.toml (with a generated token) on first run
RUST_LOG=debug cargo run # verbose, including heartbeats
Everything is configured from sidecar.toml — nothing is compiled into the binary. Auth is
always on: a blank token is generated and written back on first start, so there is no state in
which this process listens without one. --print-config resolves the configuration and prints it
as JSON, which is how an installer reads the token back without scraping a log.
See sidecar/README.md for the configuration reference and the endpoint list.
Releases, the launcher and the egg
Every merge to main carrying a feat, fix or perf commit cuts a release
(.gitea/workflows/release.yml):
| Asset | What it is |
|---|---|
rust-link-sidecar-linux-x86_64 |
Static (musl): one binary for a systemd host and for the egg's game container |
rust-link-sidecar-windows-x86_64.exe |
Runs as a console program or as a Windows service |
with-sidecar.sh |
The egg's launcher: starts the sidecar, prints the URL (and a new token, once), then execs the game |
egg-rust-runicgateway.json |
The Pterodactyl egg, for a panel admin to import |
SHA256SUMS |
The trust anchor for all of the above |
There is no linux-aarch64: RustDedicated has no arm64 build. A release then asks the installer
repo to recompose its Rust bundle, which is what the installer (--game rust) and the egg install
from.
egg/ holds the egg's sources: egg.json, install.sh (egg 18 "Rust Autowipe"'s script
with a wipe guard around its rm -rf ${REMOVE_FILES} and the bridge fetched from a bundle) and
with-sidecar.sh. bash egg/build.sh assembles them, as PR Checks and the release do.
The protocol is a contract
The loopback JSON protocol (plugin ↔ sidecar) and this sidecar's HTTP/WS API (sidecar ↔ website)
are versioned compatibility contracts, not build dependencies. PROTOCOL_VERSION lives in
sidecar/src/main.rs; every response carries X-RustLink-Version, and a
client that declares a different one is refused 409 rather than served something it will
mis-parse.
A version is declared in three places and they must agree:
| Where | Repo |
|---|---|
PROTOCOL_VERSION |
this repo |
overlay.toml |
RunicGateway/Rust-Plugins |
module.json |
RunicGateway/Module-Rust |
The canonical spec is
docs/rust-link/. If
you add or change an event or a command, update all three repos and the spec in the same
change.
Licence
GPL-3.0-or-later. See LICENSE.md.