From 26cf6e346346e7b4ce199635edc23bfffe690fbf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 09:32:48 +0000 Subject: [PATCH] docs(guides): require CLUSTER_JOIN_PROOF in the HA clustering join steps The server has required an admin join proof for a fresh cluster join since librtmp2-server 0109d98 (12 Aug), so the guide's join snippet no longer worked as written: a join without CLUSTER_JOIN_PROOF is rejected before the join request is sent. Add the join-proof minting step, CLUSTER_JOIN_PROOF and the advertise addresses the proof is bound to, and the POST /api/v1/cluster/join-proof row in the endpoint table. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HyaN2Pdss6XpJ2MWgxfhy5 --- guides/rtmp-server-ha-clustering/index.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/guides/rtmp-server-ha-clustering/index.php b/guides/rtmp-server-ha-clustering/index.php index d51df35..798728e 100644 --- a/guides/rtmp-server-ha-clustering/index.php +++ b/guides/rtmp-server-ha-clustering/index.php @@ -67,16 +67,27 @@ - "1941:1941"

2. Join additional nodes

-

Each joiner needs an empty database (no prior streams or raft_* state) and the same secret:

+

Each joiner needs an empty database (no prior streams or raft_* state) and the same secret. A fresh join also needs a one-time join proof — CLUSTER_SECRET on its own can no longer enroll a learner. Mint the proof on an existing member with the normal API token, using the exact addresses the new node will advertise:

+
curl -sS -X POST http://10.0.0.1:8080/api/v1/cluster/join-proof \
+  -H "Authorization: Bearer <api-token>" \
+  -H "Content-Type: application/json" \
+  -d '{"node_id": 2, "control_addr": "10.0.0.2:1940", "media_addr": "10.0.0.2:1941"}'
+

The response carries a proof string. Start the joining node with it, plus the advertise addresses the proof was minted for:

CLUSTER_ENABLED=true
 CLUSTER_NODE_ID=2
 CLUSTER_JOIN=10.0.0.1:1940
+CLUSTER_JOIN_PROOF=<join-proof>
 CLUSTER_SECRET=<same-secret>
+CLUSTER_BIND=0.0.0.0:1940
+CLUSTER_MEDIA_BIND=0.0.0.0:1941
+CLUSTER_ADVERTISE_ADDR=10.0.0.2:1940
+CLUSTER_MEDIA_ADVERTISE_ADDR=10.0.0.2:1941
 LRTMP2_DB=/data/node2.db
+

A fresh join without CLUSTER_JOIN_PROOF is rejected before the join request is even sent. The proof is bound to the node ID and to both advertised addresses, so mint a new one if any of those values change. Restarting a node that already has local Raft state resumes instead of joining and needs no new proof.

Joined nodes start as learners. After catch-up, promote to voter:

curl -X POST http://10.0.0.1:8080/api/v1/cluster/nodes/2/promote \
   -H "Authorization: Bearer <api-token>"
-

Do not copy a live SQLite file from another node and join — that creates conflicting Raft state. To reseed, delete the node's DB files and join again.

+

Do not copy a live SQLite file from another node and join — that creates conflicting Raft state. To reseed, delete the node's DB files, mint a fresh join proof, and join again.

3. Operate from the API or panel

Useful authenticated endpoints:

@@ -86,6 +97,7 @@ GET/api/v1/clusterLeader, term, quorum, load GET/api/v1/cluster/nodesPeer list and health states GET/api/v1/cluster/streamsOwner, epoch, mesh subscriptions + POST/api/v1/cluster/join-proofMint a proof authorizing one fresh node join POST.../nodes/{id}/drainMark node DRAINING POST.../nodes/{id}/resumeMark node READY DELETE.../nodes/{id}Remove voter (releases its owners)