Files

305 lines
16 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
STAGE_DIR="tools/build/output/hy2xs-install"
ADMIN_BUILD_DIR="tools/build/output/hy2xs-admin-build"
prepare_stage() {
local version="$1"
local build_id="$2"
rm -rf "$STAGE_DIR"
mkdir -p "$STAGE_DIR" "dist"
copy_dir_contents "package" "$STAGE_DIR"
chmod 0755 "$STAGE_DIR/install.sh"
find "$STAGE_DIR" -type d -exec chmod 0755 {} +
find "$STAGE_DIR" -type f -name "*.service" -exec chmod 0644 {} +
find "$STAGE_DIR" -type f -name "*.tpl" -exec chmod 0644 {} +
find "$STAGE_DIR/config" -type f -exec chmod 0644 {} +
rm -rf "$STAGE_DIR/orchestrator" "$STAGE_DIR/ui" "$STAGE_DIR/metadata"
mkdir -p "$STAGE_DIR/orchestrator" "$STAGE_DIR/ui/hy2xs-admin" "$STAGE_DIR/metadata"
install -m 0644 LICENSE "$STAGE_DIR/LICENSE"
printf '%s\n' "$version" >"$STAGE_DIR/metadata/package.version"
printf '%s\n' "$build_id" >"$STAGE_DIR/metadata/package.build_id"
printf '%s\n' "$HY2XS_RELEASE_LINE" >"$STAGE_DIR/metadata/package.release_line"
printf '%s\n' "$HY2XS_CONFIG_SCHEMA_VERSION" >"$STAGE_DIR/metadata/package.config_schema_version"
}
# Тесты — обязательный шаг релиза, аварийного выхода у него НЕТ.
#
# Это то же решение и по той же причине, что и у проверки зависимостей.
#
# Раньше существовал SKIP_TESTS=true, и README описывал его как «аварийное
# отключение тестов; для release-сборок недопустимо». Недопустимость держалась
# исключительно на этой фразе: ни metadata, ни финальная приёмка архива не
# проверяли, что тесты вообще запускались. То есть
#
# SKIP_TESTS=true ./tools/build/build.sh
#
# доходила до конца и выдавала обычный tarball с build_profile=production и
# dependency_security_gate=true — артефакт, по которому невозможно отличить
# проверенную сборку от непроверенной.
#
# Глушил он при этом не только тесты: под тем же флагом пропускались
# `tsc --noEmit` для оркестратора и `go vet` для админки, то есть проверка типов
# и статический анализ того самого кода, который уезжает в production.
#
# Для локальной работы обходить нечего: `bun test`, `bun x tsc --noEmit`,
# `go vet ./...` и `go test ./...` запускаются напрямую и tarball не создают.
run_orchestrator_tests() {
(
cd orchestrator
"$BUN_BIN" install --frozen-lockfile
"$BUN_BIN" x tsc --noEmit
"$BUN_BIN" test
) || fail "orchestrator contract tests failed"
# Утверждение о результате выставляется ПОСЛЕ получения результата, ровно как
# DEPENDENCY_SECURITY_GATE.
ORCHESTRATOR_TESTS_PASSED="true"
export ORCHESTRATOR_TESTS_PASSED
}
run_frontend_tests() {
# Ранние контракты панели, которые не требуют установленных зависимостей:
# цвет иконок в спрайте, совпадение словарей локализации, соответствие кодов
# ошибок серверным константам, единственность адреса атрибуции.
#
# Исполняет их Bun, уже закреплённый в versions.env, а не vitest. Причина не
# в удобстве: jsdom не вычисляет currentColor и визуальной корректности всё
# равно не доказал бы, зато vitest привёл бы в граф `pnpm audit` — а его
# порог считается по ВСЕМУ lock-файлу frontend — сотню транзитивных
# зависимостей ради нулевой дополнительной гарантии.
#
# Проверяемые модули (SvgIcon/symbol.ts, constants/branding.ts, словари)
# намеренно чистые: ни Vite, ни DOM в них нет, поэтому их можно выполнить вне
# браузера.
"$BUN_BIN" test tools/test/frontend-sprite.test.ts tools/test/frontend-contract.test.ts \
|| fail "HY2XS admin frontend contract tests failed"
FRONTEND_CONTRACT_TESTS_PASSED="true"
export FRONTEND_CONTRACT_TESTS_PASSED
}
run_admin_tests() {
# `go:embed all:dist` требует собранных frontend-ассетов, поэтому эта
# функция должна вызываться только после bundle_ui.
[ -d "${UI_SRC:-apps}/frontend/dist" ] \
|| fail "run_admin_tests must run after bundle_ui: ${UI_SRC:-apps}/frontend/dist is missing"
(
cd "${UI_SRC:-apps}"
GOTOOLCHAIN=local "$GO_BIN" vet ./...
GOTOOLCHAIN=local "$GO_BIN" test ./...
) || fail "HY2XS admin contract tests failed"
run_admin_race_tests
ADMIN_TESTS_PASSED="true"
export ADMIN_TESTS_PASSED
}
# Детектор гонок — отдельный прогон, и он ОБЯЗАТЕЛЕН.
#
# В продукте есть состояние, принадлежащее процессу, а не базе: учёт выданных
# разрешений на устройства (peer_admission.go) и мьютекс цикла учёта
# (cron.go). Оба существуют ровно затем, чтобы вести себя правильно под
# параллельным доступом, — то есть их корректность не наблюдаема ни в обычном
# прогоне тестов, ни в `go vet`.
#
# Пропуск при недоступном C-компиляторе здесь НЕ предусмотрен, и это осознанно.
# «Тихо не проверили» — ровно тот класс, от которого лечится запрет SKIP_TESTS
# в этом же файле: сборка, молча пропускающая проверку, выдаёт внешне
# неотличимый production-артефакт. build-essential входит в обязательные
# зависимости сборщика (см. tools/build/lib/deps.sh), поэтому попасть в ветку
# отказа можно только на сломанном окружении — и об этом нужно узнать.
#
# CGO_ENABLED=1 задаётся явно: production-сборка админки идёт с CGO_ENABLED=0,
# и унаследованное значение отключило бы детектор.
run_admin_race_tests() {
command -v cc >/dev/null 2>&1 || command -v gcc >/dev/null 2>&1 \
|| fail "race detector requires a C toolchain (build-essential); refusing to ship untested concurrency"
(
cd "${UI_SRC:-apps}"
CGO_ENABLED=1 GOTOOLCHAIN=local "$GO_BIN" test -race ./service/...
) || fail "HY2XS admin race detector found a data race"
}
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_compile_target" --outfile ../"$STAGE_DIR"/orchestrator/hy2xs-orchestrator
)
chmod 0755 "$STAGE_DIR/orchestrator/hy2xs-orchestrator"
}
bundle_ui() {
local ui_src="${UI_SRC:-apps}"
[ "${FRONTEND_CONTRACT_TESTS_PASSED:-false}" = "true" ] \
|| fail "bundle_ui: ранние контракты панели не проверялись"
rm -rf "$ADMIN_BUILD_DIR"
mkdir -p "$ADMIN_BUILD_DIR"
(
cd "$ui_src/frontend"
local frontend_node_old_space_size="${FRONTEND_NODE_OLD_SPACE_SIZE:-2048}"
local frontend_node_options="${NODE_OPTIONS:-}"
if [[ "$frontend_node_options" != *"--max-old-space-size="* ]]; then
frontend_node_options="${frontend_node_options:+$frontend_node_options }--max-old-space-size=${frontend_node_old_space_size}"
fi
export NODE_OPTIONS="$frontend_node_options"
log_info "Frontend Node.js options: $NODE_OPTIONS"
"$PNPM_BIN" install --frozen-lockfile
# Словари являются JavaScript-объектами и компилируются vue-i18n только
# при вызове t(). Поэтому ни vue-tsc, ни Vite не видят синтаксическую
# ошибку сообщения. Gate использует тот же установленный runtime, обходит
# все строки обоих языков и выполняется до typecheck и production bundle.
"$BUN_BIN" test test/i18n-runtime.test.ts
# Проверка типов идёт ПЕРЕД сборкой bundle, и это обязательный шаг релиза.
#
# Раньше её здесь не было вовсе: `build:prod` означал
# `vite build && vue-tsc --noEmit`, то есть сборка сначала тратила время на
# production bundle и только потом сообщала о типовой ошибке. Сам же
# `vue-tsc` был версии 0.35 и не находил ничего — на современном он даёт 142
# ошибки на том же коде.
#
# Теперь скрипты разделены, и порядок обратный: смысла собирать bundle из
# кода, который не проходит проверку типов, нет.
"$PNPM_BIN" run typecheck
"$PNPM_BIN" run build:prod
) || fail "HY2XS admin frontend runtime tests or bundle failed"
# Версия админки приходит из versions.env через ldflags: один и тот же
# исполняемый файл обязан знать версию пакета, из которого он собран.
local admin_ldflags="-s -w -X 'hy2xs-admin/model/constant.Version=v${HY2XS_VERSION}'"
(
cd "$ui_src"
GOOS="$HY2XS_TARGET_GOOS" GOARCH="$HY2XS_TARGET_ARCH" CGO_ENABLED=0 GOTOOLCHAIN=local "$GO_BIN" mod download
GOOS="$HY2XS_TARGET_GOOS" GOARCH="$HY2XS_TARGET_ARCH" CGO_ENABLED=0 GOTOOLCHAIN=local "$GO_BIN" build \
-trimpath -ldflags "$admin_ldflags" -o "../$ADMIN_BUILD_DIR/hy2xs-admin" .
)
verify_admin_version_contract "$ADMIN_BUILD_DIR/hy2xs-admin"
install -m 0755 "$ADMIN_BUILD_DIR/hy2xs-admin" "$STAGE_DIR/ui/hy2xs-admin/hy2xs-admin"
if [ -f "$ui_src/docs/sql/schema.sql" ]; then
mkdir -p "$STAGE_DIR/ui/hy2xs-admin/docs/sql"
install -m 0644 "$ui_src/docs/sql/schema.sql" "$STAGE_DIR/ui/hy2xs-admin/docs/sql/schema.sql"
fi
# Оба утверждения выставляются только после dependency-free контрактов,
# runtime-компиляции словарей и успешной сборки production bundle.
FRONTEND_RUNTIME_TESTS_PASSED="true"
FRONTEND_TESTS_PASSED="true"
export FRONTEND_RUNTIME_TESTS_PASSED FRONTEND_TESTS_PASSED
}
write_metadata() {
local version="$1"
local build_id="$2"
local source_git_commit
local dirty_tree="false"
source_git_commit="$(git rev-parse --short=12 HEAD 2>/dev/null || echo unknown)"
if [ -n "$(git status --porcelain 2>/dev/null || true)" ]; then
dirty_tree="true"
fi
# Значения приходят из resolve_and_verify_hysteria: они уже проверены
# по SHA-256, по `hysteria version` и по compatibility gate.
[ -n "${HYSTERIA_VERSION:-}" ] || fail "HYSTERIA_VERSION was not resolved before write_metadata"
[ -n "${HYSTERIA_ARTIFACT_URL:-}" ] || fail "HYSTERIA_ARTIFACT_URL was not resolved before write_metadata"
[ -n "${HYSTERIA_ARTIFACT_SHA256:-}" ] || fail "HYSTERIA_ARTIFACT_SHA256 was not resolved before write_metadata"
validate_hysteria_version_format "$HYSTERIA_VERSION"
validate_hysteria_sha256_format "$HYSTERIA_ARTIFACT_SHA256"
# tests_gate — утверждение о готовом артефакте, а не переключатель.
#
# Значение у него ровно одно, потому что не бывает пакета, собранного с
# пропущенными тестами: обе функции выставляют свой флаг только после
# успешного прогона. Поле нужно, чтобы это было ВИДНО по tarball — раньше
# SKIP_TESTS=true выдавала внешне неотличимый production-артефакт.
[ "${ORCHESTRATOR_TESTS_PASSED:-false}" = "true" ] \
|| fail "write_metadata: контракты оркестратора не проверялись; тесты обязательны для релизного пакета"
[ "${ADMIN_TESTS_PASSED:-false}" = "true" ] \
|| fail "write_metadata: контракты админки не проверялись; тесты обязательны для релизного пакета"
[ "${FRONTEND_TESTS_PASSED:-false}" = "true" ] \
|| fail "write_metadata: контракты панели не проверялись; тесты обязательны для релизного пакета"
{
printf 'name=HY2XS\n'
printf 'license=AGPL-3.0-only\n'
printf 'version=%s\n' "$version"
printf 'release_line=%s\n' "$HY2XS_RELEASE_LINE"
printf 'config_schema_version=%s\n' "$HY2XS_CONFIG_SCHEMA_VERSION"
printf 'build_id=%s\n' "$build_id"
printf 'build_host_os=%s%s\n' "$HY2XS_BUILD_OS" "$HY2XS_BUILD_OS_VERSION"
printf 'build_host_arch=%s\n' "$HY2XS_BUILD_ARCH"
printf 'target_os=%s\n' "$HY2XS_TARGET_GOOS"
printf 'target_arch=%s\n' "$HY2XS_TARGET_ARCH"
printf 'target_distro=%s\n' "$HY2XS_TARGET_OS"
printf 'target_version=%s\n' "$HY2XS_TARGET_OS_VERSION"
printf 'source_git_commit=%s\n' "$source_git_commit"
printf 'dirty_tree=%s\n' "$dirty_tree"
printf 'build_profile=production\n'
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 'govulncheck_version=%s\n' "${GOVULNCHECK_VERSION:-unknown}"
printf 'dependency_security_gate=%s\n' "${DEPENDENCY_SECURITY_GATE:-unknown}"
printf 'tests_gate=true\n'
printf 'hysteria_source=official-upstream\n'
printf 'hysteria_target=linux-amd64\n'
printf 'hysteria_version=%s\n' "$HYSTERIA_VERSION"
printf 'hysteria_artifact_url=%s\n' "$HYSTERIA_ARTIFACT_URL"
printf 'hysteria_artifact_sha256=%s\n' "$HYSTERIA_ARTIFACT_SHA256"
printf 'hysteria_sha_source=%s\n' "${HYSTERIA_SHA_SOURCE:-unknown}"
printf 'hysteria_channel=%s\n' "${HYSTERIA_CHANNEL:-unknown}"
printf 'hysteria_resolution=%s\n' "${HYSTERIA_RESOLUTION:-unknown}"
printf 'hysteria_resolved_at=%s\n' "${HYSTERIA_RESOLVED_AT:-unknown}"
printf 'hysteria_compat_gate=%s\n' "${HYSTERIA_COMPAT_GATE:-unknown}"
} >"$STAGE_DIR/metadata/package.env"
printf '%s\n' "$HYSTERIA_VERSION" >"$STAGE_DIR/metadata/hysteria.version"
printf '%s\n' "$HYSTERIA_ARTIFACT_URL" >"$STAGE_DIR/metadata/hysteria.url"
printf '%s\n' "$HYSTERIA_ARTIFACT_SHA256" >"$STAGE_DIR/metadata/hysteria.sha256"
printf '%s\n' "${HYSTERIA_RESOLUTION:-unknown}" >"$STAGE_DIR/metadata/hysteria.resolution"
(
cd "$STAGE_DIR"
find . -type f ! -path './metadata/checksums.txt' -print0 \
| sort -z \
| xargs -0 sha256sum >metadata/checksums.txt
)
}
create_archive() {
local version="$1"
local archive="dist/hy2xs-install-${version}.tar.gz"
rm -f "$archive"
tar --owner=0 --group=0 --numeric-owner -C "tools/build/output" -czf "$archive" "hy2xs-install"
}