wtclaude b3b66b1cc2
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 3m53s
feat(sidecar): a Windows service, the egg and its launcher, and the first release workflow (phase 18)
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
2026-09-25 23:14:57 -05:00
2026-09-15 19:52:25 -05:00
2026-09-15 19:52:25 -05:00
2026-09-15 19:52:25 -05:00
2026-09-15 19:52:25 -05:00

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.

Description
No description provided
Readme 451 KiB
v0.1.3 Latest
2026-09-26 14:53:33 +00:00
Languages
Rust 90.5%
Shell 9.5%