Run an HA RTMP server cluster
+Enable optional multi-node clustering, bootstrap and join voters, expose control/media ports, and operate nodes from the API or panel — with current alpha limits stated up front.
+ Read the clustering guide → +diff --git a/README.md b/README.md index e16f69e..8e188db 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ Source for [OpenRTMP.org](https://openrtmp.org), the public website for the Open OpenRTMP includes: - [`librtmp2`](https://github.com/OpenRTMP/librtmp2) — Rust RTMP/RTMPS and Enhanced RTMP protocol library with a C-compatible FFI -- [`librtmp2-server`](https://github.com/OpenRTMP/librtmp2-server) — self-hosted RTMP/RTMPS server with SQLite, stream keys, REST API, and statistics -- [`librtmp2-server-panel`](https://github.com/OpenRTMP/librtmp2-server-panel) — web UI for stream lifecycle and live monitoring +- [`librtmp2-server`](https://github.com/OpenRTMP/librtmp2-server) — self-hosted RTMP/RTMPS server with SQLite, stream keys, REST API, statistics, and optional HA clustering +- [`librtmp2-server-panel`](https://github.com/OpenRTMP/librtmp2-server-panel) — web UI for stream lifecycle, live monitoring, and cluster-aware operations All projects are active alpha software. The website intentionally avoids hard-coded release numbers where a package registry or GitHub release page can remain the source of truth. diff --git a/docs/index.php b/docs/index.php index 35942cd..b454d3f 100644 --- a/docs/index.php +++ b/docs/index.php @@ -1,7 +1,7 @@ @@ -23,6 +23,7 @@
/api/v1/streams with Bearer token auth (axum, port 8080)/stats?key=<stats_key> (JSON) and /stats-nginx?key=<stats_key> (nginx-rtmp-compatible XML)CLUSTER_ENABLED, off by default) with OpenRaft state replication and a media mesh — see HA clusteringBuild and run natively:
+Build and run natively (standalone):
git clone https://github.com/OpenRTMP/librtmp2-server.git
cd librtmp2-server
cargo build --release
@@ -155,6 +157,37 @@
-H "Content-Type: application/json" \
-d '{"id":"mystream","name":"My Live Stream","app":"live"}'
+ From server 0.2.0, librtmp2-server can run as a multi-node cluster. Clustering is off by default; standalone behavior is unchanged when CLUSTER_ENABLED=false. Published Docker images build with the cluster Cargo feature; runtime still defaults to standalone.
Architecture in short:
+| Plane | Default port | Role |
|---|---|---|
| Control | 1940 | OpenRaft RPC, join/admin, heartbeats, StatsProxy |
| Media | 1941 | Inter-node frame relay, subscribe, init-cache |
| RTMP / HTTP | 1935 / 8080 | Client publish/play and admin API (unchanged) |
Minimal bootstrap (first voter):
+CLUSTER_ENABLED=true
+CLUSTER_NODE_ID=1
+CLUSTER_BOOTSTRAP=true
+CLUSTER_SECRET=<long-random-secret>
+CLUSTER_ADVERTISE_ADDR=10.0.0.1:1940
+CLUSTER_MEDIA_ADVERTISE_ADDR=10.0.0.1:1941
+ Additional nodes join with an empty database and CLUSTER_JOIN=<existing-control-addr>, then promote learners to voters via POST /api/v1/cluster/nodes/{id}/promote.
Authenticated cluster APIs include GET /api/v1/cluster, /nodes, /streams, plus drain/resume/promote/remove. Full configuration, limitations, and operator notes live in the server repo's docs/clustering.md and the site guide Run an HA RTMP cluster.
Native builds that need clustering must compile with the feature:
+cargo build --release --features cluster
+
librtmp2-server-panel is a Flask web UI that talks to the server's REST API. It does not implement RTMP itself — it manages streams, copies URLs, and polls live stats.
/api/v1/streams/stats?key=...cluster.enabled=true: quorum overview, node drain/resume/remove, stream owner/epoch placementREQUIRE_LOGIN=True by default)The panel does not participate in Raft. Point it at any healthy synchronized node; durable admin writes are forwarded inside the cluster. Standalone servers hide the Cluster navigation automatically.
Key environment variables (see .env.example in the panel repo):
1935 | RTMP ingest / playback (RTMP_BIND) |
1936 | RTMPS ingest / playback (RTMPS_BIND) — only when TLS_ENABLED=true; not exposed in the default compose file (uncomment 1936:1936 there) |
1940 | Cluster control plane (CLUSTER_BIND) — only when CLUSTER_ENABLED=true |
1941 | Cluster media mesh (CLUSTER_MEDIA_BIND) — only when clustering is enabled |
8080 | HTTP API, stats, health check (HTTP_BIND) |
8000 | Web panel |
To enable RTMPS alongside plaintext RTMP, set LRTMP2_TLS_ENABLED=true (or TLS_ENABLED=true in .env), mount cert/key files, expose port 1936, and set RTMPS_BIND=0.0.0.0:1936 as in librtmp2-server/docker-compose.yml. The panel shows rtmps:// URLs only when GET /api/v1/health reports rtmps_enabled: true (and uses LRTMP2_RTMPS_PORT, default 1936).
For a multi-node cluster, expose 1940 and 1941 between peers, set the CLUSTER_* variables described under HA clustering, and keep each node on its own SQLite volume. See the clustering guide for bootstrap and join steps.
Only the public librtmp2 crate interface is the intended stable API surface. Everything under src/**/* that is not pub may change freely between releases.
librtmp2 follows SemVer but remains on 0.x while in alpha. Semantic-versioning guarantees begin at 1.0.0. Pin a crates.io version (currently 0.3.1) if you depend on a specific API shape.
librtmp2-server (currently 0.1.4) and librtmp2-server-panel are also alpha — REST API shapes, environment variable names, and Docker images may evolve.
librtmp2-server (currently 0.2.0) and librtmp2-server-panel (currently 0.2.0) are also alpha — REST API shapes, environment variable names, and Docker images may evolve. Prefer GitHub releases and image tags over hard-coding versions from this page.
git clone https://github.com/OpenRTMP/librtmp2-server.git
cd librtmp2-server
cargo build --release
+# Optional HA clustering:
+# cargo build --release --features cluster
cp .env.example .env
LRTMP2_DB=./server.db ./target/release/librtmp2-server
- The API token is generated on first startup unless supplied as the real LRTMP2_API_TOKEN process environment variable.
The API token is generated on first startup unless supplied as the real LRTMP2_API_TOKEN process environment variable. Docker images already include the cluster feature; runtime clustering stays off until you set CLUSTER_ENABLED=true. See the HA clustering guide.
The homepage separates the library and operator paths, while the guides cover Docker, OBS, RTMPS, and Enhanced RTMP.
+The homepage separates the library and operator paths, while the guides cover Docker, OBS, RTMPS, HA clustering, and Enhanced RTMP.
Enable optional multi-node clustering, bootstrap and join voters, expose control/media ports, and operate nodes from the API or panel — with current alpha limits stated up front.
+ Read the clustering guide → +| Statistics | JSON plus nginx-compatible XML | Classic XML statistics endpoint with XSL presentation |
| Embeddable library | Rust crate and C-compatible FFI | No equivalent standalone protocol crate |
| HLS, recording, exec, push | Not built into the current server | Common nginx-rtmp module features |
| Multi-node HA | Optional OpenRaft + media mesh clustering (alpha, off by default) | Usually external load balancers, shared storage, or custom push topology |
| Modern RTMP work | Explicit focus on RTMPS and Enhanced RTMP building blocks | Primarily traditional RTMP module workflows |
Optional multi-node mode replicates durable stream state with OpenRaft and relays live media between peers over a media mesh. Standalone single-node operation remains the default.
+0.2.0. Test failover, publisher reconnect, and firewall paths thoroughly. Invalid cluster configuration fails startup hard — there is no silent fallback to standalone.With CLUSTER_ENABLED=true, each node keeps its own SQLite file while durable mutations (streams, viewers, tokens, ownership) go through Raft. Live frames leave the publisher owner over the media mesh so players can attach to other healthy nodes.
Published Docker images already compile with --features cluster. Native builds need the Cargo feature explicitly. Runtime still defaults to CLUSTER_ENABLED=false.
| Plane | Default | Purpose |
|---|---|---|
| Control | 1940/tcp | Raft, join/admin, heartbeats, StatsProxy |
| Media | 1941/tcp | Frame relay, subscribe, init-cache |
| RTMP | 1935/tcp | Publisher and player connections (unchanged) |
| HTTP API | 8080/tcp | Admin REST API and health |
Expose 1940 and 1941 between cluster peers. Keep the admin API restricted; RTMP exposure follows the same rules as a standalone deploy.
Start the first node with a shared secret (at least 16 characters) and advertise addresses peers can dial:
+CLUSTER_ENABLED=true
+CLUSTER_NODE_ID=1
+CLUSTER_BOOTSTRAP=true
+CLUSTER_SECRET=<long-random-secret>
+CLUSTER_BIND=0.0.0.0:1940
+CLUSTER_MEDIA_BIND=0.0.0.0:1941
+CLUSTER_ADVERTISE_ADDR=10.0.0.1:1940
+CLUSTER_MEDIA_ADVERTISE_ADDR=10.0.0.1:1941
+ Existing standalone streams, viewers, and the API token are seeded into Raft on first bootstrap. Docker mappings for a clustered node typically add:
+ports:
+ - "1935:1935"
+ - "8080:8080"
+ - "1940:1940"
+ - "1941:1941"
+
+ Each joiner needs an empty database (no prior streams or raft_* state) and the same secret:
CLUSTER_ENABLED=true
+CLUSTER_NODE_ID=2
+CLUSTER_JOIN=10.0.0.1:1940
+CLUSTER_SECRET=<same-secret>
+LRTMP2_DB=/data/node2.db
+ 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.
+ +Useful authenticated endpoints:
+| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/cluster | Leader, term, quorum, load |
| GET | /api/v1/cluster/nodes | Peer list and health states |
| GET | /api/v1/cluster/streams | Owner, epoch, mesh subscriptions |
| POST | .../nodes/{id}/drain | Mark node DRAINING |
| POST | .../nodes/{id}/resume | Mark node READY |
| DELETE | .../nodes/{id} | Remove voter (releases its owners) |
When health reports cluster.enabled=true, the web panel shows a Cluster page with quorum status, node actions (drain/resume/remove), and per-stream owner/epoch placement. Point the panel at any healthy synchronized node — it does not participate in Raft.
stream_init_snapshot before playback works.CLUSTER_TLS_ENABLED with cert/key/CA for production peer links; certificate subjects must embed lrtmp2-node-{id}.The canonical operator reference is docs/clustering.md in the server repository.
Validate a single-node Docker stack first. Enable clustering only after RTMP publish/play and the panel work cleanly.
+ +1935/tcp8000/tcp8080/tcp1940/tcp / 1941/tcpCLUSTER_ENABLED=true; see the HA guideUse modern JSON statistics or nginx-rtmp-compatible XML for existing monitoring and automation tools.
Run multiple server nodes with replicated stream state and a media mesh for playback across nodes. Standalone mode stays the default.
+That separation makes it clear which project to adopt and where to contribute.
RTMP/RTMPS application layer with SQLite, keys, REST API, and monitoring endpoints.
+RTMP/RTMPS application layer with SQLite, keys, REST API, monitoring endpoints, and optional HA clustering.
Flask web UI for creating streams, copying URLs, and monitoring live publisher/player statistics.
+Flask web UI for creating streams, copying URLs, monitoring live statistics, and managing cluster nodes when HA is enabled.