# 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`](../installer/INSTALL.md), and the design of record is [`../modules/rust/PLAN.md`](../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" — and a third, RunicNPC, which is required: | Component | What it is | Released from | |---|---|---| | **The plugin** | `RunicGateway.cs`, one file that runs unchanged on Oxide and Carbon — and beside it, from protocol 13, the optional **ZoneManager helper** `RunicGatewayZones.cs` (PLAN_FIXES D181, D182), installed by default | [Rust-Plugins](https://gitea.whitlocktech.com/RunicGateway/Rust-Plugins/releases) | | **The sidecar** | `rust-link-sidecar`, which the plugin dials on loopback and the website reaches over HTTP | [Rust-Link](https://gitea.whitlocktech.com/RunicGateway/Rust-Link/releases) | | **RunicNPC** | `RunicNPC.cs`, Runic Gateway's NPC plugin ([`../runicnpc/PLAN.md`](../runicnpc/PLAN.md)), placed beside the bridge: the latest RunicNPC release that answers the API the bridge needs (D224). **Required** since RunicNPC's stage 9 (D310): without it the bridge refuses every NPC an event places, Rust's own scientists included, and Admin → Rust → Servers shows the server as incomplete. It needs **Kits**, like the bridge. Its own guide is [`../runicnpc/INSTALL.md`](../runicnpc/INSTALL.md) | [runicnpc-rust](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases) | 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**](#a-the-installer) | The server runs on a Linux host with systemd, or on Windows | | [**B. The Pterodactyl egg**](#b-the-pterodactyl-egg) | The server runs on a Pterodactyl panel | | [**C. By hand**](#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. **RunicNPC needs Kits** and will not load without it, so without Kits no event can place NPCs. - **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](https://gitea.whitlocktech.com/RunicGateway/installer/releases) — the same binary that sets up ServUO shards — with `--game rust`. Run it as root, or from an elevated PowerShell: ```bash sudo ./runicgateway-installer-linux-x86_64 install --game rust --rust /srv/rust --server-id main ``` ```powershell .\runicgateway-installer-windows-x86_64.exe install --game rust --rust D:\rust --server-id main ``` | Flag | | |---|---| | `--rust ` | The server root: the directory holding `RustDedicated` | | `--server-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 ` | The port the website reaches this sidecar on. Default: the first free from 8090 | | `--bundle ` | Install an exact published bundle instead of the current one | | `--verify` | Report everything it would do, and write nothing | | `--host ` | The hostname to print in the sidecar URL | | `--site-url ` | Your site, so the printed link opens the right page | What a run does: 1. **Resolves the bundle** for Rust and checks the plugin's own manifest against it. 2. **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. 3. **Writes the plugin's config** (`oxide/config/RunicGateway.json` or `carbon/configs/RunicGateway.json`) holding just `ServerId` and `Port`, **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. 4. **Installs the sidecar**, writes its config with this server's ports, and registers its service. 5. **Places the plugin** in `oxide/plugins/` or `carbon/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. 6. **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/.toml` (mode 600) | `%ProgramData%\RunicGateway\rust\\sidecar.toml` | | One server's database | `/var/lib/runicgateway/rust//rust-link.db` | beside its config | | One server's service | `runicgateway-rust@.service` | `RunicGatewayRust-`, as `NT SERVICE\RunicGatewayRust-` | | 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/`. `RunicNPC.cs` goes beside the plugin (its data directory is left for RunicNPC to make); a bundle without RunicNPC is refused. 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://: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 1. **Pick the bundle** — the current one is [`v2/rust/current.json`](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/bundles/v2/rust/current.json) on the installer's `bundles` branch. It names the sidecar binary for your platform and the plugin tarball, each with a `sha256`. 2. **Download and verify** both against those checksums (`sha256sum -c`, or `Get-FileHash`) — and RunicNPC's tarball, the bundle's `npc` entry, too. Copy `runicnpc/RunicNPC.cs` from it into the same plugins directory as the bridge. **Do not create `data/RunicNPC/` yourself**: RunicNPC makes it on first load, and one made from outside the game (a panel's file manager) is not writable by it. 3. **The plugin:** copy `runicgateway-rust-plugin/RunicGateway.cs` from the tarball into `oxide/plugins/` or `carbon/plugins/`, and every other `.cs` the tarball's `manifest.json` lists in `files` beside it — from protocol 13 that is `RunicGatewayZones.cs`, which lets ZoneManager count a player already standing in a zone when it opens or comes back after a restart, and (from 0.2.0) has ZoneManager enforce the flags of a zone opened after it loaded. It is optional: leave it out and events still score everybody, but ZoneManager's own flags miss that player, and an event zone's flags such as PvpGod or NoBuild may not hold at all. The second is `RunicGatewayDomes.cs`, which lets the bridge ask ZoneDomes for a dome over an event zone; leave it out and zones open without one. To name the server, create its config first — `oxide/config/RunicGateway.json` or `carbon/configs/RunicGateway.json` — holding `{ "ServerId": "", "Port": 7799 }`. Without it, the plugin calls the server `main`. 4. **The sidecar:** put the binary somewhere stable and give it a config: ```toml [game] bind = '127.0.0.1:7799' # the plugin's Port server_id = '' [web] bind = '127.0.0.1:8090' auth_token = "" # generated and saved on first start [store] path = '/var/lib/rust-link//rust-link.db' ``` `rust-link-sidecar --print-config --config ` generates the token, saves it, and prints it. 5. **Run it** under your supervisor of choice with `--config `, as an unprivileged user that can write the database directory. On Windows the same `.exe` runs as a service (`sc.exe create … binPath= "\"\" --config \"\""`); 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 ]` | Per server: the framework; whether the plugin file is still the one deployed; each helper deployed beside it, as a **warning** when missing or edited (the bridge runs without one, and the row says what that costs); RunicNPC as a **failure** when it is missing or was never installed (it is required: without it every NPC an event places is refused), and a warning when edited by hand; 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 ] [--purge]` | Removes the service, the plugin file, its helpers and RunicNPC. **Keeps the plugin's config** and RunicNPC's `data/RunicNPC/` (an admin's placements and routes) — 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 |