mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-02 01:08:41 +02:00
201 lines
5.8 KiB
JavaScript
201 lines
5.8 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import {
|
|
chmodSync,
|
|
copyFileSync,
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { basename, dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const XRAY_VERSION = "v26.3.27";
|
|
export const XRAY_SOURCE_URL = `https://github.com/XTLS/Xray-core/tree/${XRAY_VERSION}`;
|
|
export const XRAY_LICENSE_FILE = "xray-LICENSE.txt";
|
|
|
|
export const XRAY_ASSETS = {
|
|
"aarch64-apple-darwin": {
|
|
name: "Xray-macos-arm64-v8a.zip",
|
|
sha256: "2e93a67e8aa1936ecefb307e120830fcbd4c643ab9b1c46a2d0838d5f8409eaf",
|
|
},
|
|
"x86_64-apple-darwin": {
|
|
name: "Xray-macos-64.zip",
|
|
sha256: "f5b0471d3459eff1b82e48af0aeac186abcc3298210070afbbbd8437a4e8b203",
|
|
},
|
|
"x86_64-unknown-linux-gnu": {
|
|
name: "Xray-linux-64.zip",
|
|
sha256: "23cd9af937744d97776ee35ecad4972cf4b2109d1e0fe6be9930467608f7c8ae",
|
|
},
|
|
"aarch64-unknown-linux-gnu": {
|
|
name: "Xray-linux-arm64-v8a.zip",
|
|
sha256: "4d30283ae614e3057f730f67cd088a42be6fdf91f8639d82cb69e48cde80413c",
|
|
},
|
|
"x86_64-pc-windows-msvc": {
|
|
name: "Xray-windows-64.zip",
|
|
sha256: "d004c39288ce9ada487c6f398c7c545f7d749e44bdfdd59dbc9f865afba4e1ad",
|
|
},
|
|
};
|
|
|
|
const MANIFEST_DIR = dirname(fileURLToPath(import.meta.url));
|
|
|
|
export function requestedTarget() {
|
|
const targetIndex = process.argv.indexOf("--target");
|
|
if (targetIndex !== -1 && process.argv[targetIndex + 1]) {
|
|
return process.argv[targetIndex + 1];
|
|
}
|
|
if (process.env.TARGET) {
|
|
return process.env.TARGET;
|
|
}
|
|
|
|
const result = spawnSync("rustc", ["-vV"], { encoding: "utf8" });
|
|
const match = result.stdout?.match(/^host:\s*(.+)$/m);
|
|
if (!match) {
|
|
throw new Error("Unable to determine the Rust target");
|
|
}
|
|
return match[1].trim();
|
|
}
|
|
|
|
function sha256(path) {
|
|
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
}
|
|
|
|
export function xrayBinaryName(target) {
|
|
return `xray-${target}${target.includes("windows") ? ".exe" : ""}`;
|
|
}
|
|
|
|
export function xrayDownloadUrl(assetName) {
|
|
return `https://github.com/XTLS/Xray-core/releases/download/${XRAY_VERSION}/${assetName}`;
|
|
}
|
|
|
|
function extractArchive(archive, destinationDir, windowsTarget) {
|
|
if (windowsTarget) {
|
|
const result = spawnSync(
|
|
"powershell",
|
|
[
|
|
"-NoProfile",
|
|
"-NonInteractive",
|
|
"-Command",
|
|
"Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force",
|
|
archive,
|
|
destinationDir,
|
|
],
|
|
{ stdio: "inherit" },
|
|
);
|
|
if (result.status !== 0) {
|
|
throw new Error("Failed to extract the Xray-core archive");
|
|
}
|
|
return {
|
|
binary: join(destinationDir, "Xray.exe"),
|
|
license: join(destinationDir, "LICENSE"),
|
|
};
|
|
}
|
|
|
|
const result = spawnSync(
|
|
"unzip",
|
|
["-qq", "-j", archive, "xray", "LICENSE", "-d", destinationDir],
|
|
{ stdio: "inherit" },
|
|
);
|
|
if (result.status !== 0) {
|
|
throw new Error("Failed to extract the Xray-core archive");
|
|
}
|
|
|
|
return {
|
|
binary: join(destinationDir, "xray"),
|
|
license: join(destinationDir, "LICENSE"),
|
|
};
|
|
}
|
|
|
|
export async function downloadXray(target = requestedTarget()) {
|
|
const asset = XRAY_ASSETS[target];
|
|
if (!asset) {
|
|
throw new Error(`Xray-core is not packaged for Rust target '${target}'`);
|
|
}
|
|
|
|
const windowsTarget = target.includes("windows");
|
|
const destinationDir = join(MANIFEST_DIR, "binaries");
|
|
const destination = join(destinationDir, xrayBinaryName(target));
|
|
const licenseDestination = join(destinationDir, XRAY_LICENSE_FILE);
|
|
const marker = `${destination}.source.json`;
|
|
|
|
if (
|
|
existsSync(destination) &&
|
|
existsSync(licenseDestination) &&
|
|
existsSync(marker)
|
|
) {
|
|
try {
|
|
const source = JSON.parse(readFileSync(marker, "utf8"));
|
|
if (
|
|
source.version === XRAY_VERSION &&
|
|
source.archiveSha256 === asset.sha256 &&
|
|
source.binarySha256 === sha256(destination) &&
|
|
source.licenseSha256 === sha256(licenseDestination)
|
|
) {
|
|
return destination;
|
|
}
|
|
} catch {
|
|
// A partial or older cache entry is replaced from the verified archive.
|
|
}
|
|
}
|
|
|
|
mkdirSync(destinationDir, { recursive: true });
|
|
const scratch = mkdtempSync(join(tmpdir(), "donut-xray-"));
|
|
try {
|
|
const archive = join(scratch, basename(asset.name));
|
|
const response = await fetch(xrayDownloadUrl(asset.name));
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to download Xray-core (${response.status} ${response.statusText})`,
|
|
);
|
|
}
|
|
writeFileSync(archive, Buffer.from(await response.arrayBuffer()));
|
|
|
|
const actual = sha256(archive);
|
|
if (actual !== asset.sha256) {
|
|
throw new Error(
|
|
`Xray-core checksum mismatch: expected ${asset.sha256}, got ${actual}`,
|
|
);
|
|
}
|
|
|
|
const extracted = extractArchive(archive, scratch, windowsTarget);
|
|
if (!existsSync(extracted.binary) || !existsSync(extracted.license)) {
|
|
throw new Error(
|
|
"The Xray-core archive did not contain its executable and license",
|
|
);
|
|
}
|
|
copyFileSync(extracted.binary, destination);
|
|
copyFileSync(extracted.license, licenseDestination);
|
|
if (!windowsTarget) {
|
|
chmodSync(destination, 0o755);
|
|
}
|
|
writeFileSync(
|
|
marker,
|
|
`${JSON.stringify(
|
|
{
|
|
version: XRAY_VERSION,
|
|
archiveSha256: asset.sha256,
|
|
binarySha256: sha256(destination),
|
|
licenseSha256: sha256(licenseDestination),
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
);
|
|
console.log(`Downloaded Xray-core ${XRAY_VERSION} to ${destination}`);
|
|
return destination;
|
|
} finally {
|
|
rmSync(scratch, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
downloadXray().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exit(1);
|
|
});
|
|
}
|