docs(link,installer): protocol 4 is live — correct every stale 3 #172
@@ -116,7 +116,7 @@ time rather than hardcoding versions or blindly taking each repo's newest releas
|
||||
Consequences worth knowing:
|
||||
|
||||
- A sidecar patch release does **not** mean re-downloading the installer. The bundle is data.
|
||||
- `--bundle <tag>` (e.g. `--bundle 2026.08.04`) pins an exact past combination, so a reinstall six
|
||||
- `--bundle <tag>` (e.g. `--bundle 2026.08.19`) pins an exact past combination, so a reinstall six
|
||||
months from now reproduces today's install rather than tomorrow's.
|
||||
|
||||
---
|
||||
@@ -156,12 +156,12 @@ one file); `doctor`, `update` and `uninstall` are run from it later. Examples be
|
||||
### An illustrative run
|
||||
|
||||
```
|
||||
Runic Gateway installer — bundle 2026.08.04 (protocol 3)
|
||||
Runic Gateway installer — bundle 2026.08.19 (protocol 4)
|
||||
|
||||
ServUO /opt/ServUO (57.4)
|
||||
Shard process not running
|
||||
Overlay servuo-plugins v0.1.1 protocol 3
|
||||
Sidecar uo-link v1.1.0 protocol 3
|
||||
Overlay servuo-plugins v1.0.0 protocol 4
|
||||
Sidecar uo-link v2.0.0 protocol 4
|
||||
|
||||
✓ overlay tarball verified sha256 75dc6d6c…
|
||||
✓ sidecar binary verified sha256 27d491ef…
|
||||
@@ -410,7 +410,7 @@ One manual step remains — connect the website to this sidecar:
|
||||
|
||||
Base URL http://shard.example.com:8080
|
||||
WebSocket URL ws://shard.example.com:8080/ws
|
||||
Protocol version 3
|
||||
Protocol version 4
|
||||
Auth token 4f9c… (also in /etc/runicgateway/sidecar.toml)
|
||||
|
||||
Paste these into Admin → Shard on your Runic Gateway site:
|
||||
@@ -430,7 +430,7 @@ On your site, sign in as an administrator and open **Admin → Shard (uo-link)**
|
||||
| Base URL (REST) | the **Base URL** line |
|
||||
| WebSocket URL (feed) | the **WebSocket URL** line |
|
||||
| Auth token | the **Auth token** line |
|
||||
| Protocol | the **Protocol version** line (`3`) |
|
||||
| Protocol | the **Protocol version** line (`4`) |
|
||||
|
||||
Saving restarts the site's ingest client, so the change takes effect immediately. The token is
|
||||
AES-GCM encrypted at rest and **never returned to any client** — losing it means reading it back
|
||||
@@ -490,7 +490,7 @@ shedding events rather than stalling — which it is designed to do. `[bridge re
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8080/health
|
||||
{"status":"ok","protocol":3,"plugin_connected":true,"database":"ok","uptime":"2m","last_event":"2026-08-04T18:22:10.412Z"}
|
||||
{"status":"ok","protocol":4,"plugin_connected":true,"database":"ok","uptime":"2m","last_event":"2026-08-19T18:22:10.412Z"}
|
||||
```
|
||||
|
||||
`plugin_connected: true` is the one that matters — it is the only value in this whole guide that
|
||||
@@ -509,18 +509,18 @@ The command that makes this supportable. Run it before asking anyone for help
|
||||
first thing a maintainer will want.
|
||||
|
||||
```
|
||||
✓ Install record /etc/runicgateway/install.json (bundle 2026.08.04, installer 1.0.0, …)
|
||||
✓ Install record /etc/runicgateway/install.json (bundle 2026.08.19, installer 0.1.0, …)
|
||||
✓ ServUO found /opt/ServUO (57.4)
|
||||
✓ Overlay in sync 24 files, all hashes match install.json
|
||||
⚠ Patch tier 1 applied — moderation-audit (region-match)
|
||||
✓ uo-link installed uo-link-sidecar 1.1.0 (protocol 3)
|
||||
✓ uo-link installed uo-link-sidecar 2.0.0 (protocol 4)
|
||||
config /etc/runicgateway/sidecar.toml database /var/lib/runicgateway/uo-link.db
|
||||
✓ Service runicgateway-link.service active, enabled as runicgateway
|
||||
✓ Sidecar reachable 127.0.0.1:8080 /health ok, up 6h, database ok
|
||||
✓ Protocol sidecar 3 = overlay manifest 3
|
||||
✓ Protocol sidecar 4 = overlay manifest 4
|
||||
✗ Shard connected no — the shard is running (pid 8123) but has not dialed in
|
||||
✓ Bundle 2026.08.04 — up to date
|
||||
✓ Backups 2026-08-04T09:12:44Z — 3 file(s) replaced by update to bundle 2026.08.04
|
||||
✓ Bundle 2026.08.19 — up to date
|
||||
✓ Backups 2026-08-19T09:12:44Z — 3 file(s) replaced by update to bundle 2026.08.19
|
||||
3 kept in /etc/runicgateway/backups
|
||||
```
|
||||
|
||||
@@ -654,11 +654,11 @@ Use those versions together; that pairing is the only thing CI has verified.
|
||||
### A2. Deploy the plugin overlay
|
||||
|
||||
```bash
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/runicgateway-overlay-0.1.1.tar.gz
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/SHA256SUMS
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v1.0.0/runicgateway-overlay-1.0.0.tar.gz
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v1.0.0/SHA256SUMS
|
||||
sha256sum -c SHA256SUMS --ignore-missing # must say: OK
|
||||
|
||||
tar xzf runicgateway-overlay-0.1.1.tar.gz # → runicgateway-overlay/
|
||||
tar xzf runicgateway-overlay-1.0.0.tar.gz # → runicgateway-overlay/
|
||||
cd runicgateway-overlay
|
||||
cat manifest.json # version, commit, protocol, per-file hashes
|
||||
|
||||
@@ -702,8 +702,8 @@ says `aarch64`); releases from v1.2.0 carry both. Take the version from the bund
|
||||
A1 rather than the one written here.
|
||||
|
||||
```bash
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-linux-x86_64
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/SHA256SUMS
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v2.0.0/uo-link-sidecar-linux-x86_64
|
||||
curl -LO https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v2.0.0/SHA256SUMS
|
||||
sha256sum -c SHA256SUMS --ignore-missing
|
||||
|
||||
sudo install -m 0755 uo-link-sidecar-linux-x86_64 /usr/bin/runicgateway-link
|
||||
@@ -722,8 +722,8 @@ sudo UOLINK_DB_PATH=/var/lib/runicgateway/uo-link.db \
|
||||
```json
|
||||
{
|
||||
"component": "uo-link-sidecar",
|
||||
"version": "1.1.0",
|
||||
"protocol": 3,
|
||||
"version": "2.0.0",
|
||||
"protocol": 4,
|
||||
"config_path": "/etc/runicgateway/sidecar.toml",
|
||||
"config_created": true,
|
||||
"token_generated": true,
|
||||
@@ -859,7 +859,7 @@ path = "uo-link.db" # relative paths resolve against this file's directo
|
||||
|
||||
| Sidecar command | Output |
|
||||
|---|---|
|
||||
| `uo-link-sidecar --version` | `uo-link-sidecar 1.1.0 (protocol 3)` |
|
||||
| `uo-link-sidecar --version` | `uo-link-sidecar 2.0.0 (protocol 4)` |
|
||||
| `uo-link-sidecar --print-config [--config PATH]` | The JSON in [A3](#a3-install-the-sidecar). Provisions on first run. **Contains the token.** |
|
||||
| `uo-link-sidecar --help` | Usage. An unrecognized argument exits `2` rather than starting a sidecar you did not ask for. |
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ $ uo-link-sidecar --print-config --config /etc/runicgateway/sidecar.toml
|
||||
"component": "uo-link-sidecar",
|
||||
"config_created": false,
|
||||
"config_path": "/etc/runicgateway/sidecar.toml",
|
||||
"protocol": 3,
|
||||
"protocol": 4,
|
||||
"shard": { "bind": "127.0.0.1:7788" },
|
||||
"store": { "path": "/var/lib/runicgateway/uo-link.db" },
|
||||
"token_generated": false,
|
||||
"version": "0.1.0",
|
||||
"version": "2.0.0",
|
||||
"web": {
|
||||
"auth_required": true,
|
||||
"auth_token": "c0f04ace…",
|
||||
@@ -55,12 +55,14 @@ That is the same set of values Admin → Shard asks for — base URL and WS URL
|
||||
|
||||
The wire protocol is versioned so a mismatch is caught immediately instead of failing weirdly.
|
||||
|
||||
- Every response carries an **`X-UOLink-Version: 3`** header.
|
||||
- `GET /health` and the WebSocket `ws.hello` frame include `"protocol": 3`.
|
||||
- **Optionally**, send `X-UOLink-Version: 3` on your requests. If it disagrees with the sidecar, the request is rejected **409 Conflict**:
|
||||
The current version is **4**, shipped in sidecar **v2.0.0** and overlay **v1.0.0**.
|
||||
|
||||
- Every response carries an **`X-UOLink-Version: 4`** header.
|
||||
- `GET /health` and the WebSocket `ws.hello` frame include `"protocol": 4`.
|
||||
- **Optionally**, send `X-UOLink-Version: 4` on your requests. If it disagrees with the sidecar, the request is rejected **409 Conflict**:
|
||||
|
||||
```json
|
||||
{ "error": "protocol version mismatch", "sidecar_protocol": 3, "client_protocol": "2" }
|
||||
{ "error": "protocol version mismatch", "sidecar_protocol": 4, "client_protocol": "3" }
|
||||
```
|
||||
|
||||
Pin the version you built against and compare it to the header (or `/health.protocol`) at startup.
|
||||
@@ -74,12 +76,18 @@ ignores unknown kinds keeps working against the live feed, but the three new end
|
||||
sidecar. There is deliberately **no feature-negotiation array** — v3 implies all three kinds, so the
|
||||
version number alone tells you what is available.
|
||||
|
||||
**Upgrading a v2 integration.** The bump is an operator-visible hard break in one direction only: a
|
||||
client still declaring `2` gets a 409 on every protected route and, on the WebSocket, a closed
|
||||
connection on the `ws.hello` mismatch. So update the pinned version at the same time you deploy the
|
||||
v3 sidecar. Nothing that existed in v2 changed shape, so that is the whole migration — the website
|
||||
does it with a one-shot boot migration of its `uo_link_config.protocol` row ([`v3.md`](v3.md) §4.1);
|
||||
a third-party client changes the constant it sends.
|
||||
**v4 (Protocol 4.0)** adds `guild.roster` and `guild.leave`, and grows `GET /guilds` a `roster` key
|
||||
([`v4.md`](v4.md)). Before it, a guild's membership was a *count*; now the members themselves are on
|
||||
the wire. Additive in the same shape as the previous two bumps — nothing that existed in v3 changed,
|
||||
so a v3 consumer that ignores the new kinds and the new key keeps working against a v4 sidecar, once
|
||||
it declares `4`.
|
||||
|
||||
**Upgrading a pinned client.** Every bump is an operator-visible hard break in one direction only: a
|
||||
client still declaring the old number gets a 409 on every protected route and, on the WebSocket, a
|
||||
closed connection on the `ws.hello` mismatch. So update the pinned version at the same time you
|
||||
deploy the new sidecar. Nothing existing has ever changed shape across a bump, so that is the whole
|
||||
migration — the website does it with a one-shot boot migration of its `uo_link_config.protocol` row
|
||||
([`v3.md`](v3.md) §4.1); a third-party client changes the constant it sends.
|
||||
|
||||
---
|
||||
|
||||
@@ -92,7 +100,7 @@ GET /health (no auth)
|
||||
```json
|
||||
{
|
||||
"status": "ok", // "ok" when plugin connected AND db reachable, else "degraded"
|
||||
"protocol": 3,
|
||||
"protocol": 4,
|
||||
"plugin_connected": true, // is the shard link up right now?
|
||||
"database": "ok", // "ok" | "error"
|
||||
"uptime": "3d 12h",
|
||||
@@ -115,7 +123,7 @@ A push-only stream of game events as they happen. You do **not** send commands o
|
||||
**On connect**, the first frame is:
|
||||
|
||||
```json
|
||||
{ "kind": "ws.hello", "protocol": 3 }
|
||||
{ "kind": "ws.hello", "protocol": 4 }
|
||||
```
|
||||
|
||||
**Then** a continuous stream of event frames, each with at least `t` (epoch ms) and `kind`. Route on `kind`.
|
||||
@@ -133,7 +141,7 @@ const ws = new WebSocket(`ws://127.0.0.1:8080/ws?token=${TOKEN}`);
|
||||
ws.onmessage = (m) => {
|
||||
const ev = JSON.parse(m.data);
|
||||
switch (ev.kind) {
|
||||
case "ws.hello": /* check ev.protocol === 1 */ break;
|
||||
case "ws.hello": /* check ev.protocol === 4 */ break;
|
||||
case "mob.login": onLogin(ev); break;
|
||||
case "vendor.sale": onSale(ev); break;
|
||||
case "house.decay": onIdoc(ev); break;
|
||||
@@ -1012,7 +1020,7 @@ sidecar defines no audiences. Deciding who may see what is the consuming site's
|
||||
A typical character page:
|
||||
|
||||
```js
|
||||
const H = { "Authorization": `Bearer ${TOKEN}`, "X-UOLink-Version": "3" };
|
||||
const H = { "Authorization": `Bearer ${TOKEN}`, "X-UOLink-Version": "4" };
|
||||
|
||||
// 1. render the roster
|
||||
const roster = await fetch(`${BASE}/roster/${account}`, { headers: H }).then(r => r.json());
|
||||
|
||||
11
link/v4.md
11
link/v4.md
@@ -1,7 +1,7 @@
|
||||
# Protocol 4 — Guild membership on the wire
|
||||
|
||||
**Status:** Feature-complete on `edge`. All work lands on an `edge` branch in each repo; `edge` → `main` is the v4 cutover.
|
||||
**Date:** 2026-08-17
|
||||
**Status:** **Shipped.** The `edge` → `main` cutover is done and protocol 4 is live — released as uo-link sidecar **v2.0.0** and plugin overlay **v1.0.0**, paired in bundle **2026.08.19**.
|
||||
**Date:** 2026-08-17; cutover 2026-08-19
|
||||
**Codebase:** ServUO 57.4, `<servuo>`, net48 / x64, Expansion **EJ**.
|
||||
**Companion to** [`PLAN.md`](PLAN.md) (1.0 read/event plane), [`PROTOCOL_2.md`](PROTOCOL_2.md) (2.0 provisioning + world-state streams), [`v3.md`](v3.md) (3.0 shard content + the visibility framework), [`INTEGRATION.md`](INTEGRATION.md) (website API).
|
||||
|
||||
@@ -265,5 +265,8 @@ evidence and neither is a clean boot. What was actually run:
|
||||
Step 2 is what caught the reassembly bug in §3.1: every unit test passed through it, because they all
|
||||
exercised a single-frame roster. The case does not arise until a guild exceeds the cap.
|
||||
|
||||
Still outstanding for the cutover: the five-rung shard visibility walk against a live shard, confirming
|
||||
`acct`/`webId` never reach a caller below their rung.
|
||||
**Still outstanding, and the cutover shipped without it:** the five-rung shard visibility walk against
|
||||
a live shard, confirming `acct`/`webId` never reach a caller below their rung. `guild.roster` carries
|
||||
actor objects for every member of every guild, so it is the widest surface any protocol version has
|
||||
added to that check — the walk is worth doing against the released pair even though v4 is now live.
|
||||
No result is recorded here or in [`../website/SHARD_VISIBILITY.md`](../website/SHARD_VISIBILITY.md).
|
||||
|
||||
Reference in New Issue
Block a user