Проверка требовала литеральное `classifyReconfigureFailure(ownership)`, тогда как у функции давно два параметра. Второй появился вместе с типизированным распознаванием сработавшего guard: по владению он неотличим от обычного отказа smoke — тронут firewall, перезапущены сервисы, — но чинить надо другое, потому что сервер уже вернулся на ПРЕЖНИЙ firewall. То есть гейт утверждал не тот контракт, который назван в его же заголовке, и падал на коде, который этот контракт соблюдает. Поведенческие тесты при этом были и остаются зелёными: «текст ошибки на классификацию не влияет» и «сработавший guard опознаётся по типу ошибки». Проверка приведена к фактической форме, заголовок — к фактической архитектуре. `error instanceof FirewallGuardFiredError` намеренно не дублируется: тот же инвариант проверяется ниже, в «a fired guard forbids the durable commit», и для install, и для reconfigure. Заодно прогнаны ВСЕ гейты приёмки по текущему дереву, а не только упавший: 116 положительных литеральных проверок, 36 bun-блоков с текстовыми инвариантами и все отрицательные сканы. Кроме этого одного — расхождений нет.
HY2XS production builder
Этот каталог содержит production builder для HY2XS.
Builder собирает один переносимый install-archive:
dist/hy2xs-install-<version>.tar.gz
Этот архив переносится на production server, распаковывается и устанавливается через install.sh.
На production server не должно быть сборки из исходников: без go build, без bun install, без pnpm install, без frontend build и без TypeScript transpilation.
Что где лежит
Основные части проекта:
versions.env— контракт продукта, платформы и toolchain. Единственный источник истины для версий и контрольных сумм.tools/build/build.sh— главный entrypoint сборки.tools/build/lib/versions.sh— загрузкаversions.envиverify_versions_contract.tools/build/lib/deps.sh— проверка Debian/amd64, установка build dependencies, установка Go/Bun/Node.js/pnpm.tools/build/lib/package.sh— сборка orchestrator, сборка HY2XS admin, создание stage directory и tar.gz архива.tools/build/lib/verify.sh— проверка структуры репозитория и итогового архива.tools/build/lib/acceptance.sh— acceptance-проверки production-контракта.tools/build/lib/hysteria.sh— разрешение upstream-версии Hysteria и compatibility gate.tools/build/hysteria-lock.env— fallback-значения для офлайн-сборки (HYSTERIA_CHANNEL=pinned).tools/test/e2e-hysteria.sh— end-to-end проверка с реальным клиентом Hysteria.orchestrator— TypeScript/Bun install-only orchestrator.apps— HY2XS admin: Go backend и Vue frontend.package— skeleton будущего install package:install.sh, templates, systemd units, default config.dist— итоговые архивы. Создаётся builder'ом, в git обычно не хранится..toolchain— локальный toolchain builder'а. Создаётся автоматически, в git не хранится.
Требования к build machine
Production build поддерживается только на:
- Debian 13;
- amd64 / x86_64;
- root или пользователь с
sudo; - доступ в интернет.
Нужен outbound HTTPS/DNS до:
- Debian apt repositories;
go.dev;github.com;nodejs.org;- npm registry.
Windows и macOS можно использовать для редактирования исходников, но финальную production-сборку надо делать на Debian 13 amd64.
Что builder делает сам
При запуске builder:
- Загружает и валидирует
versions.env. - Проверяет, что host соответствует
HY2XS_BUILD_*(по умолчанию Debian 13 amd64). - Проверяет структуру репозитория.
- Устанавливает недостающие системные build dependencies через
apt-get. - Проверяет или скачивает локальные версии Go/Bun/Node.js/pnpm из контракта
и сверяет каждый архив с контрольной суммой из
versions.env. - Выполняет
verify_versions_contract: рассинхрон версий роняет сборку до создания tarball. - Прогоняет тесты и типы оркестратора (
bun test,tsc --noEmit). - Разрешает upstream-версию Hysteria, берёт ожидаемый SHA-256 из upstream
hashes.txtи сверяет с ним скачанный артефакт. - Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS.
- Копирует package skeleton.
- Собирает install-only orchestrator в standalone binary.
- Собирает frontend и backend HY2XS admin в Linux amd64 binary, проставляя версию админки через ldflags.
- Прогоняет
go vetиgo testдля HY2XS admin (после сборки frontend, потому чтоgo:embed all:distтребует готовых ассетов). - Записывает metadata и checksums.
- Создаёт
dist/hy2xs-install-<version>.tar.gz. - Проверяет архив и прогоняет acceptance-проверки.
Контракт версий
Версии продукта, платформы и toolchain объявлены в корневом
versions.env. Собственных значений по умолчанию у
deps.sh больше нет: без загруженного контракта сборка падает сразу.
Подход — проверка, а не генерация. profile.ts,
package/config/hy2xs.env и packageManager в обоих package.json остаются
обычными файлами, чтобы bun test, tsc и go test работали из чистого
чекаута до запуска сборки. verify_versions_contract сверяет их с контрактом и
роняет сборку при расхождении.
Контракт оркестратора сверяется не grep'ом по исходникам, а выводом
orchestrator/tools/print-contract.ts:
это доказывает, что в бинарь попало то же значение.
Версия админки приезжает в бинарь через ldflags
(-X 'hy2xs-admin/model/constant.Version=v${HY2XS_VERSION}') и проверяется
запуском собранного hy2xs-admin version. Захардкоженной константы версии в
Go-коде больше нет: она уже успела разъехаться с версией пакета.
Чего в versions.env нет намеренно: прикладных зависимостей (для них есть
pnpm-lock.yaml, bun.lock, go.sum) и конкретной версии Hysteria (здесь
только политика HYSTERIA_CHANNEL, результат резолва — в
hysteria-lock.env).
Версия Hysteria: разрешение и compatibility gate
Builder не хранит версию Hysteria вручную. По умолчанию он определяет последнюю стабильную версию сам и замораживает её в пакете.
Правила разрешения:
- канонический upstream —
HyNetworks/hysteria; - принимаются только стабильные релизы, без draft и prerelease;
- тег должен иметь вид
app/vX.Y.Z; - берётся ровно один артефакт
hysteria-linux-amd64и ровно одинhashes.txt; - URL используется в том виде, в каком его вернул upstream API, без пересборки строки;
- ожидаемый SHA-256 берётся из upstream
hashes.txt, и скачанный бинарник сверяется с ним; - разрешённые значения попадают в metadata пакета.
Почему hashes.txt, а не локальный пересчёт
Раньше SHA-256 считался от уже скачанного файла. Это защищает target от последующей подмены, но не доказывает, что builder скачал именно ожидаемый upstream artifact: сумма фиксирует то, что пришло, каким бы оно ни было.
Формат ассета:
6493dfff…f94 build/hysteria-linux-amd64
f24f63be…189 build/hysteria-linux-amd64-avx
Сопоставление идёт по базовому имени и строго на равенство: build/ — часть
пути, а hysteria-linux-amd64-avx — другой артефакт, который не должен
совпасть по префиксу. Разбор вынесен в parseUpstreamHashes
(hysteriaRelease.ts) и
покрыт юнит-тестами.
Источник ожидаемой суммы фиксируется в metadata как hysteria_sha_source.
Сравнение версий числовое, поэтому v2.9.10 считается новее v2.9.2.
После разрешения обязателен compatibility gate:
скачать бинарник
↓
сверить SHA-256 и `hysteria version`
↓
отрендерить канонический конфиг HY2XS тем же кодом, что и на target
↓
запустить настоящий Hysteria с этим конфигом (gecko и salamander)
↓
только после этого собирать release package
При несовместимости сборка останавливается:
BUILD FAILED: unsupported Hysteria stable v2.13.0
Это осознанное решение: ошибка должна проявиться на build machine, а не на production-сервере.
Переменные:
| Переменная | По умолчанию | Назначение |
|---|---|---|
HYSTERIA_CHANNEL |
из versions.env (stable) |
stable — разрешить последнюю стабильную через upstream API; pinned — офлайн-сборка по hysteria-lock.env |
HYSTERIA_VERSION_OVERRIDE |
пусто | Закрепить конкретную версию vX.Y.Z |
HYSTERIA_COMPAT_GATE |
true |
Compatibility gate; для release-сборок обязателен |
HYSTERIA_VERIFY_UPSTREAM_HASHES |
true |
Сверять артефакт с upstream hashes.txt; отключение — только break-glass |
HYSTERIA_WRITE_LOCK |
false |
Записать разрешённые значения обратно в hysteria-lock.env |
HYSTERIA_GATE_PORT |
34443 |
UDP-порт для временного запуска Hysteria в gate |
HYSTERIA_GATE_STATS_PORT |
34712 |
TCP-порт trafficStats в gate |
GITHUB_TOKEN |
пусто | Опционально: снимает anonymous rate limit GitHub API |
Тесты, проверка типов и проверка зависимостей переменными не управляются: у них
нет аварийного выхода. Готовый пакет объявляет об этом полями tests_gate=true
и dependency_security_gate=true в metadata/package.env.
Обновить lock-файл под текущий upstream:
HYSTERIA_WRITE_LOCK=true ./tools/build/build.sh
Важное про Bun и старые CPU
Bun имеет два Linux x64 artifact'а:
bun-linux-x64— обычный build;bun-linux-x64-baseline— build для CPU без AVX2.
Если CPU не поддерживает AVX2, обычный Bun может падать с Illegal instruction.
Builder автоматически проверяет /proc/cpuinfo.
По умолчанию:
BUN_FLAVOR=auto
Логика:
- если CPU поддерживает AVX2, используется
bun-linux-x64; - если CPU не поддерживает AVX2, используется
bun-linux-x64-baseline.
Поскольку артефакта два, одной контрольной суммы архитектурно недостаточно. В
versions.env зафиксированы обе:
BUN_LINUX_X64_SHA256=<sha256>
BUN_LINUX_X64_BASELINE_SHA256=<sha256>
Ожидаемый digest выбирается уже после select_bun_artifact().
Можно принудительно задать flavor:
BUN_FLAVOR=x64 ./tools/build/build.sh
или:
BUN_FLAVOR=x64-baseline ./tools/build/build.sh
Запуск
Из корня репозитория:
./tools/build/build.sh
Версия пакета берётся из versions.env, поэтому обычно нужен только build id:
BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ) ./tools/build/build.sh
Проверка результата
Проверка архива:
ls -lh dist/hy2xs-install-1.0.0.tar.gz
sha256sum dist/hy2xs-install-1.0.0.tar.gz | tee dist/hy2xs-install-1.0.0.tar.gz.sha256
Проверка обязательных файлов:
tar -tzf dist/hy2xs-install-1.0.0.tar.gz | grep -E '^(hy2xs-install/install.sh|hy2xs-install/orchestrator/hy2xs-orchestrator|hy2xs-install/ui/hy2xs-admin/hy2xs-admin|hy2xs-install/metadata/checksums.txt)$'
Проверка metadata:
tar -xOzf dist/hy2xs-install-1.0.0.tar.gz hy2xs-install/metadata/package.env
Когда обновлять orchestrator/bun.lock
Не перегенерируйте orchestrator/bun.lock во время production-сборки.
Обновлять и коммитить orchestrator/bun.lock следует только когда:
- изменился
orchestrator/package.json; - изменился
BUN_VERSIONвversions.env; - зависимости оркестратора обновляются осознанно.
При смене BUN_VERSION нужно обновить и packageManager в
orchestrator/package.json, и обе контрольные суммы Bun: иначе
verify_versions_contract остановит сборку.
Production builder всегда выполняет:
bun install --frozen-lockfile
Если команда падает, исправьте и закоммитьте lockfile в системе контроля версий. Не убирайте --frozen-lockfile.
Дисциплина lockfile для frontend
Не перегенерируйте frontend lock data во время обычной production-сборки.
Правила управления пакетами frontend:
apps/frontend/package.jsonобъявляет"packageManager": "pnpm@9.15.9";- builder использует закреплённый pnpm из
PNPM_VERSIONвversions.env, иverify_versions_contractсверяет эти два значения; - production-путь установки frontend всегда:
pnpm install --frozen-lockfile
Если frozen install падает, обновите зависимости осознанно в системе контроля версий и закоммитьте изменения lockfile. Не убирайте --frozen-lockfile из сборочного потока.
Полезные переменные
BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ)BUN_FLAVOR=auto|x64|x64-baselineFRONTEND_NODE_OLD_SPACE_SIZE=2048(default memory limit for frontend build step)TOOLCHAIN_DIR=/custom/path/.toolchainVERIFY_TOOLCHAIN_CHECKSUMS=trueVERSIONS_ENV_FILE=/custom/path/versions.env
PACKAGE_VERSION берётся из versions.env (HY2XS_VERSION); переопределять
его вручную нужно только для отладочных сборок, и verify_versions_contract
такую сборку отклонит.
Контрольные суммы toolchain больше не передаются через окружение: они
объявлены в versions.env. Раньше воспроизводимая сборка в чистой Debian-среде
требовала предварительного знания четырёх SHA-256 и не запускалась одной
командой.
Политика памяти при сборке frontend
Builder задаёт безопасный лимит heap для Node.js внутри bundle_ui():
--max-old-space-size=2048
Способы переопределения:
- изменить значение по умолчанию для этой политики:
FRONTEND_NODE_OLD_SPACE_SIZE=3072 ./tools/build/build.sh
- или передать полный набор собственных Node-опций (если
--max-old-space-sizeтам уже задан, builder не добавит второй):
NODE_OPTIONS="--max-old-space-size=3072" ./tools/build/build.sh
Диагностика
Проверить AVX2:
grep -qw avx2 <<<"$(grep -m1 '^flags' /proc/cpuinfo)" && echo 'CPU has AVX2' || echo 'CPU has NO AVX2; Bun baseline is required'
Проверить Bun:
.toolchain/bun/bin/bun --version
echo "bun_exit=$?"
Если есть Illegal instruction, удалите старый Bun и пересоберите:
rm -rf .toolchain/bun .toolchain/bun-tmp
rm -f .toolchain/downloads/bun-linux-x64-*.zip
rm -f .toolchain/downloads/bun-linux-x64-baseline-*.zip
./tools/build/build.sh
Проверить shell syntax:
bash -n tools/build/build.sh
bash -n tools/build/lib/common.sh
bash -n tools/build/lib/versions.sh
bash -n tools/build/lib/deps.sh
bash -n tools/build/lib/hysteria.sh
bash -n tools/build/lib/package.sh
bash -n tools/build/lib/verify.sh
bash -n tools/build/lib/acceptance.sh