Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
13 KiB
Installing the Rust bridge
This connects Rust servers — running Oxide or Carbon — to a Runic Gateway website that has the
Rust module. It is the Rust counterpart of ../installer/INSTALL.md, and
the design of record is ../modules/rust/PLAN.md §34.
What this installs
Two components per Rust server, released together as a bundle — an exact pair CI has checked speaks one protocol, never "the latest of each":
| Component | What it is | Released from |
|---|---|---|
| The plugin | RunicGateway.cs, one file that runs unchanged on Oxide and Carbon |
Rust-Plugins |
| The sidecar | rust-link-sidecar, which the plugin dials on loopback and the website reaches over HTTP |
Rust-Link |
The game server opens no port for the bridge: the plugin is the client and the sidecar the
listener, on 127.0.0.1. One sidecar serves one Rust server. A community running six servers
runs six sidecars, each with its own port, database and token, and the website holds six entries.
There are three ways to set a server up. They produce the same result:
| Use it when | |
|---|---|
| A. The installer | The server runs on a Linux host with systemd, or on Windows |
| B. The Pterodactyl egg | The server runs on a Pterodactyl panel |
| C. By hand | Neither fits, or you want to place every file yourself |
Before you begin
- Oxide or Carbon is installed, and the server has started once with it, so its directories exist. The bridge is a plugin; a vanilla server has nothing to load it.
- Kits and ZoneManager from uMod are what the reward and zone features use. The bridge works without them and names what is missing; nothing here installs them.
- The website has the Rust module, and you are an administrator on it. Servers are added at
Admin → Rust → Servers (
/admin/rust/servers).
Until module-rust phase 19, the released Rust module (Module-Rust v0.3.0) speaks protocol 2 while the released bridge speaks 12, and a site pairing them answers
409. This is expected and closes with phase 19's release; it is not a fault in your install.
A. The installer
The Runic Gateway installer —
the same binary that sets up ServUO shards — with --game rust. Run it as root, or from an
elevated PowerShell:
sudo ./runicgateway-installer-linux-x86_64 install --game rust --rust /srv/rust --server-id main
.\runicgateway-installer-windows-x86_64.exe install --game rust --rust D:\rust --server-id main
| Flag | |
|---|---|
--rust <path> |
The server root: the directory holding RustDedicated |
--server-id <id> |
Required. The id the website will know this server by: lowercase letters, digits and -, up to 64. It names this server's sidecar, service and files |
--web-port <port> |
The port the website reaches this sidecar on. Default: the first free from 8090 |
--bundle <tag> |
Install an exact published bundle instead of the current one |
--verify |
Report everything it would do, and write nothing |
--host <name> |
The hostname to print in the sidecar URL |
--site-url <url> |
Your site, so the printed link opens the right page |
What a run does:
- Resolves the bundle for Rust and checks the plugin's own manifest against it.
- Detects the framework — Oxide or Carbon — from the server itself. A tree with both is refused (they cannot run together); one with neither is refused with what to install.
- Writes the plugin's config (
oxide/config/RunicGateway.jsonorcarbon/configs/RunicGateway.json) holding justServerIdandPort, only if there is none. The plugin fills in every other key on its first load. An existing config is never rewritten, and one that names a different server stops the run and says so — the website locks a server's id once it has seen it. - Installs the sidecar, writes its config with this server's ports, and registers its service.
- Places the plugin in
oxide/plugins/orcarbon/plugins/. Both frameworks load a plugin the moment it lands, so on a running server the bridge comes up at once; on a stopped one, at the next boot. The installer never starts or stops your server. - Prints what the website needs.
More than one server on a host
Run it once per server, each with its own --rust and --server-id. Each gets its own service,
config, database, game port (from 7799) and web port (from 8090). They share one sidecar binary, so
update moves them all together.
Where everything lands
| Linux | Windows | |
|---|---|---|
| The sidecar binary (shared) | /usr/bin/runicgateway-rust-link |
%ProgramFiles%\RunicGateway\rust-link-sidecar.exe |
| One server's sidecar config — holds its token | /etc/runicgateway/rust/<id>.toml (mode 600) |
%ProgramData%\RunicGateway\rust\<id>\sidecar.toml |
| One server's database | /var/lib/runicgateway/rust/<id>/rust-link.db |
beside its config |
| One server's service | runicgateway-rust@<id>.service |
RunicGatewayRust-<id>, as NT SERVICE\RunicGatewayRust-<id> |
| The record | /etc/runicgateway/rust/install.json |
%ProgramData%\RunicGateway\rust\install.json |
The Rust record is separate from ServUO's, so a host running both games keeps two records that cannot interfere. The token is in the sidecar config and nowhere else — not in the record.
B. The Pterodactyl egg
Each Rust-Link release carries egg-rust-runicgateway.json: egg 18 "Rust Autowipe" with the
bridge added. A panel administrator imports it (Admin → Nests → Import Egg); the panel has no
API for writing an egg.
When you create a server from it:
| Variable | |
|---|---|
Modding Framework (FRAMEWORK) |
oxide or carbon. vanilla installs no bridge and the server boots exactly as egg 18's |
Runic Gateway: server id (RUSTLINK_SERVER_ID) |
The id on the website. Read once, when the plugin writes its first config; after that the file holds it and the site locks it, so changing the variable later changes nothing — the launcher hands the sidecar the id from the plugin's config and says so in the console |
Runic Gateway: sidecar port (RUSTLINK_WEB_PORT) |
One of this server's allocations. The panel does not tell a server which ports it holds, so a port that is not allocated binds but is never reachable |
Runic Gateway: sidecar token (RUSTLINK_WEB_TOKEN) |
Leave blank: the sidecar generates one. Anyone who can see this server's startup variables can read a token typed here |
Runic Gateway: bundle (RUNICGATEWAY_BUNDLE) |
Blank takes the current bundle. A tag pins one |
Runic Gateway: history days (RUSTLINK_RETAIN_DAYS) |
Blank keeps the default |
The install downloads the sidecar, its launcher and the plugin from the bundle, checks each
against the bundle's checksum, and only then places them: the sidecar in rust-link/, the plugin in
oxide/plugins/ or carbon/plugins/. Any mismatch fails the install with the reason, before
anything is placed.
The first boot prints, in the console, the token the sidecar generated — once — and a line with what the website needs:
[rust-link] A new sidecar token was generated. It is shown ONCE, here:
[rust-link] 4f9c…
[rust-link] It is kept in rust-link/sidecar.toml; read it there if you lose it.
[rust-link] Admin -> Rust -> Servers: server id 'main', sidecar URL http://<this node's address>:21009 (listening on 0.0.0.0:21009)
Every later boot prints only the second line. The sidecar runs inside the server's container and stops when the server stops.
Updating is a reinstall. The bridge is fetched at install and reinstall only, never on a restart, so a restart can never change the protocol under a website that has not been updated. Reinstall with Regen Server off to take the current bundle (or the pinned one) and keep the map.
A wipe keeps the bridge's history. With Regen Server on, the install script deletes
REMOVE_FILES — and moves rust-link/ out of the way while it does, so no list, wildcards
included, can reach the database or the token. After a wipe the website still has the server's
history, and the token is unchanged.
C. By hand
-
Pick the bundle — the current one is
v2/rust/current.jsonon the installer'sbundlesbranch. It names the sidecar binary for your platform and the plugin tarball, each with asha256. -
Download and verify both against those checksums (
sha256sum -c, orGet-FileHash). -
The plugin: copy
runicgateway-rust-plugin/RunicGateway.csfrom the tarball intooxide/plugins/orcarbon/plugins/. To name the server, create its config first —oxide/config/RunicGateway.jsonorcarbon/configs/RunicGateway.json— holding{ "ServerId": "<id>", "Port": 7799 }. Without it, the plugin calls the servermain. -
The sidecar: put the binary somewhere stable and give it a config:
[game] bind = '127.0.0.1:7799' # the plugin's Port server_id = '<id>' [web] bind = '127.0.0.1:8090' auth_token = "" # generated and saved on first start [store] path = '/var/lib/rust-link/<id>/rust-link.db'rust-link-sidecar --print-config --config <file>generates the token, saves it, and prints it. -
Run it under your supervisor of choice with
--config <file>, as an unprivileged user that can write the database directory. On Windows the same.exeruns as a service (sc.exe create … binPath= "\"<exe>\" --config \"<file>\""); it speaks the service handshake itself.
Every environment variable the sidecar reads (RUSTLINK_CONFIG, RUSTLINK_WEB_BIND,
RUSTLINK_WEB_TOKEN, …) overrides the file; an empty one is ignored.
Connect the website
On your site, open Admin → Rust → Servers and add the server:
| Field | Value |
|---|---|
| Id | the server id — the installer's --server-id, or the egg's variable |
| Name | anything; players see it |
| Sidecar address | the sidecar URL the installer or the console printed |
| Sidecar token | the token |
Test it from the same page: it should answer plugin connected with the protocol.
If the website is on another machine
The installer's sidecar listens on 127.0.0.1, reachable only from the game host. Put a TLS
reverse proxy (or a VPN link) in front of it and give the website the proxy's URL — the token is a
plain bearer token, and the sidecar speaks HTTP. The egg's sidecar listens on its allocation,
which is reachable from the network; firewall it to the website's address. Leave the game bind
on loopback either way: that socket takes commands for the game server and has no token, and
being loopback-only is what makes that safe.
Day two
With the installer:
doctor --game rust [--server-id <id>] |
Per server: the framework; whether the plugin file is still the one deployed; that the plugin's config names this server; the required uMod plugins; the service; and /health through to plugin connected. A stopped server is a warning; a running one whose plugin never connected is a failure, printed with the framework versions the plugin is known good on |
update --game rust |
Moves the sidecar and every server's plugin to the current bundle, and restarts the sidecars. Always all servers together — they share one binary |
uninstall --game rust [--server-id <id>] [--purge] |
Removes the service and the plugin file. Keeps the plugin's config — it is the website's, and it names the server. --purge also removes the sidecar config (the token) and the database. Removing the last server removes the shared binary too |
With the egg: reinstall to update (above); the console is the diagnosis.
Troubleshooting
| Symptom | Cause |
|---|---|
| The install stops: already names this server "x" | The plugin's config names another server. Use that id, or — if the server really is new to the site — remove the config file |
| The install stops: has both Oxide and Carbon | A tree mid-migration. Remove the framework you are leaving |
| The site says the sidecar is unreachable | The website cannot reach the URL: a loopback bind with the site elsewhere (see above), a firewall, or — on the egg — a sidecar port that is not one of the server's allocations |
The site answers 409 |
The site's Rust module speaks another protocol than the bridge. Update whichever is behind (and see the note in Before you begin) |
doctor: plugin connected — no, and the server is running |
The plugin did not compile or cannot reach its sidecar. The server console names a compile error; the plugin's Port must equal the sidecar's game port |
| The sidecar logs refusing a plugin that names another server | Two servers dial one sidecar: a plugin's Port is another server's game port. Correct that plugin's Port (in its config) and reload it. Nothing it sent was stored |
doctor: not the file that was deployed |
The plugin was edited by hand. update --game rust puts the released one back |
| The egg's console prints a new token after every boot | Only a sidecar older than the first Rust-Link release does this; reinstall the server |