diff --git a/README.md b/README.md index f5e65eb..b5bba6b 100644 --- a/README.md +++ b/README.md @@ -23,26 +23,6 @@ HY2XS — production installer/runtime-manager для развёртывания - [`docs`](docs) — архитектурная документация и acceptance. - [`dist`](dist) — итоговые install archives, создаются builder'ом и не хранятся в git. -## Сборка - -Production-сборка выполняется только на Debian 13 amd64: - -```sh -PACKAGE_VERSION=0.1.0 ./tools/build/build.sh -``` - -Builder сам проверяет ОС, архитектуру, структуру проекта, lock-файлы, версии Go/Bun/Node.js/pnpm и при необходимости скачивает локальный toolchain. - -Ожидаемый результат: - -```text -dist/hy2xs-install-0.1.0.tar.gz -``` - -На target server не должно быть `bun install`, `pnpm install`, TypeScript transpilation, frontend build или Go build. - -Подробности сборки: [`tools/build/README.md`](tools/build/README.md). - ## Публикация Целевой репозиторий: diff --git a/tools/build/README.md b/tools/build/README.md index 023696d..6d52cb3 100644 --- a/tools/build/README.md +++ b/tools/build/README.md @@ -1,39 +1,104 @@ # HY2XS production builder -## Назначение +Этот каталог содержит production builder для HY2XS. -[`build.sh`](build.sh) собирает переносимый установочный пакет HY2XS для production-развёртывания. +Builder собирает один переносимый install-archive: -Итоговый архив создаётся в [`dist`](../../dist) и предназначен для установки на чистый Debian 13 amd64 без сборки на целевом сервере. +```text +dist/hy2xs-install-.tar.gz +``` -## Поддерживаемая среда сборки +Этот архив переносится на production server, распаковывается и устанавливается через [`install.sh`](../../package/install.sh). -Builder поддерживает только: +На production server не должно быть сборки из исходников: без `go build`, без `bun install`, без `pnpm install`, без frontend build и без TypeScript transpilation. + +## Что где лежит + +Основные части проекта: + +- [`tools/build/build.sh`](build.sh) — главный entrypoint сборки. +- [`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/hysteria-lock.env`](hysteria-lock.env) — pinned версия, URL и SHA256 upstream Hysteria2 binary. +- [`orchestrator`](../../orchestrator) — TypeScript/Bun install-only orchestrator. +- [`apps`](../../apps) — HY2XS admin fork: backend на Go и 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; -- bash; -- доступ к интернету для установки build-зависимостей и toolchain. +- root или пользователь с `sudo`; +- доступ в интернет. -Windows/macOS не являются production build host. На Windows можно править исходники, но финальную сборку нужно выполнять на Debian 13 amd64. +Нужен outbound HTTPS/DNS до: + +- Debian apt repositories; +- `go.dev`; +- `github.com`; +- `nodejs.org`; +- npm registry. + +Windows и macOS можно использовать для редактирования исходников, но финальную production-сборку надо делать на Debian 13 amd64. ## Что builder делает сам При запуске builder: -1. Проверяет ОС и архитектуру build host. +1. Проверяет, что host — Debian 13 amd64. 2. Проверяет структуру репозитория. -3. Доставляет отсутствующие системные build-зависимости через `apt-get`. -4. Проверяет и при необходимости скачивает локальный toolchain: +3. Устанавливает недостающие системные build dependencies через `apt-get`. +4. Проверяет или скачивает локальные версии: - Go `1.21.13`; - Bun `1.1.45`; - Node.js `20.19.0`; - pnpm `9.15.9`. -5. Собирает install-only orchestrator под Linux amd64. -6. Собирает bundled HY2XS admin под Linux amd64. -7. Формирует metadata и checksums. -8. Создаёт архив install package. -9. Проверяет состав итогового архива. +5. Собирает install-only orchestrator в standalone binary. +6. Собирает frontend HY2XS admin. +7. Собирает backend HY2XS admin в Linux amd64 binary. +8. Копирует package skeleton. +9. Записывает metadata и checksums. +10. Создаёт `dist/hy2xs-install-.tar.gz`. +11. Проверяет, что архив содержит обязательные файлы. + +## Важное про 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`](../../fix17.txt). + +По умолчанию: + +```bash +BUN_FLAVOR=auto +``` + +Логика: + +- если CPU поддерживает AVX2, используется `bun-linux-x64`; +- если CPU не поддерживает AVX2, используется `bun-linux-x64-baseline`. + +Можно принудительно задать flavor: + +```bash +BUN_FLAVOR=x64 ./tools/build/build.sh +``` + +или: + +```bash +BUN_FLAVOR=x64-baseline ./tools/build/build.sh +``` ## Запуск @@ -43,47 +108,73 @@ Windows/macOS не являются production build host. На Windows можн ./tools/build/build.sh ``` -С явной версией пакета: +С явной версией и build id: ```bash -PACKAGE_VERSION=0.1.0 ./tools/build/build.sh +PACKAGE_VERSION=0.1.6 \ +BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ) \ +./tools/build/build.sh ``` -С явным build id: +## Проверка результата + +Проверка архива: ```bash -PACKAGE_VERSION=0.1.0 BUILD_ID=prod-20260425-001 ./tools/build/build.sh +ls -lh dist/hy2xs-install-0.1.6.tar.gz +sha256sum dist/hy2xs-install-0.1.6.tar.gz | tee dist/hy2xs-install-0.1.6.tar.gz.sha256 ``` -## Локальный toolchain - -Builder ставит управляемый toolchain в [`.toolchain`](../../.toolchain) и не требует ручной установки Go/Bun/Node/pnpm в систему. - -Если нужная версия уже установлена глобально, builder может использовать её. Если версия не совпадает, будет скачана локальная версия. - -## Важные ограничения - -- Builder не ставит HY2XS на сервер. -- Builder не выполняет target install. -- Builder не собирает ничего на target machine. -- Builder не вендорит бинарь Hysteria2 в пакет: Hysteria2 скачивается install layer'ом с official upstream. -- Итоговый пакет не должен содержать build scripts, `.toolchain` или временные каталоги. - -## Результат - -После успешной сборки появится архив: +Проверка обязательных файлов: ```bash -dist/hy2xs-install-.tar.gz +tar -tzf dist/hy2xs-install-0.1.6.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)$' ``` -Его нужно перенести на target Debian 13 amd64, распаковать и запустить [`install.sh`](../../package/install.sh) от root. +Проверка metadata: + +```bash +tar -xOzf dist/hy2xs-install-0.1.6.tar.gz hy2xs-install/metadata/package.env +``` + +## Полезные переменные + +- `PACKAGE_VERSION=0.1.6` +- `BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ)` +- `BUN_FLAVOR=auto|x64|x64-baseline` +- `TOOLCHAIN_DIR=/custom/path/.toolchain` +- `VERIFY_TOOLCHAIN_CHECKSUMS=true` ## Диагностика -Если сборка падает: +Проверить AVX2: -1. Проверьте, что host — Debian 13 amd64. -2. Проверьте доступ к `go.dev`, `github.com`, `nodejs.org`, npm registry и apt repositories. -3. Удалите [`.toolchain`](../../.toolchain) и повторите запуск, если toolchain скачался повреждённым. -4. Проверьте lock-файлы [`bun.lock`](../../orchestrator/bun.lock), [`pnpm-lock.yaml`](../../apps/frontend/pnpm-lock.yaml), [`go.sum`](../../apps/go.sum). +```bash +grep -m1 '^flags' /proc/cpuinfo | grep -qw avx2 && 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 +PACKAGE_VERSION=0.1.6 ./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/deps.sh +bash -n tools/build/lib/package.sh +bash -n tools/build/lib/verify.sh +``` diff --git a/tools/build/lib/deps.sh b/tools/build/lib/deps.sh index 5a227c7..a66c72f 100644 --- a/tools/build/lib/deps.sh +++ b/tools/build/lib/deps.sh @@ -10,6 +10,7 @@ VERIFY_TOOLCHAIN_CHECKSUMS="${VERIFY_TOOLCHAIN_CHECKSUMS:-false}" GO_ARCHIVE_SHA256="${GO_ARCHIVE_SHA256:-}" NODE_ARCHIVE_SHA256="${NODE_ARCHIVE_SHA256:-}" BUN_ARCHIVE_SHA256="${BUN_ARCHIVE_SHA256:-}" +BUN_FLAVOR="${BUN_FLAVOR:-auto}" verify_archive_sha256() { local archive="$1" @@ -119,30 +120,102 @@ ensure_go() { [ "$(go_version "$GO_BIN")" = "$GO_REQUIRED" ] || fail "Go version mismatch: required $GO_REQUIRED, got $($GO_BIN version)" } +cpu_has_avx2() { + grep -m1 '^flags' /proc/cpuinfo 2>/dev/null | grep -qw avx2 +} + +select_bun_artifact() { + case "$BUN_FLAVOR" in + auto) + if cpu_has_avx2; then + BUN_ARTIFACT="bun-linux-x64" + else + BUN_ARTIFACT="bun-linux-x64-baseline" + fi + ;; + x64|bun-linux-x64) + BUN_ARTIFACT="bun-linux-x64" + ;; + baseline|x64-baseline|bun-linux-x64-baseline) + BUN_ARTIFACT="bun-linux-x64-baseline" + ;; + *) + fail "unsupported BUN_FLAVOR: $BUN_FLAVOR (use auto, x64, or x64-baseline)" + ;; + esac + + BUN_COMPILE_TARGET="$BUN_ARTIFACT" + export BUN_ARTIFACT BUN_COMPILE_TARGET +} + +bun_version() { + local bun="$1" + "$bun" --version 2>/dev/null || true +} + ensure_bun() { + select_bun_artifact + local managed="$TOOLCHAIN_DIR/bun/bin/bun" - if [ -x "$managed" ] && [ "$($managed --version)" = "$BUN_REQUIRED" ]; then - BUN_BIN="$managed" - elif command -v bun >/dev/null 2>&1 && [ "$(bun --version)" = "$BUN_REQUIRED" ]; then - BUN_BIN="$(command -v bun)" - else - log_info "Installing Bun $BUN_REQUIRED into $TOOLCHAIN_DIR/bun" - mkdir -p "$TOOLCHAIN_DIR/downloads" "$TOOLCHAIN_DIR/bun" - local archive="$TOOLCHAIN_DIR/downloads/bun-linux-x64-${BUN_REQUIRED}.zip" - download_file "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_REQUIRED}/bun-linux-x64.zip" "$archive" - verify_archive_sha256 "$archive" "$BUN_ARCHIVE_SHA256" "BUN_ARCHIVE" - rm -rf "$TOOLCHAIN_DIR/bun-tmp" "$TOOLCHAIN_DIR/bun" - mkdir -p "$TOOLCHAIN_DIR/bun-tmp" - unzip -q "$archive" -d "$TOOLCHAIN_DIR/bun-tmp" - mkdir -p "$TOOLCHAIN_DIR/bun/bin" - install -m 0755 "$TOOLCHAIN_DIR/bun-tmp/bun-linux-x64/bun" "$managed" - rm -rf "$TOOLCHAIN_DIR/bun-tmp" - BUN_BIN="$managed" + local selected_bun="" + local actual="" + + if [ -x "$managed" ]; then + actual="$(bun_version "$managed")" + if [ "$actual" = "$BUN_REQUIRED" ]; then + selected_bun="$managed" + else + log_info "Ignoring managed Bun at $managed: required $BUN_REQUIRED, got ${actual:-failed to execute}" + fi fi + if [ -z "$selected_bun" ] && command -v bun >/dev/null 2>&1; then + local system_bun + system_bun="$(command -v bun)" + + if [ "$system_bun" != "$managed" ]; then + actual="$(bun_version "$system_bun")" + if [ "$actual" = "$BUN_REQUIRED" ]; then + selected_bun="$system_bun" + else + log_info "Ignoring system Bun at $system_bun: required $BUN_REQUIRED, got ${actual:-failed to execute}" + fi + fi + fi + + if [ -z "$selected_bun" ]; then + log_info "Installing Bun $BUN_REQUIRED ($BUN_ARTIFACT) into $TOOLCHAIN_DIR/bun" + + mkdir -p "$TOOLCHAIN_DIR/downloads" "$TOOLCHAIN_DIR/bun" + + local archive="$TOOLCHAIN_DIR/downloads/${BUN_ARTIFACT}-${BUN_REQUIRED}.zip" + download_file "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_REQUIRED}/${BUN_ARTIFACT}.zip" "$archive" + verify_archive_sha256 "$archive" "$BUN_ARCHIVE_SHA256" "BUN_ARCHIVE" + + rm -rf "$TOOLCHAIN_DIR/bun-tmp" "$TOOLCHAIN_DIR/bun" + mkdir -p "$TOOLCHAIN_DIR/bun-tmp" + + unzip -q "$archive" -d "$TOOLCHAIN_DIR/bun-tmp" + + mkdir -p "$TOOLCHAIN_DIR/bun/bin" + install -m 0755 "$TOOLCHAIN_DIR/bun-tmp/${BUN_ARTIFACT}/bun" "$managed" + + rm -rf "$TOOLCHAIN_DIR/bun-tmp" + + selected_bun="$managed" + fi + + BUN_BIN="$selected_bun" export BUN_BIN export PATH="$(dirname "$BUN_BIN"):$PATH" - [ "$($BUN_BIN --version)" = "$BUN_REQUIRED" ] || fail "Bun version mismatch: required $BUN_REQUIRED, got $($BUN_BIN --version)" + + actual="$(bun_version "$BUN_BIN")" + + if [ "$actual" != "$BUN_REQUIRED" ]; then + fail "Bun version mismatch: required $BUN_REQUIRED, got ${actual:-failed to execute}. Selected artifact: ${BUN_ARTIFACT}. On old CPUs without AVX2 use BUN_FLAVOR=x64-baseline." + fi + + log_info "Using Bun $actual; artifact=$BUN_ARTIFACT; compile_target=$BUN_COMPILE_TARGET" } node_version() { diff --git a/tools/build/lib/package.sh b/tools/build/lib/package.sh index a307c75..95022c9 100644 --- a/tools/build/lib/package.sh +++ b/tools/build/lib/package.sh @@ -26,11 +26,16 @@ prepare_stage() { } build_orchestrator() { + local bun_compile_target="${BUN_COMPILE_TARGET:-bun-linux-x64}" + + log_info "Building orchestrator with Bun compile target: $bun_compile_target" + ( cd orchestrator "$BUN_BIN" install --frozen-lockfile - "$BUN_BIN" build src/cli.ts --compile --target=bun-linux-x64 --outfile ../"$STAGE_DIR"/orchestrator/hy2xs-orchestrator + "$BUN_BIN" build src/cli.ts --compile --target="$bun_compile_target" --outfile ../"$STAGE_DIR"/orchestrator/hy2xs-orchestrator ) + chmod 0755 "$STAGE_DIR/orchestrator/hy2xs-orchestrator" } @@ -90,6 +95,7 @@ write_metadata() { printf 'orchestrator_stack=Bun+TypeScript\n' printf 'go_version=%s\n' "$($GO_BIN version)" printf 'bun_version=%s\n' "$($BUN_BIN --version)" + printf 'bun_compile_target=%s\n' "${BUN_COMPILE_TARGET:-unknown}" printf 'node_version=%s\n' "$($NODE_BIN --version)" printf 'pnpm_version=%s\n' "$($PNPM_BIN --version)" printf 'hysteria_source=official-upstream\n'