10d2c48cf0
SHA-256 считался локально от уже скачанного файла. Это защищает target от последующей подмены, но не доказывает, что builder скачал именно ожидаемый upstream artifact: сумма фиксирует то, что пришло, каким бы оно ни было. То есть trust-on-first-use, а не проверка происхождения. Upstream публикует контрольные суммы релиза ассетом hashes.txt: 6493dfff...f94 build/hysteria-linux-amd64 f24f63be...189 build/hysteria-linux-amd64-avx Теперь резолвер отдаёт и URL этого ассета, сборка скачивает его, берёт оттуда ожидаемую сумму и сверяет с ней бинарник - и только после этого записывает SHA-256 в HY2XS lock и metadata. Сопоставление идёт по базовому имени и строго на равенство: build/ - часть пути, а hysteria-linux-amd64-avx - другой артефакт, который не должен совпасть по префиксу. Разбор вынесен в parseUpstreamHashes и покрыт тестами, включая форму sha256:<hex>, верхний регистр, противоречивые и отсутствующие записи. Релиз без hashes.txt для production-сборки непригоден и отклоняется. Источник ожидаемой суммы фиксируется в metadata как hysteria_sha_source.
55 lines
1.4 KiB
TypeScript
55 lines
1.4 KiB
TypeScript
/**
|
|
* Build-time CLI: достаёт ожидаемый SHA-256 артефакта из upstream hashes.txt.
|
|
*
|
|
* Вынесено в отдельную команду, чтобы разбор формата жил в одном месте и был
|
|
* покрыт юнит-тестами, а не повторялся awk-выражением в bash.
|
|
*
|
|
* bun run tools/parse-upstream-hashes.ts --file hashes.txt --asset hysteria-linux-amd64
|
|
*/
|
|
|
|
import { parseUpstreamHashes } from "../src/build/hysteriaRelease";
|
|
|
|
function fail(message: string): never {
|
|
console.error(`[hy2xs-build] ERROR: ${message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
function parseArgs(argv: string[]): { file: string; asset: string } {
|
|
let file = "";
|
|
let asset = "";
|
|
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const arg = argv[i];
|
|
const value = argv[i + 1];
|
|
if (arg === "--file") {
|
|
file = value ?? "";
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (arg === "--asset") {
|
|
asset = value ?? "";
|
|
i += 1;
|
|
continue;
|
|
}
|
|
fail(`unknown argument: ${arg}`);
|
|
}
|
|
|
|
if (!file) {
|
|
fail("missing --file");
|
|
}
|
|
if (!asset) {
|
|
fail("missing --asset");
|
|
}
|
|
|
|
return { file, asset };
|
|
}
|
|
|
|
const { file, asset } = parseArgs(Bun.argv.slice(2));
|
|
|
|
try {
|
|
const text = await Bun.file(file).text();
|
|
process.stdout.write(`${parseUpstreamHashes(text, asset)}\n`);
|
|
} catch (error) {
|
|
fail(error instanceof Error ? error.message : String(error));
|
|
}
|