mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-07 09:56:40 +02:00
209 lines
5.9 KiB
JavaScript
209 lines
5.9 KiB
JavaScript
const { spawn, spawnSync } = require("child_process");
|
|
const path = require("path");
|
|
const fs = require("fs");
|
|
|
|
const backendDir = path.resolve(__dirname, "backend");
|
|
const isWindows = process.platform === "win32";
|
|
const configuredBasePython = String(process.env.BACKEND_BASE_PYTHON || process.env.PYTHON || "").trim();
|
|
const configuredVenvDir = String(process.env.BACKEND_VENV_DIR || "").trim();
|
|
const canonicalVenvDir = path.join(backendDir, "venv");
|
|
const venvMarkerPath = path.join(backendDir, ".venv-dir");
|
|
|
|
function venvPythonPath(dir) {
|
|
return isWindows
|
|
? path.join(dir, "Scripts", "python.exe")
|
|
: path.join(dir, "bin", "python3");
|
|
}
|
|
|
|
function readPersistedVenvDir() {
|
|
try {
|
|
const value = fs.readFileSync(venvMarkerPath, "utf8").trim();
|
|
if (!value) {
|
|
return "";
|
|
}
|
|
return path.isAbsolute(value) ? value : path.join(backendDir, value);
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function persistSelectedVenv(pythonBin) {
|
|
const envDir = path.dirname(path.dirname(pythonBin));
|
|
const relativeDir = path.relative(backendDir, envDir);
|
|
if (!relativeDir || relativeDir.startsWith("..") || path.isAbsolute(relativeDir)) {
|
|
return;
|
|
}
|
|
try {
|
|
fs.writeFileSync(venvMarkerPath, `${relativeDir}\n`, "utf8");
|
|
} catch {
|
|
// Best effort only. Startup should still succeed if the marker cannot be updated.
|
|
}
|
|
}
|
|
|
|
const explicitVenvCandidate = configuredVenvDir
|
|
? venvPythonPath(path.isAbsolute(configuredVenvDir) ? configuredVenvDir : path.join(backendDir, configuredVenvDir))
|
|
: "";
|
|
const persistedVenvDir = readPersistedVenvDir();
|
|
const persistedVenvCandidate = persistedVenvDir ? venvPythonPath(persistedVenvDir) : "";
|
|
|
|
const venvCandidates = [
|
|
explicitVenvCandidate,
|
|
persistedVenvCandidate,
|
|
...(isWindows
|
|
? [
|
|
path.join(backendDir, "venv", "Scripts", "python.exe"),
|
|
path.join(backendDir, "venv-repair", "Scripts", "python.exe"),
|
|
path.join(backendDir, ".venv", "Scripts", "python.exe"),
|
|
path.join(backendDir, ".venv-repair", "Scripts", "python.exe"),
|
|
]
|
|
: [
|
|
path.join(backendDir, "venv", "bin", "python3"),
|
|
path.join(backendDir, "venv-repair", "bin", "python3"),
|
|
path.join(backendDir, ".venv", "bin", "python3"),
|
|
path.join(backendDir, ".venv-repair", "bin", "python3"),
|
|
]),
|
|
].filter(Boolean);
|
|
const repairTargetDir = isWindows
|
|
? path.join(backendDir, "venv-repair")
|
|
: path.join(backendDir, "venv-repair");
|
|
|
|
function canRun(command, args) {
|
|
const result = spawnSync(command, args, {
|
|
cwd: backendDir,
|
|
env: process.env,
|
|
stdio: "ignore",
|
|
});
|
|
return !result.error && result.status === 0;
|
|
}
|
|
|
|
function findBasePython() {
|
|
const candidates = isWindows
|
|
? [
|
|
[configuredBasePython, []],
|
|
["python", []],
|
|
["py", ["-3.11"]],
|
|
["py", ["-3"]],
|
|
]
|
|
: [
|
|
[configuredBasePython, []],
|
|
["python3", []],
|
|
["python", []],
|
|
];
|
|
|
|
for (const [command, prefixArgs] of candidates) {
|
|
if (!command) {
|
|
continue;
|
|
}
|
|
if (canRun(command, [...prefixArgs, "-V"])) {
|
|
return { command, prefixArgs };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function rebuildBackendVenv(targetDir, basePython) {
|
|
console.log(`[*] Preparing backend Python environment at ${targetDir}...`);
|
|
try {
|
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
} catch (error) {
|
|
console.warn(`[*] Could not clear ${targetDir} cleanly (${error.code || error.message}). Trying a fresh repair path...`);
|
|
targetDir = `${targetDir}-${Date.now()}`;
|
|
}
|
|
|
|
let result = spawnSync(
|
|
basePython.command,
|
|
[...basePython.prefixArgs, "-m", "venv", targetDir],
|
|
{
|
|
cwd: backendDir,
|
|
env: process.env,
|
|
stdio: "inherit",
|
|
}
|
|
);
|
|
if (result.error || result.status !== 0) {
|
|
return null;
|
|
}
|
|
|
|
const repairedBin = isWindows
|
|
? path.join(targetDir, "Scripts", "python.exe")
|
|
: path.join(targetDir, "bin", "python3");
|
|
|
|
result = spawnSync(repairedBin, ["-m", "pip", "install", "-q", "."], {
|
|
cwd: backendDir,
|
|
env: process.env,
|
|
stdio: "inherit",
|
|
});
|
|
if (result.error || result.status !== 0) {
|
|
return null;
|
|
}
|
|
return canRun(repairedBin, ["-V"]) ? repairedBin : null;
|
|
}
|
|
|
|
function ensureBackendVenv() {
|
|
for (const candidate of venvCandidates) {
|
|
if (fs.existsSync(candidate) && canRun(candidate, ["-V"])) {
|
|
persistSelectedVenv(candidate);
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
const hadExisting = venvCandidates.some((candidate) => fs.existsSync(candidate));
|
|
console.log(
|
|
hadExisting
|
|
? "[*] Backend venv exists but is stale. Rebuilding it automatically..."
|
|
: "[*] Backend venv is missing. Creating it automatically..."
|
|
);
|
|
|
|
const basePython = findBasePython();
|
|
if (!basePython) {
|
|
return null;
|
|
}
|
|
|
|
const preferredRebuildDir = persistedVenvDir || canonicalVenvDir;
|
|
const rebuilt = rebuildBackendVenv(hadExisting ? preferredRebuildDir : canonicalVenvDir, basePython);
|
|
if (rebuilt) {
|
|
persistSelectedVenv(rebuilt);
|
|
}
|
|
return rebuilt;
|
|
}
|
|
|
|
const venvBin = ensureBackendVenv();
|
|
|
|
if (!venvBin) {
|
|
console.error(`[!] Unable to prepare backend Python venv. Checked: ${venvCandidates.join(", ")}`);
|
|
console.error("[!] Install Python 3.10-3.12 and rerun start.sh/start.bat if the repair could not complete.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const backendArgs = ["-m", "uvicorn", "main:app", "--timeout-keep-alive", "120"];
|
|
if (["1", "true", "yes"].includes(String(process.env.BACKEND_RELOAD || "").toLowerCase())) {
|
|
backendArgs.push("--reload");
|
|
}
|
|
|
|
console.log(`[*] Starting backend with: ${venvBin} ${backendArgs.join(" ")}`);
|
|
const backendProc = spawn(venvBin, backendArgs, {
|
|
cwd: backendDir,
|
|
stdio: "inherit",
|
|
env: process.env,
|
|
});
|
|
|
|
const cleanupAll = () => {
|
|
if (backendProc && !backendProc.killed) {
|
|
backendProc.kill();
|
|
}
|
|
};
|
|
|
|
process.on("exit", cleanupAll);
|
|
process.on("SIGINT", () => {
|
|
cleanupAll();
|
|
process.exit(0);
|
|
});
|
|
process.on("SIGTERM", () => {
|
|
cleanupAll();
|
|
process.exit(0);
|
|
});
|
|
|
|
backendProc.on("exit", (code) => {
|
|
cleanupAll();
|
|
process.exit(code ?? 0);
|
|
});
|