All checks were successful
PR Checks / rust-gates (pull_request) Successful in 3m53s
Module-rust phase 18, step 4 of docs/modules/rust/PLAN.md §34.2.7.
The Windows service (D149, §34.2.5): src/windows.rs, ported from link's fix
for error 1053. The same exe tries the SCM handshake and falls through to a
console run on 1063; it reports Running only once the listener and store are
up, and logs to a daily file beside its config. One binary serves every
RunicGatewayRust-<id> instance, because the SCM ignores the dispatcher's name
for an own-process service.
An empty environment variable now counts as unset. A Pterodactyl egg exports
every variable it declares, so a blank RUSTLINK_WEB_TOKEN arrived as "" and
overrode the saved token, and a new one was generated and persisted on every
boot. That breaks D152, which this change makes true.
The egg (R20, R22, D151, D152, §34.2.6), in egg/:
- install.sh is egg 18's script with two changes. A wipe guard moves
rust-link/ to /tmp around `rm -rf ${REMOVE_FILES}`. The bridge block then
fetches a schema-2 Rust bundle (pinnable by RUNICGATEWAY_BUNDLE), checks
every asset's sha256 and the plugin's protocol before placing anything, and
places the plugin by FRAMEWORK. Vanilla installs nothing and does not fail.
- with-sidecar.sh is the launcher. It unsets blank variables, builds the web
bind from RUSTLINK_WEB_PORT, and runs --print-config so that a newly
generated token is printed once. It prints the URL and server id for the
admin page, then execs the game. It no longer uses `set -e`: nothing the
bridge gets wrong may keep the game from booting.
- The startup's launcher prefix is conditional, so a server with no bridge
boots exactly as egg 18 does.
- build.sh assembles egg-rust-runicgateway.json. PR Checks runs it.
The release (D145, §34.2.1) reuses servuo-plugins' engine. It publishes the
static musl Linux binary, the Windows exe, the launcher, the egg and
SHA256SUMS, and dispatches the installer's bundle.yml. PR Checks gains a
clippy run for the Windows target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
93 lines
4.3 KiB
Markdown
93 lines
4.3 KiB
Markdown
# rust-link
|
|
|
|
The **sidecar** half of the Runic Gateway bridge for [Rust](https://rust.facepunch.com/). 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`](https://gitea.whitlocktech.com/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
|
|
|
|
```bash
|
|
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`](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 `exec`s 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/`](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`](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`](https://gitea.whitlocktech.com/RunicGateway/Rust-Plugins) |
|
|
| `module.json` | [`RunicGateway/Module-Rust`](https://gitea.whitlocktech.com/RunicGateway/Module-Rust) |
|
|
|
|
The canonical spec is
|
|
[`docs/rust-link/`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/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](LICENSE.md).
|