openvibers/openvibe.live SETUP.md
SETUP.md
| 1 | # OpenVibe.Live Setup |
| 2 | |
| 3 | This document describes the OpenVibe.Live runtime setup, local development, and its integration points with the other OpenVibe services. |
| 4 | |
| 5 | ## Architecture at a glance |
| 6 | |
| 7 | OpenVibe.Live (port **3000**) is the streaming front — ingest (RTMP / WHIP / WebRTC / JSMPEG), chat, channels, monetization (Vibes), moderation, and the SPA. Two sibling services do heavy lifting: |
| 8 | |
| 9 | - **OpenVibe.Network** (`openvibe.network`, port **4000**) — SSO/OAuth2 identity provider (client id `live`), RS256 JWTs, URL registry, notifications, and the network-wide **OpenCoins wallet**. Live calls the wallet server-to-server via `OV_NETWORK_INTERNAL_URL` with its service token (client credentials). |
| 10 | - **OpenVibe.Media** (`openvibe.media`, port **4100**) — owns **VODs, clips, pastes, thumbnails, and file storage** (Media API v1). Live records streams by pointing Media at its ingest (RTMP pull / RTP ports 12000-12199), proxies the SPA's `/api/vods`, `/api/clips`, `/api/pastes`, `/api/thumbnails` calls to it (`server/media-client.js`), 302-redirects file payloads to `MEDIA_PUBLIC_URL`, and receives `vod.ready` / `clip.ready` outcomes as OpenVibe.Events deliveries at `POST /internal/media-events`. |
| 11 | |
| 12 | Live keeps locally, in its PostgreSQL database: users/streams/channel state, Vibes (PayPal tipping/cashout), channel points, comments, stream memories + AI state for Media-hosted vods/clips (`vod_ai_state` / `clip_ai_state`), ephemeral live thumbnails, and the song-request queue. |
| 13 | |
| 14 | The shared package is a pinned OpenVibe.Shared release (`"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/vX.Y.Z"` in package.json), installed into `node_modules` by `npm ci` (the host needs outbound HTTPS to codeload.github.com) and served at `/shared/*`. Change it in OpenVibe.Shared and bump the tag; never edit `node_modules`. |
| 15 | |
| 16 | ## Prerequisites |
| 17 | |
| 18 | - Node.js 18 or later, npm. |
| 19 | - FFmpeg (live thumbnails, frame extraction, AI jobs). |
| 20 | - A running **OpenVibe.Network** instance (auth + wallet). |
| 21 | - A running **OpenVibe.Media** instance (VODs/clips/pastes) — Live boots without it, but media features return 502 until it's up. |
| 22 | - Linux is recommended for production (`mediasoup` and `node-media-server` are most reliable there). |
| 23 | |
| 24 | ## Install dependencies |
| 25 | |
| 26 | ```bash |
| 27 | npm install |
| 28 | ``` |
| 29 | |
| 30 | The shared package resolves from `node_modules/openvibe-shared` (the pinned release) — no external workspace layout needed. |
| 31 | |
| 32 | ## Environment configuration |
| 33 | |
| 34 | Copy the environment template into `.env` (production: `/etc/openvibe/live.env`, mode 0600): |
| 35 | |
| 36 | ```bash |
| 37 | cp .env.example .env |
| 38 | ``` |
| 39 | |
| 40 | ### Required values |
| 41 | |
| 42 | - `BASE_URL` — public URL used by the browser client, e.g. `http://localhost:3000`. |
| 43 | - `JWT_SECRET` — app-specific secret for Live's own JWT operations. |
| 44 | - `OV_NETWORK_URL` — public SSO issuer (default `https://openvibe.network`). |
| 45 | - `OV_NETWORK_INTERNAL_URL` — internal Network base (default `http://127.0.0.1:4000`). |
| 46 | - `OV_OAUTH_CLIENT_ID` / `OV_OAUTH_CLIENT_SECRET` — OAuth client (`live`). The same client mints Live's service tokens for Network's `/internal/*` endpoints (client credentials; the shared `INTERNAL_API_KEY` is retired). |
| 47 | - `OV_NETWORK_PUBLIC_KEY` — path to the Network RS256 public key (offline JWT verification). |
| 48 | - `MEDIA_URL` — internal Media API base (default `http://127.0.0.1:4100`). |
| 49 | - `MEDIA_PUBLIC_URL` — public Media host (default `https://openvibe.media`). |
| 50 | - `MEDIA_APP_ID` (`live`) — Live's Media tenant. Media calls authenticate with Live's service token (`OV_OAUTH_CLIENT_ID`/`OV_OAUTH_CLIENT_SECRET`, audience `openvibe.media`, namespace `live`), which needs the `media.object.read`, `media.object.list`, `media.object.upload` and `media.object.delete` grants. |
| 51 | - `MEDIA_EVENTS_SECRET` — the secret of Live's OpenVibe.Events subscriptions to Media's outcomes (`scripts/subscribe-media-events.js`). |
| 52 | |
| 53 | #### Public key for token verification |
| 54 | |
| 55 | OpenVibe.Live verifies RS256 tokens from OpenVibe.Network with openvibe-sdk/auth (`createNetworkKeys`), using the first |
| 56 | key file that exists: |
| 57 | |
| 58 | 1. the path in the `OV_NETWORK_PUBLIC_KEY` environment variable; |
| 59 | 2. `<DATA_DIR>/keys/openvibe-tools-public.pem`; |
| 60 | 3. `/opt/openvibe/openvibe-tools/data/keys/public.pem`. |
| 61 | |
| 62 | With none of them, Live verifies against Network's JWKS (`<OV_NETWORK_INTERNAL_URL>/api/.well-known/jwks`), loaded at boot |
| 63 | and kept fresh, and follows a key rotation on the first token that names a new key. `/api/ready` reports the |
| 64 | `network_key` check either way. A key file pins one key: after Network rotates its key, replace the file. |
| 65 | |
| 66 | ## Database |
| 67 | |
| 68 | Live runs on PostgreSQL (since 2026-10-08). In production, OpenVibe.Host's data role creates the database and roles |
| 69 | (`sudo ovhost data provision live`) and writes `DATABASE_URL` (the pooled runtime role, through PgBouncer) and |
| 70 | `DATABASE_DIRECT_URL` (the owner, for migrations) into the env file; the release applies `migrations/` at boot. |
| 71 | |
| 72 | In development, leave `DATABASE_URL` unset: Live opens an embedded PGlite database under `DATA_DIR` and migrates it. |
| 73 | Nothing to initialize by hand. |
| 74 | |
| 75 | ## Storage directories |
| 76 | |
| 77 | The server writes Live-local state under `data/`: |
| 78 | |
| 79 | - `data/live-thumbs` — ephemeral live-stream thumbnails |
| 80 | - `data/emotes`, `data/avatars`, `data/offline` |
| 81 | - `data/media/cache` — song-request (watch-party) downloads |
| 82 | - `data/keys` |
| 83 | |
| 84 | VOD/clip/paste **files** live in OpenVibe.Media's storage, not here. |
| 85 | |
| 86 | ## Local development setup |
| 87 | |
| 88 | 1. Start OpenVibe.Network locally (port 4000) and OpenVibe.Media (port 4100). |
| 89 | 2. Configure `.env`: |
| 90 | |
| 91 | ```env |
| 92 | PORT=3000 |
| 93 | HOST=0.0.0.0 |
| 94 | NODE_ENV=development |
| 95 | BASE_URL=http://localhost:3000 |
| 96 | OV_NETWORK_URL=http://localhost:4000 |
| 97 | OV_NETWORK_INTERNAL_URL=http://127.0.0.1:4000 |
| 98 | OV_NETWORK_PUBLIC_KEY=./data/keys/openvibe-network-public.pem |
| 99 | MEDIA_URL=http://127.0.0.1:4100 |
| 100 | MEDIA_PUBLIC_URL=http://127.0.0.1:4100 |
| 101 | MEDIA_APP_ID=live |
| 102 | ``` |
| 103 | |
| 104 | 3. Run `npm run dev` (without `DATABASE_URL`, Live migrates an embedded PGlite database under `DATA_DIR`). |
| 105 | |
| 106 | Network's `local-dev` bootstrap seeds the `live` OAuth client with `http://localhost:3000/api/auth/callback`. |
| 107 | |
| 108 | ## Admin/bootstrap behavior |
| 109 | |
| 110 | On startup, the server: |
| 111 | |
| 112 | - refreshes URL registry values from OpenVibe.Network (needs `OV_NETWORK_INTERNAL_URL` + `OV_OAUTH_CLIENT_SECRET`). |
| 113 | - initializes the database and schema. |
| 114 | - seeds/refreshes built-in themes (default theme slug: `vibe`). |
| 115 | - creates an admin user from `ADMIN_USERNAME` / `ADMIN_PASSWORD` if no admin exists (dev defaults `admin` / `changeme123`). |
| 116 | |
| 117 | ## Protocol-specific notes |
| 118 | |
| 119 | ### Recording (all protocols) |
| 120 | |
| 121 | Server-side recording is delegated to OpenVibe.Media: |
| 122 | |
| 123 | - **RTMP** — on publish, Live creates a VOD in Media and asks it to pull `rtmp://127.0.0.1:1935/live/<key>`. |
| 124 | - **WebRTC/WHIP** — Live creates a VOD, requests RTP ingest ports from Media (UDP 12000-12199), and points mediasoup PlainRtpTransports at them. |
| 125 | - **Browser MediaRecorder** — chunk uploads to `/api/vods/stream/:id/chunk` are forwarded to Media's chunks endpoints. |
| 126 | - **JSMPEG** — not recorded (no Media ingest for mpeg-ts push). |
| 127 | |
| 128 | Completion arrives as `media.vod.ready` / `media.clip.ready` events over OpenVibe.Events. |
| 129 | |
| 130 | ### WebRTC / mediasoup |
| 131 | |
| 132 | - `MEDIASOUP_LISTEN_IP`, `MEDIASOUP_ANNOUNCED_IP`, `MEDIASOUP_MIN_PORT` / `MEDIASOUP_MAX_PORT`, `WEBRTC_PORT`. |
| 133 | - `TURN_URL` / `TURN_USERNAME` / `TURN_CREDENTIAL` — required in production for reliable NAT traversal (default `turn:turn.openvibe.live`). |
| 134 | - Viewer media is SFU-only (mediasoup); there is no peer-to-peer fallback. |
| 135 | |
| 136 | ### WHIP |
| 137 | |
| 138 | - `WHIP_PUBLIC_URL` — public ingest URL for WHIP (`https://whip.openvibe.live` in production). |
| 139 | - `WHIP_PUBLIC_URL_ENABLED` — boolean to enable or disable WHIP. |
| 140 | |
| 141 | ### RTMP |
| 142 | |
| 143 | - `RTMP_PORT` (1935), `RTMP_CHUNK_SIZE`. |
| 144 | - `RTMP_HOST` — public ingest hostname shown to streamers (default `ingest.openvibe.live` → `rtmp://ingest.openvibe.live/live`). |
| 145 | |
| 146 | ### JSMPEG |
| 147 | |
| 148 | - `JSMPEG_VIDEO_PORT`, `JSMPEG_AUDIO_PORT`, `JSMPEG_PUBLIC_URL` (TLS relay ports 9710-9789 via nginx stream in production). |
| 149 | |
| 150 | ### Optional production settings |
| 151 | |
| 152 | - `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_MODE` for Vibes cashout (Vibes stay Live-local). |
| 153 | - `LIVE_THUMBS_PATH` for the live thumbnail directory. |
| 154 | |
| 155 | ## Troubleshooting |
| 156 | |
| 157 | - **Auth fails**: `OV_NETWORK_PUBLIC_KEY` missing/invalid, or Network unreachable. |
| 158 | - **Invalid redirect_uri**: local callback URIs not registered on the `live` OAuth client in Network. |
| 159 | - **VOD/clip/paste endpoints return 502**: OpenVibe.Media is down, `MEDIA_URL` is wrong, or Live's service token lacks the `media.object.*` grants. |
| 160 | - **VODs never leave "processing"**: Live's Events subscriptions to `media.*` are missing or `MEDIA_EVENTS_SECRET` does not match them (Events shows the deliveries failing with 401). |
| 161 | - **OpenCoins balance always 0 / spends fail**: `OV_NETWORK_INTERNAL_URL`/`OV_OAUTH_CLIENT_SECRET` wrong, or the user has no linked Network account. |
| 162 | - **CORS rejects browser traffic**: `BASE_URL` set to localhost in production. |
| 163 | - **WebRTC fails**: `mediasoup` could not initialize or TURN is not configured. |
| 164 | - **RTMP fails to start**: port conflict on `RTMP_PORT`. |
| 165 | |
| 166 | ### How to verify local startup |
| 167 | |
| 168 | 1. Run `npm run dev`. |
| 169 | 2. Confirm the server logs show `HTTP server` and WebSocket endpoints. |
| 170 | 3. Confirm `Effective BASE_URL` and `Effective OV_NETWORK_URL` are correct. |
| 171 | 4. Confirm `/api/ready` reports the database check `ok` (the release migrated it at boot). |
| 172 | 5. `curl http://localhost:3000/api/health` returns `{"status":"ok", ...}`. |
| 173 | |
| 174 | ## What this setup does not cover |
| 175 | |
| 176 | - production Nginx / Cloudflare deployment (see `deploy/`). |
| 177 | - real TLS certificate configuration. |
| 178 | - the OpenVibe.Media / OpenVibe.Network services themselves. |
| 179 | |
| 180 | For stream-specific guidance, see `docs/broadcasting.md`. |
| 181 |