deploy(truenas): Core image + compose for M2 WebRTC rollout
Some checks failed
tests / build (push) Failing after 3s
Some checks failed
tests / build (push) Failing after 3s
Adds a dedicated deploy bundle under deploy/truenas/core/ so the real root Core binary — with the M2 WebRTC subsystem wired in — can replace the M1 webrtc-poc stack on the TrueNAS host. - Dockerfile: two-stage build on golang:1.24-alpine3.20 + alpine:3.20 runtime. FFmpeg is bundled so restream processes have their subprocess path ready. Copies the core binary from core/core (Go places the output file inside the core/ package directory because it can't overwrite a directory with a file) plus import and ffmigrate from the repo root. - docker-compose.yml: host-networked Core service, env-driven config (CORE_ADDRESS, CORE_API_AUTH_*, CORE_WEBRTC_ENABLE, CORE_WEBRTC_PUBLIC_IP), with config/ and data/ bind mounts. - README.md: M1→M2 cutover notes, one-time setup, JWT smoke test against /api/v3/whep/:id, and teardown. Verified: make release + make import + make ffmigrate all cross-compile cleanly for linux/amd64; go build ./... and go test ./... pass on the branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b030102611
commit
d96aa70c27
3 changed files with 218 additions and 0 deletions
60
deploy/truenas/core/Dockerfile
Normal file
60
deploy/truenas/core/Dockerfile
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Dragon Fork datarhei Core image (M2 + WebRTC egress).
|
||||||
|
#
|
||||||
|
# Builds the real root Core binary — the one that replaces the M1 PoC
|
||||||
|
# in production. FFmpeg is baked in so restream processes can run the
|
||||||
|
# RTP output legs emitted by the WebRTC subsystem.
|
||||||
|
#
|
||||||
|
# Two-stage:
|
||||||
|
# 1. builder: compile a static Go binary (CGO off — no dynamic libs)
|
||||||
|
# 2. runtime: alpine with ffmpeg for the subprocess path
|
||||||
|
#
|
||||||
|
# Usage via compose:
|
||||||
|
# docker compose -f deploy/truenas/core/docker-compose.yml up -d --build
|
||||||
|
#
|
||||||
|
# The compose file drives configuration via CORE_* env vars — see
|
||||||
|
# README.md in this directory.
|
||||||
|
|
||||||
|
# ---- builder ----
|
||||||
|
# go.mod requires go 1.24; pinning the image keeps Docker's toolchain
|
||||||
|
# download off the hot path and makes the build reproducible.
|
||||||
|
FROM golang:1.24-alpine3.20 AS builder
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
RUN apk add --no-cache git make
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
||||||
|
RUN make release && make import && make ffmigrate
|
||||||
|
|
||||||
|
# ---- runtime ----
|
||||||
|
# Alpine with ffmpeg (Core shells out to it for every restream process).
|
||||||
|
# Scratch isn't an option here because the process manager needs ffmpeg
|
||||||
|
# on PATH.
|
||||||
|
FROM alpine:3.20 AS runtime
|
||||||
|
|
||||||
|
RUN apk add --no-cache ffmpeg tini ca-certificates
|
||||||
|
|
||||||
|
# make release's `-o core` lands the binary inside the core/ Go
|
||||||
|
# package directory (Go cannot overwrite a directory with a file, so
|
||||||
|
# it places the output file _inside_ it). The `import` and `ffmigrate`
|
||||||
|
# Makefile targets cd into app/<name> and write the binary back up to
|
||||||
|
# the repo root with a relative path, so those end up at /src/import
|
||||||
|
# and /src/ffmigrate.
|
||||||
|
COPY --from=builder /src/core/core /core/bin/core
|
||||||
|
COPY --from=builder /src/import /core/bin/import
|
||||||
|
COPY --from=builder /src/ffmigrate /core/bin/ffmigrate
|
||||||
|
COPY --from=builder /src/mime.types /core/mime.types
|
||||||
|
COPY --from=builder /src/run.sh /core/bin/run.sh
|
||||||
|
|
||||||
|
RUN mkdir -p /core/config /core/data
|
||||||
|
|
||||||
|
ENV CORE_CONFIGFILE=/core/config/config.json
|
||||||
|
ENV CORE_STORAGE_DISK_DIR=/core/data
|
||||||
|
ENV CORE_DB_DIR=/core/config
|
||||||
|
|
||||||
|
VOLUME ["/core/data", "/core/config"]
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--", "/core/bin/run.sh"]
|
||||||
|
WORKDIR /core
|
||||||
102
deploy/truenas/core/README.md
Normal file
102
deploy/truenas/core/README.md
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
## Smoke-test via API
|
||||||
|
|
||||||
|
```
|
||||||
|
# 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.
|
||||||
56
deploy/truenas/core/docker-compose.yml
Normal file
56
deploy/truenas/core/docker-compose.yml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Dragon Fork datarhei Core — M2 deployment with WebRTC egress.
|
||||||
|
#
|
||||||
|
# This replaces the M1 webrtc-poc stack. It runs the real root Core
|
||||||
|
# binary with the WebRTC subsystem wired into the restream manager, so
|
||||||
|
# every process whose config has `webrtc.enabled=true` will have its
|
||||||
|
# output fanned out to WHEP subscribers automatically.
|
||||||
|
#
|
||||||
|
# Host networking is required for the same reason as M1: ICE encodes
|
||||||
|
# host:port pairs into SDP candidates, and bridge-mode port mapping
|
||||||
|
# breaks that.
|
||||||
|
#
|
||||||
|
# Copy this file to /mnt/NVME/Docker/dragonfork-core/ alongside a .env:
|
||||||
|
#
|
||||||
|
# PUBLIC_IP=10.0.0.25
|
||||||
|
# API_AUTH_USERNAME=admin
|
||||||
|
# API_AUTH_PASSWORD=change-me-please
|
||||||
|
# API_AUTH_JWT_SECRET=<32+ random bytes, base64>
|
||||||
|
#
|
||||||
|
# Then:
|
||||||
|
# docker compose up -d --build
|
||||||
|
# docker compose logs -f
|
||||||
|
|
||||||
|
services:
|
||||||
|
core:
|
||||||
|
build:
|
||||||
|
context: ../../.. # repo root (where go.mod lives)
|
||||||
|
dockerfile: deploy/truenas/core/Dockerfile
|
||||||
|
container_name: dragonfork-core
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
environment:
|
||||||
|
# --- API ---
|
||||||
|
CORE_ADDRESS: ":${CORE_HTTP_PORT:-8080}"
|
||||||
|
CORE_API_AUTH_ENABLE: "true"
|
||||||
|
CORE_API_AUTH_USERNAME: "${API_AUTH_USERNAME:?set in .env}"
|
||||||
|
CORE_API_AUTH_PASSWORD: "${API_AUTH_PASSWORD:?set in .env}"
|
||||||
|
CORE_API_AUTH_JWT_SECRET: "${API_AUTH_JWT_SECRET:?set in .env}"
|
||||||
|
|
||||||
|
# --- WebRTC egress ---
|
||||||
|
CORE_WEBRTC_ENABLE: "true"
|
||||||
|
CORE_WEBRTC_PUBLIC_IP: "${PUBLIC_IP:?set in .env}"
|
||||||
|
# Leave NAT1To1_IPS empty unless you need multiple advertised IPs.
|
||||||
|
# CORE_WEBRTC_NAT_1_TO_1_IPS: "10.0.0.25 203.0.113.10"
|
||||||
|
|
||||||
|
# --- Storage ---
|
||||||
|
# Let the volumes below provide durable paths; defaults are fine.
|
||||||
|
|
||||||
|
# --- Logging ---
|
||||||
|
CORE_LOG_LEVEL: "${LOG_LEVEL:-info}"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- ./config:/core/config
|
||||||
|
- ./data:/core/data
|
||||||
|
|
||||||
|
# No ports: host networking exposes whatever the process binds.
|
||||||
|
# The WHEP endpoint lives at /api/v3/whep/:id on CORE_HTTP_PORT.
|
||||||
Loading…
Reference in a new issue