diff --git a/deploy/truenas/core/Dockerfile b/deploy/truenas/core/Dockerfile
index 0e51269..f1f9166 100644
--- a/deploy/truenas/core/Dockerfile
+++ b/deploy/truenas/core/Dockerfile
@@ -39,9 +39,20 @@ FROM node:21-alpine3.20 AS ui-builder
ARG RESTREAMER_UI_REF=v1.14.0
RUN apk add --no-cache git
WORKDIR /ui
+
+# 1. Pull upstream restreamer-ui at the pinned tag.
RUN git clone --depth=1 --branch ${RESTREAMER_UI_REF} \
- https://github.com/datarhei/restreamer-ui.git . \
- && yarn install --frozen-lockfile --network-timeout 600000 \
+ https://github.com/datarhei/restreamer-ui.git .
+
+# 2. Layer Wild Dragon overlays on top of the upstream tree before the
+# build runs. apply-overlay.sh does the rsync + targeted seds; see
+# deploy/truenas/core/ui-overlay/apply-overlay.sh for the contract.
+COPY deploy/truenas/core/ui-overlay /overlay
+RUN OVERLAY=/overlay UI=/ui /overlay/apply-overlay.sh
+
+# 3. Install + build. PUBLIC_URL=./ keeps asset references relative so
+# the bundle is portable across mount paths.
+RUN yarn install --frozen-lockfile --network-timeout 600000 \
&& PUBLIC_URL="./" GENERATE_SOURCEMAP=false yarn build
# ---- runtime ----
diff --git a/deploy/truenas/core/ui-overlay/apply-overlay.sh b/deploy/truenas/core/ui-overlay/apply-overlay.sh
new file mode 100755
index 0000000..02e2343
--- /dev/null
+++ b/deploy/truenas/core/ui-overlay/apply-overlay.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+# apply-overlay.sh — Wild Dragon reskin patches applied to a freshly
+# cloned datarhei/restreamer-ui tree. Two phases:
+#
+# 1. File overlay: rsync the contents of $OVERLAY/{public,src} on top
+# of the upstream working tree. Whole-file replacements only —
+# simple and idempotent.
+#
+# 2. Targeted in-place sed for one-line UI strings that aren't worth
+# a whole-file overlay (the header title, a few welcome strings).
+# Each pattern is anchored to a unique surrounding context so a
+# future upstream rename doesn't silently rewrite the wrong line.
+#
+# Caller: the Dockerfile's ui-builder stage. Expects:
+# $OVERLAY = /overlay (the COPY destination)
+# $UI = /ui (the cloned upstream source root)
+#
+# Idempotent on a single source tree (rerunning is a no-op).
+
+set -eu
+
+OVERLAY="${OVERLAY:-/overlay}"
+UI="${UI:-/ui}"
+
+echo "wilddragon-overlay: layering $OVERLAY -> $UI"
+
+# Phase 1 — file copies. -L follows any future symlinks, -p preserves
+# perms, -R recursive. We deliberately avoid --delete: the upstream
+# tree must stay intact except for the files we override.
+for sub in public src; do
+ if [ -d "$OVERLAY/$sub" ]; then
+ cp -RLp "$OVERLAY/$sub/." "$UI/$sub/"
+ fi
+done
+
+# Phase 2 — targeted seds. Each replacement is wrapped in a check so
+# the script fails loudly if upstream changed the line we're patching
+# (rather than silently no-op'ing and shipping un-rebranded UI).
+patch_line() {
+ file="$1"; needle="$2"; replacement="$3"
+ if ! grep -qF "$needle" "$file"; then
+ echo "wilddragon-overlay: ERROR — pattern not found in $file:"
+ echo " $needle"
+ exit 1
+ fi
+ # Use awk for safe literal substitution (sed's regex would mishandle
+ # special chars in the replacement).
+ tmp="$(mktemp)"
+ awk -v n="$needle" -v r="$replacement" '
+ index($0, n) { sub(n, r); }
+ { print }
+ ' "$file" > "$tmp"
+ mv "$tmp" "$file"
+ echo "wilddragon-overlay: patched $(basename "$file") — $needle -> $replacement"
+}
+
+patch_line "$UI/src/Header.js" \
+ 'Restreamer' \
+ 'Wild Dragon'
+
+# Welcome view top-of-page card.
+patch_line "$UI/src/views/Welcome.js" \
+ 'title="Welcome to Restreamer v2"' \
+ 'title="Welcome to Wild Dragon"'
+
+patch_line "$UI/src/views/Settings.js" \
+ 'title="Welcome to Restreamer v2"' \
+ 'title="Welcome to Wild Dragon"'
+
+echo "wilddragon-overlay: done."
diff --git a/deploy/truenas/core/ui-overlay/public/favicon.ico b/deploy/truenas/core/ui-overlay/public/favicon.ico
new file mode 100644
index 0000000..c5bf589
Binary files /dev/null and b/deploy/truenas/core/ui-overlay/public/favicon.ico differ
diff --git a/deploy/truenas/core/ui-overlay/public/index.html b/deploy/truenas/core/ui-overlay/public/index.html
new file mode 100644
index 0000000..4465026
--- /dev/null
+++ b/deploy/truenas/core/ui-overlay/public/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+ Wild Dragon
+
+
+
+
+
+
diff --git a/deploy/truenas/core/ui-overlay/public/logo192.png b/deploy/truenas/core/ui-overlay/public/logo192.png
new file mode 100644
index 0000000..8985006
Binary files /dev/null and b/deploy/truenas/core/ui-overlay/public/logo192.png differ
diff --git a/deploy/truenas/core/ui-overlay/public/logo512.png b/deploy/truenas/core/ui-overlay/public/logo512.png
new file mode 100644
index 0000000..da15e57
Binary files /dev/null and b/deploy/truenas/core/ui-overlay/public/logo512.png differ
diff --git a/deploy/truenas/core/ui-overlay/public/manifest.json b/deploy/truenas/core/ui-overlay/public/manifest.json
new file mode 100644
index 0000000..90a0fef
--- /dev/null
+++ b/deploy/truenas/core/ui-overlay/public/manifest.json
@@ -0,0 +1,13 @@
+{
+ "short_name": "Wild Dragon",
+ "name": "Wild Dragon — Live Streaming",
+ "icons": [
+ { "src": "favicon.ico", "sizes": "64x64 32x32 16x16", "type": "image/x-icon" },
+ { "src": "logo192.png", "type": "image/png", "sizes": "192x192" },
+ { "src": "logo512.png", "type": "image/png", "sizes": "512x512" }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#0d0e12",
+ "background_color": "#0d0e12"
+}
diff --git a/deploy/truenas/core/ui-overlay/src/misc/Logo/images/logo.svg b/deploy/truenas/core/ui-overlay/src/misc/Logo/images/logo.svg
new file mode 100644
index 0000000..f7d2242
--- /dev/null
+++ b/deploy/truenas/core/ui-overlay/src/misc/Logo/images/logo.svg
@@ -0,0 +1,24 @@
+
diff --git a/deploy/truenas/core/ui-overlay/src/misc/Logo/images/rs-logo.svg b/deploy/truenas/core/ui-overlay/src/misc/Logo/images/rs-logo.svg
new file mode 100644
index 0000000..5842917
--- /dev/null
+++ b/deploy/truenas/core/ui-overlay/src/misc/Logo/images/rs-logo.svg
@@ -0,0 +1,19 @@
+
diff --git a/deploy/truenas/core/ui-overlay/src/misc/Logo/index.js b/deploy/truenas/core/ui-overlay/src/misc/Logo/index.js
new file mode 100644
index 0000000..ee3c4e2
--- /dev/null
+++ b/deploy/truenas/core/ui-overlay/src/misc/Logo/index.js
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import makeStyles from '@mui/styles/makeStyles';
+
+import company_logo from './images/logo.svg';
+
+const useStyles = makeStyles((theme) => ({
+ Logo: {
+ height: 27,
+ },
+}));
+
+export default function Logo(props) {
+ const classes = useStyles();
+
+ let link = 'https://forge.wilddragon.net/zgaetano/datarhei-dragonfork-core';
+
+ // eslint-disable-next-line no-useless-escape
+ return (
+
+
+
+ );
+}
diff --git a/deploy/truenas/core/ui-overlay/src/misc/Logo/rsLogo.js b/deploy/truenas/core/ui-overlay/src/misc/Logo/rsLogo.js
new file mode 100644
index 0000000..be2a60f
--- /dev/null
+++ b/deploy/truenas/core/ui-overlay/src/misc/Logo/rsLogo.js
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import makeStyles from '@mui/styles/makeStyles';
+
+import company_logo from './images/rs-logo.svg';
+
+const useStyles = makeStyles((theme) => ({
+ Logo: {
+ height: 95,
+ },
+}));
+
+export default function Logo(props) {
+ const classes = useStyles();
+
+ let link = 'https://forge.wilddragon.net/zgaetano/datarhei-dragonfork-core';
+
+ // eslint-disable-next-line no-useless-escape
+ return (
+
+
+
+ );
+}