# 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": | Component | What it is | Released from | |---|---|---| | **The plugin** | `RunicGateway.cs`, one file that runs unchanged on Oxide and Carbon | [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) | 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. - **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 | | **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://: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`). 3. **The plugin:** copy `runicgateway-rust-plugin/RunicGateway.cs` from the tarball into `oxide/plugins/` or `carbon/plugins/`. 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; 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 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 | | `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 |