feat(v1): Gecko-обфускация, latest-stable Hysteria на сборке и forward-compatible admin
Сквозная миграция HY2XS на современную Hysteria (2.12.2) и переход на v1. Build: - версия Hysteria резолвится на этапе сборки из HyNetworks/hysteria и замораживается в metadata пакета (version + immutable url + sha256); - compatibility gate: реальный бинарник должен принять канонический конфиг HY2XS для gecko и salamander до создания пакета; - сборка прогоняет тесты оркестратора и админки. Конфигурационный контракт: - HY2XS_CONFIG_SCHEMA_VERSION=2, чужая схема отклоняется fail-fast; - obfs стал настоящим union gecko|salamander, gecko — default; - obfs-блок рендерится оркестратором целиком, два подтипа одновременно структурно невозможны; - современный baseline: congestion bbr/standard, disableLossCompensation=false, disableStatelessReset=false, полный quic-блок. Исправления: - share URI для gecko: генератор был завязан на Obfs.Salamander.Password и выдавал нерабочую ссылку при любой другой обфускации; - SNI брался только из ACME-блока и уходил пустым при HY2XS_TLS_MODE=file; - экспорт конфига выносил trafficStats.secret, access_token и obfs-пароль; - экспорт терял неизвестные upstream-поля при round-trip через типизированную модель; - renderRuntimeEnv печатал тип обфускации литералом, расходясь с конфигом; - namedotcom удалён из ACME-реестра (нет в Hysteria с 2.11.0). Тесты: - 95 тестов оркестратора: env, рендер, семантика профиля, резолвер, rollover; - тесты URI и экспорта в Go; - tools/test/e2e-hysteria.sh с реальным клиентом Hysteria. UX: - подсказки и примеры в форме создания пира. Прочее: CHANGELOG.md, .gitattributes (LF для target-side файлов), документация на русском.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
HYSTERIA_LINUX_AMD64_ASSET,
|
||||
selectLatestStableRelease,
|
||||
type GithubRelease
|
||||
} from "../src/build/hysteriaRelease";
|
||||
|
||||
function release(tag: string): GithubRelease {
|
||||
return {
|
||||
tag_name: tag,
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
published_at: "2026-08-23T00:59:00Z",
|
||||
assets: [
|
||||
{
|
||||
name: HYSTERIA_LINUX_AMD64_ASSET,
|
||||
browser_download_url: `https://github.com/HyNetworks/hysteria/releases/download/${tag}/${HYSTERIA_LINUX_AMD64_ASSET}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function srcFile(...parts: string[]): string {
|
||||
return readFileSync(join(import.meta.dir, "..", "src", ...parts), "utf8");
|
||||
}
|
||||
|
||||
describe("release rollover", () => {
|
||||
// Пакет A собран сегодня, пакет B — завтра. Выход новой upstream-версии
|
||||
// не должен менять содержимое уже собранного пакета A.
|
||||
const upstreamToday = [release("app/v2.12.1"), release("app/v2.12.2")];
|
||||
const upstreamTomorrow = [...upstreamToday, release("app/v2.12.3")];
|
||||
|
||||
test("сборка сегодня закрепляет 2.12.2", () => {
|
||||
expect(selectLatestStableRelease(upstreamToday).version).toBe("v2.12.2");
|
||||
});
|
||||
|
||||
test("сборка завтра закрепляет 2.12.3", () => {
|
||||
expect(selectLatestStableRelease(upstreamTomorrow).version).toBe("v2.12.3");
|
||||
});
|
||||
|
||||
test("резолв чистый: тот же список всегда даёт тот же результат", () => {
|
||||
const first = selectLatestStableRelease(upstreamToday);
|
||||
const second = selectLatestStableRelease(upstreamToday);
|
||||
expect(first).toEqual(second);
|
||||
});
|
||||
});
|
||||
|
||||
describe("install-time никогда не резолвит latest", () => {
|
||||
// Гарантия acceptance-критерия: "latest" относится к моменту сборки,
|
||||
// поэтому переустановка старого пакета обязана ставить старую версию.
|
||||
const installTimeSources = [
|
||||
["commands", "install.ts"],
|
||||
["steps", "hysteria.ts"],
|
||||
["lib", "packageMeta.ts"],
|
||||
["commands", "reconfigure.ts"]
|
||||
];
|
||||
|
||||
test("install-time код не импортирует build-time резолвер", () => {
|
||||
for (const parts of installTimeSources) {
|
||||
expect(srcFile(...parts)).not.toContain("build/hysteriaRelease");
|
||||
}
|
||||
});
|
||||
|
||||
test("install-time код не обращается к upstream API и не использует moving latest", () => {
|
||||
for (const parts of installTimeSources) {
|
||||
const source = srcFile(...parts);
|
||||
expect(source).not.toContain("api.github.com");
|
||||
expect(source).not.toContain("download.hysteria.network");
|
||||
expect(source).not.toMatch(/releases\/latest/);
|
||||
}
|
||||
});
|
||||
|
||||
test("установка берёт версию, url и sha256 только из metadata пакета", () => {
|
||||
const install = srcFile("commands", "install.ts");
|
||||
expect(install).toContain('readPackageValue(options.packageDir, "hysteria.version"');
|
||||
expect(install).toContain('readPackageValue(options.packageDir, "hysteria.url"');
|
||||
expect(install).toContain('readPackageValue(options.packageDir, "hysteria.sha256"');
|
||||
});
|
||||
|
||||
test("отсутствие любой части lock-метаданных прерывает установку", () => {
|
||||
expect(srcFile("commands", "install.ts")).toContain("missing Hysteria lock metadata in package");
|
||||
});
|
||||
|
||||
test("установленный бинарник сверяется с закреплённой версией", () => {
|
||||
const step = srcFile("steps", "hysteria.ts");
|
||||
expect(step).toContain("installed Hysteria version mismatch");
|
||||
expect(step).toContain("sha256sum -c -");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user