# rust-link-sidecar Configuration reference and endpoint list. For what this component *is*, see the [repo README](../README.md). ## Configuration `sidecar.toml`, resolved in this order: `--config `, else `$RUSTLINK_CONFIG`, else `./sidecar.toml`. Environment variables override the file; the file overrides the defaults. | Key | Env | Default | What it is | |---|---|---|---| | `[game].bind` | `RUSTLINK_GAME_BIND` | `127.0.0.1:7799` | Where the Oxide plugin dials in | | `[game].server_id` | `RUSTLINK_SERVER_ID` | *(empty)* | Optional cross-check against the plugin's own `serverId` | | `[web].bind` | `RUSTLINK_WEB_BIND` | `127.0.0.1:8090` | Where the website reaches this sidecar | | `[web].auth_token` | `RUSTLINK_WEB_TOKEN` | *(generated)* | The shared secret the website presents | | `[store].path` | `RUSTLINK_DB_PATH` | `rust-link.db` | SQLite file | Two things about those defaults are load-bearing: - **`[game].bind` is loopback, and there is no token on that link.** The plugin and the sidecar share a host; `127.0.0.1` *is* the authentication. Binding it to a routable address puts an unauthenticated command channel on the network. - **A relative `[store].path` resolves against the directory holding `sidecar.toml`**, not the working directory. A service manager's working directory must not decide where the database lands — on Windows that can be `%SystemRoot%\System32`, or a silently redirected VirtualStore copy. `[game].server_id` is a **cross-check, not a second source of truth**. The plugin announces its own `serverId` and that is the authority; when both are set and they disagree, the sidecar logs the disagreement loudly and keeps the plugin's. Two game servers pointed at one sidecar by a copied config is the mistake this catches, and it is silent in every other design. ### Reading the token back ```bash rust-link-sidecar --print-config ``` Resolves the configuration exactly as a normal start would — writing the file and generating the token if they are missing — and prints it as JSON on stdout, **including the token in clear text**. That is the supported way for an installer to obtain it; the alternative is scraping a log. ## Endpoints Everything except `/health` requires the token, as `Authorization: Bearer `, `X-Api-Key: `, or `?token=` (the last so browser WebSocket clients, which cannot set handshake headers, can still authenticate). Every response carries `X-RustLink-Version`. | Route | Backed by | Notes | |---|---|---| | `GET /health` | — | Unauthenticated, so monitoring can reach it | | `GET /server` | store | The last `server.hello`. **`204` when the game has never connected** | | `GET /events?kind=&limit=` | store | Newest first; `limit` clamped to 1–1000 | | `GET /status` | plugin (RPC) | A live round trip. `503` with no plugin, `504` on no reply | | `GET /ws` | broadcast | The live feed. Sends `ws.hello` on connect | The split is the point: the store-backed reads answer while the game is off, which is what lets the website render a server list during a wipe or a restart. `/status` is the one route that fails when the game is down, because "what is it doing right now" has no stale answer worth giving. `/server` answers `204`, not `200` with a null, when the game has never connected. "We have never heard from this server" and "this server reports nothing" are different answers, and a client that cannot tell them apart renders a server that does not exist. ## Protocol 1 Newline-delimited JSON over TCP, both directions. Outbound frames (plugin → sidecar) carry `kind`; inbound frames (sidecar → plugin) carry `cmd`. Lines are capped at 1 MiB; an over-long line is discarded and the connection stays up. | Frame | Direction | Purpose | |---|---|---| | `server.hello` | plugin → sidecar | Sent on **every connect**, not once at server start — this process restarts independently of the game. Carries `serverId` and `bootId` | | `ping` / `pong` | sidecar → plugin → sidecar | The heartbeat, every 30s. A `pong` is never persisted; it only moves `last_event` | | `server.status` | sidecar → plugin → sidecar | The one request/reply verb, correlated by `reqId` | `bootId` is how a game restart is told apart from a sidecar reconnect — the distinction the event system's `reconcile` hangs off later. **The RPC reply timeout (`rpc::REPLY_TIMEOUT`, 10s) is a ceiling every later command budget sits under.** Core classifies a budget overrun as retryable unconditionally, because it cannot ask the game while the action is still awaiting a socket. An action whose `budgetMs` exceeds this can never report `retry: false`. ## Checks ```bash cargo fmt --all -- --check cargo clippy --all-targets -- -D warnings cargo test ```