# HY2XS production builder Этот каталог содержит production builder для HY2XS. Builder собирает один переносимый install-archive: ```text dist/hy2xs-install-.tar.gz ``` Этот архив переносится на production server, распаковывается и устанавливается через [`install.sh`](../../package/install.sh). На production server не должно быть сборки из исходников: без `go build`, без `bun install`, без `pnpm install`, без frontend build и без TypeScript transpilation. ## Что где лежит Основные части проекта: - [`versions.env`](../../versions.env) — контракт продукта, платформы и toolchain. Единственный источник истины для версий и контрольных сумм. - [`tools/build/build.sh`](build.sh) — главный entrypoint сборки. - [`tools/build/lib/versions.sh`](lib/versions.sh) — загрузка `versions.env` и `verify_versions_contract`. - [`tools/build/lib/deps.sh`](lib/deps.sh) — проверка Debian/amd64, установка build dependencies, установка Go/Bun/Node.js/pnpm. - [`tools/build/lib/package.sh`](lib/package.sh) — сборка orchestrator, сборка HY2XS admin, создание stage directory и tar.gz архива. - [`tools/build/lib/verify.sh`](lib/verify.sh) — проверка структуры репозитория и итогового архива. - [`tools/build/lib/acceptance.sh`](lib/acceptance.sh) — acceptance-проверки production-контракта. - [`tools/build/lib/hysteria.sh`](lib/hysteria.sh) — разрешение upstream-версии Hysteria и compatibility gate. - [`tools/build/hysteria-lock.env`](hysteria-lock.env) — fallback-значения для офлайн-сборки (`HYSTERIA_CHANNEL=pinned`). - [`tools/test/e2e-hysteria.sh`](../test/e2e-hysteria.sh) — end-to-end проверка с реальным клиентом Hysteria. - [`orchestrator`](../../orchestrator) — TypeScript/Bun install-only orchestrator. - [`apps`](../../apps) — HY2XS admin: Go backend и Vue frontend. - [`package`](../../package) — skeleton будущего install package: `install.sh`, templates, systemd units, default config. - [`dist`](../../dist) — итоговые архивы. Создаётся builder'ом, в git обычно не хранится. - [`.toolchain`](../../.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: 1. Загружает и валидирует [`versions.env`](../../versions.env). 2. Проверяет, что host соответствует `HY2XS_BUILD_*` (по умолчанию Debian 13 amd64). 3. Проверяет структуру репозитория. 4. Устанавливает недостающие системные build dependencies через `apt-get`. 5. Проверяет или скачивает локальные версии Go/Bun/Node.js/pnpm **из контракта** и сверяет каждый архив с контрольной суммой из `versions.env`. 6. Выполняет `verify_versions_contract`: рассинхрон версий роняет сборку до создания tarball. 7. Прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`). 8. Рано прогоняет dependency-free контракты панели. 9. Разрешает upstream-версию Hysteria, берёт ожидаемый SHA-256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт. 10. Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS. 11. Копирует package skeleton и собирает install-only orchestrator. 12. Устанавливает frozen frontend lock-граф, runtime-компилирует все сообщения RU/EN реальным `vue-i18n`, проверяет типы и собирает production bundle. 13. Собирает backend HY2XS admin в Linux amd64 binary, проставляя версию через ldflags. 14. Прогоняет `go vet` и `go test` для HY2XS admin (после сборки frontend, потому что `go:embed all:dist` требует готовых ассетов). 15. Проверяет зависимости через `govulncheck ./...` и `pnpm audit` по всему lock-графу. 16. Записывает metadata и checksums. 17. Создаёт архив и прогоняет его acceptance-проверки. ## Контракт версий Версии продукта, платформы и toolchain объявлены в корневом [`versions.env`](../../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`](../../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-lock.env)). Security overrides frontend находятся в `apps/frontend/pnpm-workspace.yaml`: это канонический файл настроек pnpm. Поле `pnpm` в `package.json` для этой цели не используется, потому что новые версии package manager его игнорируют. ## Версия Hysteria: разрешение и compatibility gate Builder не хранит версию Hysteria вручную. По умолчанию он определяет последнюю стабильную версию сам и замораживает её в пакете. Правила разрешения: 1. канонический upstream — `HyNetworks/hysteria`; 2. принимаются только стабильные релизы, без draft и prerelease; 3. тег должен иметь вид `app/vX.Y.Z`; 4. берётся ровно один артефакт `hysteria-linux-amd64` и ровно один `hashes.txt`; 5. URL используется в том виде, в каком его вернул upstream API, без пересборки строки; 6. ожидаемый SHA-256 берётся из upstream `hashes.txt`, и скачанный бинарник сверяется с ним; 7. разрешённые значения попадают в metadata пакета. ### Почему hashes.txt, а не локальный пересчёт Раньше SHA-256 считался от уже скачанного файла. Это защищает target от последующей подмены, но не доказывает, что builder скачал именно ожидаемый upstream artifact: сумма фиксирует то, что пришло, каким бы оно ни было. Формат ассета: ```text 6493dfff…f94 build/hysteria-linux-amd64 f24f63be…189 build/hysteria-linux-amd64-avx ``` Сопоставление идёт по базовому имени и строго на равенство: `build/` — часть пути, а `hysteria-linux-amd64-avx` — другой артефакт, который не должен совпасть по префиксу. Разбор вынесен в `parseUpstreamHashes` ([`hysteriaRelease.ts`](../../orchestrator/src/build/hysteriaRelease.ts)) и покрыт юнит-тестами. Источник ожидаемой суммы фиксируется в metadata как `hysteria_sha_source`. Сравнение версий числовое, поэтому `v2.9.10` считается новее `v2.9.2`. После разрешения обязателен compatibility gate: ```text скачать бинарник ↓ сверить SHA-256 и `hysteria version` ↓ отрендерить канонический конфиг HY2XS тем же кодом, что и на target ↓ запустить настоящий Hysteria с этим конфигом (gecko и salamander) ↓ только после этого собирать release package ``` При несовместимости сборка останавливается: ```text 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: ```bash 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`. По умолчанию: ```bash BUN_FLAVOR=auto ``` Логика: - если CPU поддерживает AVX2, используется `bun-linux-x64`; - если CPU не поддерживает AVX2, используется `bun-linux-x64-baseline`. Поскольку артефакта два, одной контрольной суммы архитектурно недостаточно. В [`versions.env`](../../versions.env) зафиксированы обе: ```bash BUN_LINUX_X64_SHA256= BUN_LINUX_X64_BASELINE_SHA256= ``` Ожидаемый digest выбирается уже **после** `select_bun_artifact()`. Можно принудительно задать flavor: ```bash BUN_FLAVOR=x64 ./tools/build/build.sh ``` или: ```bash BUN_FLAVOR=x64-baseline ./tools/build/build.sh ``` ## Запуск Из корня репозитория: ```bash ./tools/build/build.sh ``` Версия пакета берётся из `versions.env`, поэтому обычно нужен только build id: ```bash BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ) ./tools/build/build.sh ``` ## Проверка результата Проверка архива: ```bash 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 ``` Проверка обязательных файлов: ```bash 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: ```bash tar -xOzf dist/hy2xs-install-1.0.0.tar.gz hy2xs-install/metadata/package.env ``` ## Когда обновлять orchestrator/bun.lock Не перегенерируйте [`orchestrator/bun.lock`](../../orchestrator/bun.lock) во время production-сборки. Обновлять и коммитить [`orchestrator/bun.lock`](../../orchestrator/bun.lock) следует только когда: - изменился [`orchestrator/package.json`](../../orchestrator/package.json); - изменился `BUN_VERSION` в [`versions.env`](../../versions.env); - зависимости оркестратора обновляются осознанно. При смене `BUN_VERSION` нужно обновить и `packageManager` в `orchestrator/package.json`, и обе контрольные суммы Bun: иначе `verify_versions_contract` остановит сборку. Production builder всегда выполняет: ```bash bun install --frozen-lockfile ``` Если команда падает, исправьте и закоммитьте lockfile в системе контроля версий. Не убирайте `--frozen-lockfile`. ## Дисциплина lockfile для frontend Не перегенерируйте frontend lock data во время обычной production-сборки. Правила управления пакетами frontend: - [`apps/frontend/package.json`](../../apps/frontend/package.json) объявляет `"packageManager": "pnpm@9.15.9"`; - builder использует закреплённый pnpm из `PNPM_VERSION` в [`versions.env`](../../versions.env), и `verify_versions_contract` сверяет эти два значения; - production-путь установки frontend всегда: ```bash 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-baseline` - `FRONTEND_NODE_OLD_SPACE_SIZE=2048` (default memory limit for frontend build step) - `TOOLCHAIN_DIR=/custom/path/.toolchain` - `VERIFY_TOOLCHAIN_CHECKSUMS=true` - `VERSIONS_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()`](lib/package.sh): ```bash --max-old-space-size=2048 ``` Способы переопределения: - изменить значение по умолчанию для этой политики: ```bash FRONTEND_NODE_OLD_SPACE_SIZE=3072 ./tools/build/build.sh ``` - или передать полный набор собственных Node-опций (если `--max-old-space-size` там уже задан, builder не добавит второй): ```bash NODE_OPTIONS="--max-old-space-size=3072" ./tools/build/build.sh ``` ## Диагностика Проверить AVX2: ```bash grep -qw avx2 <<<"$(grep -m1 '^flags' /proc/cpuinfo)" && echo 'CPU has AVX2' || echo 'CPU has NO AVX2; Bun baseline is required' ``` Проверить Bun: ```bash .toolchain/bun/bin/bun --version echo "bun_exit=$?" ``` Если есть `Illegal instruction`, удалите старый Bun и пересоберите: ```bash 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 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 ```