# 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.
