T-HUB/apps/media-worker/src/gate/binary.ts
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

85 lines
3.6 KiB
TypeScript

/**
* Native helper binaries (src/gate/rust, std only): `frame-gate` (the channel's
* clock master) and `source-bus` (per-source raw frame fan-out).
*
* Resolution order, first hit wins:
* 1. `MEDIA_FRAME_GATE_BIN` / `MEDIA_SOURCE_BUS_BIN`: an explicit path per binary;
* 2. `MEDIA_HELPER_DIR/<name>`: the packaged directory (the image builds the crate
* with `scripts/build-helpers.sh` and sets this; see runs/wp4/s2b-report.md item 5);
* 3. the crate's `target/release/<name>` (development checkouts and tests).
*
* Nothing builds at runtime in production. `ensureHelperBinaries()` is for tests
* and the spike/smoke scripts: it builds the crate offline when a binary is
* missing or older than its sources.
*/
import { spawnSync } from "node:child_process";
import { existsSync, readdirSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
export type HelperBinary = "frame-gate" | "source-bus";
const ENV: Record<HelperBinary, string> = { "frame-gate": "MEDIA_FRAME_GATE_BIN", "source-bus": "MEDIA_SOURCE_BUS_BIN" };
/** The crate directory, from `src/gate` (tsx) or `dist/gate` (compiled). */
export function helperCrateDir(): string {
const local = resolve(__dirname, "rust");
return existsSync(join(local, "Cargo.toml")) ? local : resolve(__dirname, "../../src/gate/rust");
}
function candidates(name: HelperBinary): string[] {
return [
process.env[ENV[name]],
process.env.MEDIA_HELPER_DIR ? join(process.env.MEDIA_HELPER_DIR, name) : undefined,
join(helperCrateDir(), "target/release", name),
].filter((path): path is string => typeof path === "string" && path.length > 0);
}
export function resolveHelperBinary(name: HelperBinary): string {
const found = candidates(name).find((path) => existsSync(path));
if (!found) throw new Error(`${name} binary not found: set ${ENV[name]} or MEDIA_HELPER_DIR, or run scripts/build-helpers.sh`);
return found;
}
function newestSource(crate: string): number {
const files = ["Cargo.toml", "Cargo.lock"].map((f) => join(crate, f));
const walk = (dir: string): void => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) walk(path);
else if (entry.name.endsWith(".rs")) files.push(path);
}
};
walk(join(crate, "src"));
return Math.max(...files.filter((f) => existsSync(f)).map((f) => statSync(f).mtimeMs));
}
/** Development binaries that are missing or older than the crate sources. Env/packaged binaries are taken as given. */
export function staleHelpers(): HelperBinary[] {
const crate = helperCrateDir();
if (!existsSync(join(crate, "Cargo.toml"))) return [];
const newest = newestSource(crate);
return (Object.keys(ENV) as HelperBinary[]).filter((name) => {
if (process.env[ENV[name]] || process.env.MEDIA_HELPER_DIR) return false;
const path = join(crate, "target/release", name);
return !existsSync(path) || statSync(path).mtimeMs < newest;
});
}
/**
* Tests/scripts: build the crate if a development binary is missing or stale.
* `--offline --locked`: no network, and the (dependency-free) lockfile must not change.
* Concurrent callers are serialized by cargo's own build-directory lock.
*/
export function ensureHelperBinaries(): boolean {
if (staleHelpers().length > 0) {
const built = spawnSync("cargo", ["build", "--release", "--offline", "--locked", "--manifest-path", join(helperCrateDir(), "Cargo.toml")], { stdio: "ignore" });
if (built.status !== 0) return false;
}
try {
resolveHelperBinary("frame-gate");
resolveHelperBinary("source-bus");
return true;
} catch {
return false;
}
}