datarhei-dragonfork-core/deploy/truenas/core
ZGaetano 353fa0f3f3
Some checks failed
ci / vet + build (push) Failing after 5m7s
ci / race tests (push) Has been skipped
ci / WebRTC smoke (5-viewer fanout) (push) Has been skipped
ci / WebRTC latency p95 gate (push) Has been skipped
seed-data.sh: always refresh static/ and index.html from image
The React bundle hash changes every time the UI is rebuilt. With the old
no-clobber approach, a redeployed container kept serving the old bundle
because the static/ directory already existed on the data volume.

Fix: always overwrite index.html, asset-manifest.json, and the entire
static/ subdirectory from /core/static on every container start. These
are build artifacts (not operator-edited content) so overwriting is safe.
All other top-level entries (channels/, config/, etc.) remain no-clobber.
2026-05-09 17:21:05 -04:00
..
grafana feat(deploy): add Grafana WebRTC health dashboard 2026-05-06 15:59:56 -04:00
prom feat(deploy): add WebRTC Prometheus alert rules 2026-05-06 15:59:11 -04:00
static gui: redesign WebRTC admin page with Wild Dragon brand 2026-05-09 12:27:08 -04:00
ui-overlay feat(branding): replace placeholder logo192 with real Wild Dragon logo 2026-05-03 17:25:42 -04:00
docker-compose.yml feat(deploy): add Prometheus + Grafana observability stack (closes #11) 2026-05-06 16:00:15 -04:00
Dockerfile fix(docker): chmod apply-overlay.sh before execution (exit 126) 2026-05-09 16:25:28 -04:00
README.md docs(deploy): document the GUI surface (Restreamer UI + Wild Dragon WebRTC admin) 2026-05-03 16:33:48 -04:00
seed-data.sh seed-data.sh: always refresh static/ and index.html from image 2026-05-09 17:21:05 -04:00

TrueNAS deploy — datarhei Core (M2, WebRTC-in-Core)

Host-networked Docker stack that runs the real root Core binary with the M2 WebRTC egress subsystem wired in. This replaces the M1 webrtc-poc stack — WebRTC is now a first-class output alongside RTMP/SRT/HLS.

What changed from M1

M1 (webrtc-poc) M2 (this stack)
Standalone cmd/webrtc-poc binary Full Core with restream, HTTP API, storage
One hard-coded stream id Every restream process can opt into WebRTC
Single UDP ingest, PT-split forwarding Two UDP ports per process, per-track
Plain /whep/:id on a side port /api/v3/whep/:id on the JWT-protected API
No auth JWT (same creds as the rest of Core)

Prereqs

  • Docker on the TrueNAS host (TrueNAS SCALE includes it)
  • LAN or public IP that clients can reach (set in .env as PUBLIC_IP)
  • Admin credentials for Core's API
  • FFmpeg is bundled in the image — no host install required

One-time setup

sudo mkdir -p /mnt/NVME/Docker/dragonfork-core
cd /mnt/NVME/Docker/dragonfork-core

# Pull the repo (or sync deploy files) onto the host. The compose
# build `context:` points at the repo root.
git clone https://forgejo.wilddragon.net/zgaetano/datarhei-dragonfork-core.git
cd datarhei-dragonfork-core/deploy/truenas/core

cat > .env <<EOF
PUBLIC_IP=10.0.0.25
CORE_HTTP_PORT=8080
API_AUTH_USERNAME=admin
API_AUTH_PASSWORD=$(openssl rand -base64 24)
API_AUTH_JWT_SECRET=$(openssl rand -base64 48)
LOG_LEVEL=info
EOF

mkdir -p config data

Run

docker compose up -d --build
docker compose logs -f

You should see Core come up logging all configured listeners, including a line from the WebRTC component confirming the subsystem is enabled.

The first build takes ~5 minutes — it compiles Core from source AND builds the React UI bundle. Subsequent rebuilds are faster (Docker layer cache).

GUI surfaces

Once the stack is up, three browser-reachable UIs ship out of the box:

URL What it is
http://<host>:8080/ The full Restreamer UI (rebranded "Wild Dragon"). Manage processes, configure ingests, set up RTMP/SRT/HLS outputs, view logs. The standard Datarhei admin experience.
http://<host>:8080/wilddragon-webrtc.html Wild Dragon WebRTC admin. Sign in, pick a process, click "Enable WebRTC". The page restarts the process so the new RTP output legs go live, then surfaces the WHEP URL with a one-click jump to the smoke player. The fastest path from "I want WebRTC on this stream" to "the smoke player is rendering it."
http://<host>:8080/whep-player.html Standalone WHEP subscriber (the smoke player). ICE / codec / bitrate diagnostics, JWT input, shareable URLs. Use to verify WebRTC actually works after enabling it.
http://<host>:8080/api/swagger/index.html Swagger API docs. Same auth. Hit the WHEP endpoints directly when scripting.

The Restreamer UI doesn't (yet) have a WebRTC checkbox in its process editor — that's why the standalone admin page exists. A proper UI fork that adds WebRTC controls inline is tracked in issue #15.

End-to-end smoke test

1. Open http://<host>:8080/  (Restreamer UI). Sign in with admin / <API_AUTH_PASSWORD>.
2. Create a new "Source" with type RTMP. Note the RTMP push URL it shows.
3. Push your test source to that URL (OBS, ffmpeg, etc.). Confirm it
   shows "running" in the UI.
4. Open http://<host>:8080/wilddragon-webrtc.html. Sign in with the same creds.
5. Click "Enable WebRTC" on the process you just created.
6. Click the "open ↗" link next to the WHEP URL to load the smoke player.
7. Click "Subscribe" in the smoke player. Within ~1s you should see your
   RTMP source rendering as WebRTC.

If step 7 hangs, the most common cause is PUBLIC_IP in .env not matching what the browser can actually reach (host firewall, wrong LAN IP, etc.). Check the WHEP smoke player's log panel — it'll surface the ICE state transitions.

Smoke-test via API directly

# Issue a JWT against the admin creds from .env:
TOKEN=$(curl -s -X POST -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<from .env>"}' \
  http://10.0.0.25:8080/api/login | jq -r '.access_token')

# Probe the WHEP endpoint — should 404 for an unknown id.
curl -i -H "Authorization: Bearer $TOKEN" \
  -X POST http://10.0.0.25:8080/api/v3/whep/nope
# → HTTP/1.1 404 Not Found

# Create a process with WebRTC enabled, send RTMP to its input, then
# subscribe the Pion whep-client to /api/v3/whep/<process-id>.

Cutting over from the M1 PoC

The M1 webrtc-poc stack is independent; it binds its own ports. You can run both side-by-side during the cutover:

# Stop the M1 stack when you're ready to retire it:
cd /mnt/NVME/Docker/dragonfork-webrtc-poc
docker compose down

Teardown

docker compose down

Security notes

  • The WHEP endpoint is mounted under /api/v3, which is JWT-protected. That's the M2 posture — WHEP clients (browsers) need a token. M3 adds per-process signed-URL tokens so embeds don't require admin credentials.
  • The binary runs as root inside the container; if you need an unpriv user, mount volumes owned by a fixed UID and add a user: directive. This matches how the upstream datarhei/core image ships.
  • Put Caddy or nginx in front for TLS. The media itself is DTLS-SRTP-encrypted regardless.
  • The Wild Dragon WebRTC admin page (/wilddragon-webrtc.html) talks to the same JWT-protected API. The token is held in localStorage and cleared when you click "Sign out". If you've configured Core's API to require auth — which you should — this page is gated by it.