diff --git a/installer/INSTALL.md b/installer/INSTALL.md index 26993b5..dffa9f1 100644 --- a/installer/INSTALL.md +++ b/installer/INSTALL.md @@ -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 ` (e.g. `--bundle 2026.08.04`) pins an exact past combination, so a reinstall six +- `--bundle ` (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. | diff --git a/link/INTEGRATION.md b/link/INTEGRATION.md index 25f00d5..806a2f7 100644 --- a/link/INTEGRATION.md +++ b/link/INTEGRATION.md @@ -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()); diff --git a/link/v4.md b/link/v4.md index 9789d9e..5699d89 100644 --- a/link/v4.md +++ b/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, ``, 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).