deploy: bundle the official Datarhei Restreamer UI
Some checks are pending
ci / vet + build (push) Waiting to run
ci / race tests (push) Blocked by required conditions
ci / WebRTC smoke (5-viewer fanout) (push) Blocked by required conditions
ci / WebRTC latency p95 gate (push) Blocked by required conditions

Replaces the placeholder Dragon Fork landing page at / with the real
React SPA — the same UI that ships in upstream's datarhei/restreamer
image. Operators get the full process management dashboard, log
viewer, restream config, and so on.

Implementation: a new Docker stage 'ui-builder' (node:21-alpine3.20)
clones datarhei/restreamer-ui at a pinned tag (v1.14.0), runs
'yarn install + yarn build' with PUBLIC_URL="./" so all asset
references are relative, and the runtime stage pulls /ui/build into
/core/static. The existing seed-data.sh script then copies it into
/core/data on first boot.

Stacking order in /core/static:
  1. UI bundle from ui-builder — provides index.html, the SPA bundle
     and assets, _player, _playersite, etc.
  2. Dragon Fork deploy/static/* — currently only whep-player.html;
     the placeholder index.html was removed so the UI's wins.

Pinned to v1.14.0 (the most recent tagged restreamer-ui release)
rather than 'main' for reproducible builds. Bumping the pin is a
one-line ARG override.

Image size: ~+25MB compressed (Restreamer UI bundle is ~3MB
gzipped, plus the build-stage layer overhead until pruned).

UI-side configuration: the SPA defaults to talking to the
same-origin /api endpoints, which is exactly what we want when
serving from Core. No '?address=' query string needed on the URL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-05-03 12:58:51 +00:00
parent 45f39a9132
commit 26991ec463
2 changed files with 28 additions and 74 deletions

View file

@ -27,6 +27,23 @@ COPY . .
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
RUN make release && make import && make ffmigrate
# ---- ui-builder ----
# Builds the official Datarhei Restreamer UI (React 18 + MUI). Pinned
# to a specific tag so reproducible. PUBLIC_URL=./ makes asset
# references relative — the bundle then works when served from / or
# any subdirectory under Core's static-disk filesystem.
#
# Pulling from the public github mirror keeps the Forgejo runner's
# network footprint small; no auth required for clone.
FROM node:21-alpine3.20 AS ui-builder
ARG RESTREAMER_UI_REF=v1.14.0
RUN apk add --no-cache git
WORKDIR /ui
RUN git clone --depth=1 --branch ${RESTREAMER_UI_REF} \
https://github.com/datarhei/restreamer-ui.git . \
&& yarn install --frozen-lockfile --network-timeout 600000 \
&& PUBLIC_URL="./" GENERATE_SOURCEMAP=false yarn build
# ---- 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
@ -47,11 +64,17 @@ 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
# Dragon Fork landing page + browser WHEP player. Seeded into
# /core/data on first boot by /core/bin/seed-data.sh below; the seed
# is a no-op when the operator has already put content in /core/data.
COPY --from=builder /src/deploy/truenas/core/static/ /core/static/
COPY --from=builder /src/deploy/truenas/core/seed-data.sh /core/bin/seed-data.sh
# Static content for /core/data, seeded on first boot by seed-data.sh.
# Stacking order:
# 1. Restreamer UI bundle (the React SPA — gives us index.html)
# 2. Dragon Fork extras (whep-player.html, etc.) — won't overwrite
# the UI's index.html (seed-data is no-clobber).
#
# The result: GET / serves the official Restreamer dashboard, and
# /whep-player.html serves the standalone WHEP smoke player.
COPY --from=ui-builder /ui/build/ /core/static/
COPY --from=builder /src/deploy/truenas/core/static/ /core/static/
COPY --from=builder /src/deploy/truenas/core/seed-data.sh /core/bin/seed-data.sh
RUN chmod +x /core/bin/seed-data.sh && mkdir -p /core/config /core/data

View file

@ -1,69 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Datarhei — Dragon Fork</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root { color-scheme: dark; --fg:#e7e7ea; --bg:#0d0e12; --accent:#ff6633; --muted:#8b8e98; --panel:#1a1c22; }
* { box-sizing: border-box; }
body { margin:0; font:15px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:var(--bg); color:var(--fg); min-height:100vh; }
header { padding:1.5rem 2rem; border-bottom:1px solid #232530; }
header h1 { margin:0; font-size:1.2rem; letter-spacing:0.02em; }
header h1 .accent { color:var(--accent); }
header .subtitle { color:var(--muted); font-size:0.85rem; margin-top:0.3rem; }
main { max-width:840px; margin:0 auto; padding:2rem; }
h2 { font-size:1.05rem; margin-top:2rem; color:var(--muted); text-transform:uppercase; letter-spacing:0.06em; }
.card { background:var(--panel); border-radius:10px; padding:1.25rem 1.5rem; margin-bottom:1rem; }
.card a { color:var(--accent); text-decoration:none; font-weight:600; }
.card a:hover { text-decoration:underline; }
.card p { color:var(--muted); margin:0.4rem 0 0; font-size:0.9rem; }
code { background:#0d0e12; padding:0.1rem 0.4rem; border-radius:4px; font:0.85em ui-monospace,Menlo,Consolas,monospace; }
footer { padding:2rem; text-align:center; color:var(--muted); font-size:0.8rem; }
</style>
</head>
<body>
<header>
<h1>Datarhei <span class="accent">Dragon Fork</span></h1>
<div class="subtitle">a fork of <a href="https://github.com/datarhei/core" style="color:var(--muted)">datarhei/core</a> with native WebRTC (WHEP) egress</div>
</header>
<main>
<h2>Quick links</h2>
<div class="card">
<a href="/api/swagger/index.html">API documentation (Swagger UI)</a>
<p>Full HTTP API including <code>/api/v3/process</code>, <code>/api/v3/whep/{id}</code>, RTMP / SRT / config / metrics. Most endpoints require a JWT — issue one via <code>POST /api/login</code>.</p>
</div>
<div class="card">
<a href="/whep-player.html">Browser WHEP player</a>
<p>Self-contained subscriber for any process whose <code>config.webrtc.enabled = true</code>. Paste the WHEP URL and a JWT; press Subscribe.</p>
</div>
<h2>About this build</h2>
<div class="card" id="about">Loading…</div>
<h2>How to add a WebRTC stream</h2>
<div class="card">
<p style="color:var(--fg)">Create a process with <code>"webrtc": { "enabled": true }</code>. Once it starts, <code>POST /api/v3/whep/&lt;process-id&gt;</code> takes an SDP offer and returns an SDP answer.</p>
</div>
</main>
<footer>Datarhei — Dragon Fork &middot; Apache License 2.0 &middot; Built on datarhei Core + Pion WebRTC</footer>
<script>
fetch('/api')
.then(r => r.json())
.then(d => {
const el = document.getElementById('about');
const v = d.version || {};
el.innerHTML = '<p style="color:var(--fg)">' +
'<strong>' + (d.fork || d.app) + '</strong> &middot; ' +
'instance <code>' + (d.name || '?') + '</code> &middot; ' +
'version <code>' + (v.number || '?') + '</code> &middot; ' +
'commit <code>' + ((v.repository_commit || '?').slice(0,8)) + '</code>' +
'</p>';
})
.catch(() => {
document.getElementById('about').innerHTML =
'<p>Status panel needs an authenticated request. Visit <a href="/api/swagger/index.html">Swagger</a> to log in.</p>';
});
</script>
</body>
</html>