feat(shard)!: declare wire protocol 3
The site's declared version is the admin-set uo_link_config.protocol column, so the sidecar's PROTOCOL_VERSION 2 -> 3 bump has to be matched here or every REST call 409s and uoLinkSocket closes the WS on the ws.hello mismatch. Five places carry the number and all five move together: the column default, the model's DEFAULT_PROTOCOL (what a site with nothing saved yet declares), the two `config.protocol || 1` fallbacks in uoLinkClient/uoLinkSocket -- unreachable today, but an unset value quietly sending 1 is exactly the confusing 409 the version check exists to prevent -- the admin form's initial value, and the documented env default. The boot migration is the only subtle part. schema.sql is re-run on EVERY boot, and `protocol` is admin-editable, so a bare UPDATE would silently un-pin an operator who had deliberately pinned an older sidecar in Admin -> Shard. It is therefore gated on a marker row in `settings`, written after the UPDATE: the first boot on this build migrates, every later boot is a no-op. `protocol < 3` rather than `= 2` picks up an install still on the old default of 1, which could not have been talking to a v2 sidecar anyway. A fresh install has no row to update and just gets the marker plus the new column default. Verified against the local MariaDB through ensureSchema (the production path): 2 -> 3 with the marker written and the column default now 3; pinned back to 2 by hand, re-ran, and it STAYED 2 -- the one-shot property holds. 673 server tests, 47 client tests, client build green. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -117,7 +117,10 @@ BOT_INTERNAL_KEY=change-me-to-a-long-random-string
|
||||
# token). These URLs are just defaults; the admin can override them at runtime.
|
||||
UOLINK_BASE_URL=http://127.0.0.1:8080
|
||||
UOLINK_WS_URL=ws://127.0.0.1:8080/ws
|
||||
UOLINK_PROTOCOL=1
|
||||
# Wire protocol this build speaks (3 = Protocol 3.0). Only a fallback for a site
|
||||
# with nothing saved yet — the admin panel's pinned value wins — but set it lower
|
||||
# if you deliberately run an older sidecar.
|
||||
UOLINK_PROTOCOL=3
|
||||
|
||||
# ─── Push notifications (M7) — self-hosted ntfy UnifiedPush relay ───
|
||||
# The `ntfy` compose service and the backend's push fan-out (opt-in notifications
|
||||
|
||||
@@ -155,7 +155,7 @@ export default function ShardAdmin() {
|
||||
const [baseUrl, setBaseUrl] = useState('')
|
||||
const [wsUrl, setWsUrl] = useState('')
|
||||
const [token, setToken] = useState('')
|
||||
const [protocol, setProtocol] = useState(1)
|
||||
const [protocol, setProtocol] = useState(3)
|
||||
const [enabled, setEnabled] = useState(false)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [msg, setMsg] = useState('')
|
||||
@@ -172,7 +172,7 @@ export default function ShardAdmin() {
|
||||
if (!initializedRef.current) {
|
||||
setBaseUrl(c.baseUrl || '')
|
||||
setWsUrl(c.wsUrl || '')
|
||||
setProtocol(c.protocol || 1)
|
||||
setProtocol(c.protocol || 3)
|
||||
setEnabled(c.enabled)
|
||||
initializedRef.current = true
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ CREATE TABLE IF NOT EXISTS uo_link_config (
|
||||
base_url VARCHAR(255) NULL,
|
||||
ws_url VARCHAR(255) NULL,
|
||||
auth_token_enc TEXT NULL,
|
||||
protocol INT NOT NULL DEFAULT 1,
|
||||
protocol INT NOT NULL DEFAULT 3,
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'disconnected',
|
||||
status_detail VARCHAR(500) NULL,
|
||||
@@ -1397,3 +1397,19 @@ ALTER TABLE mobile_refresh_tokens ADD COLUMN IF NOT EXISTS last_used_at DATETIME
|
||||
-- trust token. A boolean only — the token is returned over that app→server call
|
||||
-- and never persisted here (only its sha256 lands in trusted_devices).
|
||||
ALTER TABLE mobile_auth_sessions ADD COLUMN IF NOT EXISTS trust_device TINYINT(1) NOT NULL DEFAULT 0;
|
||||
|
||||
-- Protocol 3.0 cutover: this build speaks wire protocol 3 (world.ruleset,
|
||||
-- points.board, vendor.listing), so the pinned version an existing install
|
||||
-- carries has to move with it — a 2 against a v3 sidecar 409s every REST call
|
||||
-- and closes the WS on ws.hello. MODIFY fixes the column default for installs
|
||||
-- created before the bump (idempotent, like the other MODIFYs here).
|
||||
ALTER TABLE uo_link_config MODIFY COLUMN protocol INT NOT NULL DEFAULT 3;
|
||||
-- The row itself is admin-editable, and schema.sql runs on EVERY boot, so this
|
||||
-- must be one-shot: an operator who deliberately pins an older sidecar in
|
||||
-- Admin → Shard has to stay pinned. The marker row in `settings` is what makes
|
||||
-- it fire once — written after the UPDATE, and on a fresh install (no
|
||||
-- uo_link_config row yet) it is simply written with nothing to update.
|
||||
UPDATE uo_link_config SET protocol = 3
|
||||
WHERE id = 1 AND protocol < 3
|
||||
AND NOT EXISTS (SELECT 1 FROM settings WHERE `key` = 'uo_link_protocol_3_migrated');
|
||||
INSERT IGNORE INTO settings (`key`, value) VALUES ('uo_link_protocol_3_migrated', '1');
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
const db = require('./uoLinkConfig.db')
|
||||
const secretBox = require('../../utils/secretBox')
|
||||
|
||||
const DEFAULT_PROTOCOL = Number(process.env.UOLINK_PROTOCOL) || 1
|
||||
// The wire protocol this build speaks (link/sidecar/src/main.rs PROTOCOL_VERSION).
|
||||
// Only used before an admin has saved anything — the stored row wins once it exists,
|
||||
// and UOLINK_PROTOCOL still overrides for an operator running an older sidecar.
|
||||
const DEFAULT_PROTOCOL = Number(process.env.UOLINK_PROTOCOL) || 3
|
||||
|
||||
function toSafe(row) {
|
||||
if (!row) {
|
||||
|
||||
@@ -58,7 +58,7 @@ async function call(path, { method = 'GET', body } = {}) {
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-UOLink-Version': String(config.protocol || 1),
|
||||
'X-UOLink-Version': String(config.protocol || 3),
|
||||
}
|
||||
if (config.token) headers.Authorization = `Bearer ${config.token}`
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ async function connect() {
|
||||
return
|
||||
}
|
||||
|
||||
state.protocol = config.protocol || 1
|
||||
state.protocol = config.protocol || 3
|
||||
helloSeen = false
|
||||
const url = buildUrl(config.wsUrl, config.token)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user