openvibers/openrestream Commits

README and STATUS.json are current (roadmap WS-U task 3): a Capabilities section lists the seven openre.* capabilities and what OpenRe calls at Events, Live (live.lineage.resolve, also added to the grants list) and Media; Security notes point to SECURITY.md; Deploying says how an API and a worker deploy roll back. Stale facts corrected: the status line and launch rule say the RTMP ingest is public on 1936 and Live is wired (phase A of docs/cutover.md, 2026-09-23), not loopback-only and inert; Depends on names the current pins (contracts v0.49.0, sdk v0.12.0, shared v1.22.0). STATUS.json names openvibe-contracts v0.49.0, fixes the livePatch note, gains features and is updated 2026-09-28.

OP OpenVibers committed 28 Sept 2026 · c77ec2a9a1 · 2 files, +48 −18


Changes

ddiff --git a/README.md b/README.md
iindex 5ce246f..3741408 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Ingest and restream: stream definitions, keys, sessions, transport workers, outputs and output health.
--**Status:** alpha (roadmap Wave 7). Tested end to end with real RTMP. Deployed internally on `openvibe-ovh` since 2026-09-23 (API on 127.0.0.1:4500, RTMP ingest bound to 127.0.0.1:1936, one rehearsal broadcast passed), but not launched: no broadcaster uses it (all 102 Live slots are still `ingest_authority=live`), and Live's side of the integration is deployed but inert until `OPENRE_*` is set in Live. The domain keeps its placeholder page on OpenVibe.Sites until the launch rule below is met.
++**Status:** alpha (roadmap Wave 7). Tested end to end with real RTMP. Deployed internally on `openvibe-ovh` since 2026-09-23 (API and coordinator on 127.0.0.1:4500; RTMP ingest public on port 1936 at `ingest.openre.stream` since the cutover runbook's phase A on 2026-09-23; Live wired with `OPENRE_URL` and an `openre.session.*` subscription; one rehearsal broadcast passed), but not launched: no broadcaster uses it, since every Live slot is still ingested by Live until its per-slot cutover (`docs/cutover.md` phase B). The domain keeps its placeholder page on OpenVibe.Sites until the launch rule below is met.
**Domain:** `openre.stream` (UI + API), `ingest.openre.stream` (RTMP, DNS only)
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §10, §10.5, §15.10; ADR-009 (binding), ADR-004, ADR-006, ADR-007.
**License:** AGPL-3.0 (same as every OpenVibe service).
@@@ -32,7 +32,8 @@ The ingest/restream control plane and runtime extracted from OpenVibe.Live. Live
- OpenVibe.Network (JWKS for service tokens and user JWTs; OAuth client `openre`; client-credentials tokens for Events/Media)
- OpenVibe.Events (event relay; optional: rows wait in the outbox)
- OpenVibe.Media (recording requests; optional: sessions work without it)
--- OpenVibe.Contracts (ids, problem+json, capability checks), OpenVibe.SDK v0.2.2 (outbox, token client), OpenVibe.Shared (chrome)
++- OpenVibe.Contracts v0.49.0 (ids, problem+json, capability checks), OpenVibe.SDK v0.12.0 (outbox, token client, per-actor limits), OpenVibe.Shared v1.22.0 (chrome), pinned by release tarball
++- OpenVibe.Live (`live.lineage.resolve`: which channel a Live-linked stream belongs to)
- node-media-server 2.7.4 (the RTMP session code Live runs) and the system ffmpeg
## What is ported, per protocol
@@@ -79,6 +80,23 @@ npm test # every test/*.test.js; the RTMP e2e test need
Node 22 (≥ 22.12 for `reusePort`): `fnm exec --using=22.22.1 npm test`.
++## Capabilities
++
++Implemented here (the service manifest's `capabilities`, audience `openvibe.openre`; routes under
++[Auth](#auth)): `openre.stream.read`, `openre.stream.write`, `openre.key.rotate`,
++`openre.session.read`, `openre.session.end`, `openre.output.read` and `openre.output.write`.
++
++Called elsewhere, as the service principal `openre` (client credentials from Network):
++
++| Service | Grant | Why |
++|---|---|---|
++| OpenVibe.Events | `events.event.publish` | the coordinator's outbox relay (`openre.*` events) |
++| OpenVibe.Live | `live.lineage.resolve` | the channel a Live-linked stream definition belongs to |
++| OpenVibe.Media | the `live` tenant key today (`MEDIA_API_KEY`); `media.object.upload` once `OPENRE_MEDIA_AUTH=service` | recording requests |
++
++The full grant list, including what Live needs to call OpenRe, is under "Grants the lead adds in
++Network" below.
++
## Auth
Services call with an OpenVibe.Network client-credentials token (audience `openvibe.openre`), verified offline against the Network JWKS; each route checks one capability. A service may send `X-OV-Subject: usr_…` to act for one owner (it is then limited to that owner's streams). Browsers/owners use the Network user JWT (`ov_token` cookie from `/auth/login`, or Bearer) and act on their own streams; Network role `admin` is staff (read all, end sessions).
@@@ -93,12 +111,13 @@ Services call with an OpenVibe.Network client-credentials token (audience `openv
| `openre.output.read` | `GET /api/v1/streams/:id/destinations`, `GET /api/v1/sessions/:id/outputs`, `GET /api/v1/destinations/:id/logs`, `GET /api/v1/outputs/:id/logs` |
| `openre.output.write` | `POST /api/v1/streams/:id/destinations`, `PATCH`/`DELETE /api/v1/destinations/:id`, `POST /api/v1/destinations/:id/test|start|stop` |
--The ids were proposed in [docs/capabilities-proposal/](docs/capabilities-proposal/) with the service manifest ([docs/service-manifest-proposal.json](docs/service-manifest-proposal.json)) and are registered in `openvibe-contracts` since v0.16.0 (this repository pins v0.33.0). `server/auth/index.js` grants them with the contracts rule (exact id or `family.*`) and defers to `capabilities.check()` for every id the installed contracts know. Errors are RFC 9457 problem+json with a stable `code`.
++The ids were proposed in [docs/capabilities-proposal/](docs/capabilities-proposal/) with the service manifest ([docs/service-manifest-proposal.json](docs/service-manifest-proposal.json)) and are registered in `openvibe-contracts` since v0.16.0 (this repository pins v0.49.0). `server/auth/index.js` grants them with the contracts rule (exact id or `family.*`) and defers to `capabilities.check()` for every id the installed contracts know. Errors are RFC 9457 problem+json with a stable `code`.
**Grants the lead adds in Network** (`[client, capability, audience]`):
- `[live, openre.stream.read, openvibe.openre]`, `[live, openre.stream.write, openvibe.openre]`, `[live, openre.key.rotate, openvibe.openre]`, `[live, openre.session.read, openvibe.openre]`
- `[openre, events.event.publish, openvibe.events]`
++- `[openre, live.lineage.resolve, openvibe.live]` (the channel lineage of Live-linked streams)
- `[live, events.subscription.manage, openvibe.events]` if Live creates its own `openre.session.*` subscription (or an operator creates it)
- Media, only when `OPENRE_MEDIA_AUTH=service`: `[openre, media.object.upload, openvibe.media]` with namespace `live` — and Media must first name that capability on its VOD create/ingest/finalize/delete routes (today they accept only the tenant app key; see "Recording").
- OAuth client `openre` (authorization code + client credentials) with redirect `https://openre.stream/auth/callback`.
@@@ -126,6 +145,8 @@ When a session is live and its definition says `recording_mode` `vod` or `clips`
## Security notes
++Reporting a vulnerability: [SECURITY.md](SECURITY.md).
++
- Ingest keys: 256-bit random, `ork_` + 43 base64url characters, SHA-256 at rest, compared by indexed lookup inside the RTMP handshake. Never logged (only the last four characters), never in an event, never in a URL after the handshake (the publish is renamed to the session id).
- Destination keys and SRT passphrases: AES-256-GCM (`OPENRE_SECRETS_KEY`, rotation via `OPENRE_SECRETS_KEY_PREVIOUS`); APIs return `****` + last four only; ffmpeg command lines are logged redacted.
- SSRF (plan anti-goal 13): destination URLs must be `rtmp://`, `rtmps://` or `srt://` with a hostname, no credentials, and no loopback/private/link-local/CGNAT/multicast host — checked on write and again, with DNS resolution, right before ffmpeg starts (`OPENRE_DEST_ALLOW_PRIVATE` opts out, e.g. a LAN box). Live allowed private hosts; the migration holds such destinations with the reason recorded.
@@@ -164,6 +185,10 @@ sudo deploy/scripts/deploy.sh status # deploy-legacy.sh
- A **worker deploy** starts `openre-rtmp-ingest@<sha>` and `openre-restream-worker@<sha>`; older instances are disabled (not stopped) and exit on their own when drained. Never `systemctl restart` a worker instance during a broadcast; `systemctl stop` starts a drain and waits up to 30 min (`TimeoutStopSec`), then kills.
- Worker instances are named after the release. `deploy.sh workers <sha>` for a release that already runs a generation does nothing. An env change that workers read, such as `OPENRE_RTMP_BIND`, takes effect with the next release's generation (docs/cutover.md A3).
- **Restore drills** (`ovhost drill openre`, OpenVibe.Host) start only `openre-api`, with `OPENRE_DRILL=1`, on a restored copy of the database. In drill mode the API serves reads and refuses writes, `/play/` and sign-in with 503 `openre.drill_read_only`. The coordinator and every worker exit before they open the database. The event relay and Media calls are off (`test/drill-mode.test.js`). Never set `OPENRE_DRILL` in `/etc/openvibe/openre.env`; the preflight's `env` check fails if it is set.
++- Rollback: when `openre-api` is not ready within 60 s ovhost switches `current` back to the previous
++ release by itself; afterwards `sudo deploy/scripts/deploy.sh rollback [<sha>]` (`ovhost rollback
++ openre`). Workers roll back by starting the previous generation (`deploy.sh workers <sha>`); the
++ coordinator drains the newer one. The schema code only adds, so an older release reads a newer database.
- First install: create the `openre` OAuth client and grants in Network, `/etc/openvibe/openre.env` (0600), the certificate for `openre.stream`, DNS for `openre.stream` (proxied) and `ingest.openre.stream` (DNS only), firewall 1936/tcp, then `release`, `api`, `workers`, and `systemctl enable --now openre-api openre-session-coordinator`.
## Live integration (patch)
@@@ -239,8 +264,8 @@ JavaScript; real persistence and end-to-end workflows; capability and event regi
sitemap/robots/feed behaviour; acceptance tests proving the advertised functionality. Today the
runtime, identity integration, server-rendered routes, persistence, capability registration (contracts v0.16.0),
the migration strategy and the acceptance tests are in place, and the service is deployed
--internally (loopback only). An independent security review, a public ingest port and the first
--slot cutover are not.
++internally with its public ingest port open. An independent security review and the first slot
++cutover are not.
---
ddiff --git a/STATUS.json b/STATUS.json
iindex 2cca835..c60e58e 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -8,7 +8,7 @@
"port": 4500,
"rtmpPort": 1936,
"domain": "openre.stream",
-- "contracts": "openvibe-contracts v0.33.0 (openvibe-sdk v0.5.0, openvibe-shared v1.5.1)",
++ "contracts": "openvibe-contracts v0.49.0 (openvibe-sdk v0.12.0, openvibe-shared v1.22.0)",
"protocols": {
"rtmp": "ingest ported (openre-rtmp-ingest)",
"restream": "RTMP source to RTMP/RTMPS/SRT destinations ported (openre-restream-worker)",
@@@ -16,18 +16,23 @@
"webrtc-sfu": "not ported (worker interface only)",
"jsmpeg": "not ported (worker interface only)"
},
-- "capabilities": [
-- "openre.stream.read",
-- "openre.stream.write",
-- "openre.key.rotate",
-- "openre.session.read",
-- "openre.session.end",
-- "openre.output.read",
-- "openre.output.write"
-- ],
++ "capabilities": ["openre.stream.read", "openre.stream.write", "openre.key.rotate", "openre.session.read", "openre.session.end", "openre.output.read", "openre.output.write"],
"capabilitiesRegistered": true,
-- "livePatch": "applied in Live f0ca18b and deployed, but inert: live.env has no OPENRE_URL/OPENRE_PUBLIC_URL/OPENRE_EVENTS_SECRET and there is no Events subscription openre.session.* -> Live; 102 of 102 Live slots have ingest_authority=live, so no broadcaster uses OpenRe",
++ "livePatch": "applied in Live f0ca18b and deployed; Live wired on 2026-09-23 (OPENRE_URL and the openre.session.* subscription, docs/cutover.md phase A); every Live slot keeps ingest_authority=live until its per-slot cutover (phase B), so no broadcaster uses OpenRe yet",
"deployed": true,
-- "deployment": "Production release e0dc1b46fd99 (2026-09-23 19:23 UTC): API + coordinator on 127.0.0.1:4500, RTMP ingest public on 0.0.0.0:1936 (ingest.openre.stream, DNS-only; reachable from outside), Live wired (OPENRE_URL, openre.session.* subscription), preflight all PASS, restore drill passed. No slot switched yet: every slot is still ingested by Live until the per-slot cutover (docs/cutover.md phase B).",
-- "updated": "2026-09-23"
++ "deployment": "Deployed with `sudo ovhost deploy openre` (OpenVibe.Host strategy release-layout since the 2026-09-27 cutover; deploy/scripts/deploy.sh is the wrapper and keeps workers/status/prune): API + coordinator on 127.0.0.1:4500, RTMP ingest public on 0.0.0.0:1936 (ingest.openre.stream, DNS-only; reachable from outside since 2026-09-23), Live wired (OPENRE_URL, openre.session.* subscription), preflight all PASS, restore drill passed. No slot switched yet: every slot is still ingested by Live until the per-slot cutover (docs/cutover.md phase B).",
++ "updated": "2026-09-28",
++ "features": [
++ "stream definitions owned by a Network subject, with typed references",
++ "ingest keys hashed at rest, shown once, rotation with grace",
++ "RTMP ingest workers with generations, leases and drains that survive API deploys",
++ "RTMP/RTMPS/SRT restream with output health and a circuit breaker",
++ "SSRF-checked destinations with sealed keys",
++ "recording requests to OpenVibe.Media",
++ "HTTP-FLV playback descriptors",
++ "outbox events openre.session.*, openre.output.*, openre.recording.requested, openre.key.rotated",
++ "migration from Live without importing old keys",
++ "restore-drill read-only mode",
++ "per-actor limits (openvibe-sdk/limits)"
++ ]
}