Files
HY2XS_flamy/tools/build/lib/verify.sh
T
founder 086b5d6624 build: закрепить новые инварианты приёмкой и документацией
verify_versions_contract получил сверку API namespace. Путь machine-auth
записывается в /etc/hysteria/config.yaml и в post-install.env, то есть по нему
Hysteria обращается к админке. Пока строка была продублирована в шаблонах,
smoke, тестах, приёмке и e2e, расхождение обнаруживалось только на живом
сервере. Теперь Go-константы, API_BASE фронтенда и оба шаблона сверяются
против значений, скомпилированных в оркестратор.

Приёмка проверяет, что:
  - fatal_pre_apply недостижим после записи install-state;
  - каждый ownership-флаг взводится раньше своего шага;
  - у read-only фазы нет универсального раннера, через который можно
    проскользнуть;
  - инвариант публичного endpoint живёт в preflight и не обращается к внешним
    сервисам определения IP;
  - purge-v0.sh и clean-host описывают одну границу;
  - секреты не попадают в персистентный файл экспорта;
  - импорт пиров валидируется так же строго, как их создание;
  - удалённые exportConfig/importConfig не вернулись.

Захардкоженная схема =2 в приёмке заменена на значение из versions.env: при
переходе на schema 3 пришлось бы помнить ещё и про эту строку.

Документация: контракт раннеров и ownership в 08, инвариант публичного
endpoint в 08/09/12/13 и README, сетевая идентичность панели и удалённые
export/import в 04, сценарии D1 (отказ сразу после PHASE 0) и D2 (устаревший
DNS после смены IPv4) в 11, версии package.json как не-версия продукта в 02.
2026-08-27 20:50:03 +05:00

167 lines
10 KiB
Bash
Raw 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
require_repo_layout() {
[ -f "LICENSE" ] || fail "missing LICENSE"
[ -f "versions.env" ] || fail "missing versions.env (product/platform/toolchain contract)"
[ -d "orchestrator/src" ] || fail "missing orchestrator/src"
[ -f "orchestrator/package.json" ] || fail "missing orchestrator/package.json"
[ -f "orchestrator/bun.lock" ] || fail "missing orchestrator/bun.lock"
[ -f "package/install.sh" ] || fail "missing package/install.sh"
[ -d "package/templates" ] || fail "missing package/templates"
[ -f "package/config/hy2xs.env" ] || fail "missing package/config/hy2xs.env"
[ -f "package/templates/env/post-install.env.tpl" ] || fail "missing package/templates/env/post-install.env.tpl"
[ -d "package/systemd" ] || fail "missing package/systemd"
[ -d "apps" ] || fail "missing apps HY2XS admin source"
[ -f "apps/go.mod" ] || fail "missing apps/go.mod"
[ -f "apps/go.sum" ] || fail "missing apps/go.sum"
[ -f "apps/frontend/package.json" ] || fail "missing apps/frontend/package.json"
[ -f "apps/frontend/pnpm-lock.yaml" ] || fail "missing apps/frontend/pnpm-lock.yaml"
}
verify_source_tree_policy() {
local allow_dirty="${ALLOW_DIRTY_BUILD:-false}"
local dirty="false"
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
fail "build must run inside git work tree"
fi
if [ -n "$(git status --porcelain 2>/dev/null || true)" ]; then
dirty="true"
fi
if [ "$dirty" = "true" ] && [ "$allow_dirty" != "true" ]; then
fail "dirty git tree is not allowed for production build; set ALLOW_DIRTY_BUILD=true to override"
fi
}
verify_archive() {
local version="$1"
local archive="dist/hy2xs-install-${version}.tar.gz"
[ -f "$archive" ] || fail "archive was not created: $archive"
local listing
listing="$(tar -tzf "$archive")"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/install.sh$' || fail "archive missing install.sh"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/LICENSE$' || fail "archive missing LICENSE"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/orchestrator/hy2xs-orchestrator$' || fail "archive missing orchestrator artifact"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/ui/hy2xs-admin/hy2xs-admin$' || fail "archive missing bundled UI binary"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/systemd/hysteria-server.service$' || fail "archive missing hysteria systemd unit"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/systemd/hy2xs-admin.service$' || fail "archive missing admin systemd unit"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/templates/hysteria/config.yaml.tpl$' || fail "archive missing Hysteria config template"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/config/hy2xs.env$' || fail "archive missing canonical runtime config"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/templates/env/post-install.env.tpl$' || fail "archive missing post-install env template"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/checksums.txt$' || fail "archive missing checksums"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/hysteria.version$' || fail "archive missing pinned Hysteria version"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/hysteria.url$' || fail "archive missing pinned Hysteria artifact url"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/hysteria.sha256$' || fail "archive missing pinned Hysteria sha256"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/hysteria.resolution$' || fail "archive missing Hysteria resolution marker"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/package.release_line$' || fail "archive missing product release line"
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/package.config_schema_version$' || fail "archive missing packaged config schema version"
# Двухфазный контракт установки проверяется структурно: read-only preflight
# обязан стоять раньше первой мутации, иначе ошибочный запуск поверх
# работающего сервера снова начнёт его ломать.
local packaged_installer preflight_line first_mutation_line
packaged_installer="$(tar -xOzf "$archive" hy2xs-install/install.sh)"
printf '%s\n' "$packaged_installer" | grep -q 'preflight-install' \
|| fail "packaged install.sh does not run the read-only preflight"
preflight_line="$(printf '%s\n' "$packaged_installer" | grep -n 'preflight-install --package-dir' | head -n1 | cut -d: -f1)"
first_mutation_line="$(printf '%s\n' "$packaged_installer" \
| grep -nE '^[[:space:]]*(install -d|install -m|ln -sf|rm -rf|cp -a)' | head -n1 | cut -d: -f1)"
[ -n "$preflight_line" ] || fail "packaged install.sh: could not locate the preflight invocation"
[ -n "$first_mutation_line" ] || fail "packaged install.sh: could not locate the mutation phase"
[ "$preflight_line" -lt "$first_mutation_line" ] \
|| fail "packaged install.sh mutates the host (line $first_mutation_line) before the clean-host preflight (line $preflight_line)"
local license_text
license_text="$(tar -xOzf "$archive" hy2xs-install/LICENSE)"
printf '%s\n' "$license_text" | grep -q 'GNU AFFERO GENERAL PUBLIC LICENSE' \
|| fail "packaged LICENSE is not the GNU Affero General Public License"
printf '%s\n' "$license_text" | grep -q 'Version 3, 19 November 2007' \
|| fail "packaged LICENSE is not AGPL version 3"
local env_content
env_content="$(tar -xOzf "$archive" hy2xs-install/config/hy2xs.env)"
printf '%s\n' "$env_content" | grep -q 'replace-with-your-domain.example' && fail "packaged hy2xs.env contains placeholder domain"
printf '%s\n' "$env_content" | grep -q 'replace-with-your-email@example.com' && fail "packaged hy2xs.env contains placeholder email"
local hysteria_tpl
hysteria_tpl="$(tar -xOzf "$archive" hy2xs-install/templates/hysteria/config.yaml.tpl)"
printf '%s\n' "$hysteria_tpl" | grep -q '/internal/hysteria/auth?access_token={{HYSTERIA_API_SECRET}}' || fail "hysteria auth template must include machine access_token"
printf '%s\n' "$hysteria_tpl" | grep -q '{{OBFS_BLOCK}}' || fail "hysteria template must render a typed obfs block"
# Замороженная версия обязана совпадать во всех местах пакета.
local pinned_version pinned_sha pinned_url meta_env
pinned_version="$(tar -xOzf "$archive" hy2xs-install/metadata/hysteria.version | tr -d '\n')"
pinned_sha="$(tar -xOzf "$archive" hy2xs-install/metadata/hysteria.sha256 | tr -d '\n')"
pinned_url="$(tar -xOzf "$archive" hy2xs-install/metadata/hysteria.url | tr -d '\n')"
meta_env="$(tar -xOzf "$archive" hy2xs-install/metadata/package.env)"
printf '%s' "$pinned_version" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|| fail "packaged Hysteria version is not a concrete vX.Y.Z: $pinned_version"
printf '%s' "$pinned_sha" | grep -Eq '^[a-f0-9]{64}$' \
|| fail "packaged Hysteria sha256 is not a 64-char hex digest"
printf '%s' "$pinned_url" | grep -Fq "$pinned_version" \
|| fail "packaged Hysteria artifact url does not point at the pinned version: $pinned_url"
if printf '%s' "$pinned_url" | grep -q 'latest'; then
fail "packaged Hysteria artifact url must be immutable, not a moving latest: $pinned_url"
fi
printf '%s\n' "$meta_env" | grep -q "^hysteria_version=${pinned_version}$" \
|| fail "metadata package.env disagrees with metadata/hysteria.version"
printf '%s\n' "$meta_env" | grep -q "^hysteria_artifact_sha256=${pinned_sha}$" \
|| fail "metadata package.env disagrees with metadata/hysteria.sha256"
local post_install_tpl
post_install_tpl="$(tar -xOzf "$archive" hy2xs-install/templates/env/post-install.env.tpl)"
printf '%s\n' "$post_install_tpl" | grep -q '^HY2_AUTH_URL=http://127.0.0.1:{{UI_PORT}}/internal/hysteria/auth?access_token={{HYSTERIA_API_SECRET}}$' || fail "post-install env template must include machine access_token in HY2_AUTH_URL"
local tmp
tmp="$(mktemp -d)"
tar -xzf "$archive" -C "$tmp"
[ -x "$tmp/hy2xs-install/install.sh" ] || fail "install.sh is not executable"
[ -x "$tmp/hy2xs-install/orchestrator/hy2xs-orchestrator" ] || fail "orchestrator is not executable"
[ -x "$tmp/hy2xs-install/ui/hy2xs-admin/hy2xs-admin" ] || fail "hy2xs-admin is not executable"
"$tmp/hy2xs-install/orchestrator/hy2xs-orchestrator" status --package-dir "$tmp/hy2xs-install" >/dev/null 2>&1 || fail "orchestrator status sanity check failed"
local meta
meta="$(cat "$tmp/hy2xs-install/metadata/package.env")"
printf '%s\n' "$meta" | grep -q '^source_git_commit=' || fail "metadata missing source_git_commit"
printf '%s\n' "$meta" | grep -q '^dirty_tree=' || fail "metadata missing dirty_tree"
printf '%s\n' "$meta" | grep -q '^build_profile=production$' || fail "metadata missing build_profile=production"
printf '%s\n' "$meta" | grep -q '^license=AGPL-3.0-only$' || fail "metadata missing license=AGPL-3.0-only"
# Поколение продукта обязано доехать до пакета: install-state строится из
# него, а reconfigure/repair по нему отличают v1 от чужой установки.
printf '%s\n' "$meta" | grep -q "^version=${HY2XS_VERSION}$" \
|| fail "metadata version disagrees with versions.env HY2XS_VERSION=${HY2XS_VERSION}"
printf '%s\n' "$meta" | grep -q "^release_line=${HY2XS_RELEASE_LINE}$" \
|| fail "metadata release_line disagrees with versions.env HY2XS_RELEASE_LINE=${HY2XS_RELEASE_LINE}"
printf '%s\n' "$meta" | grep -q "^config_schema_version=${HY2XS_CONFIG_SCHEMA_VERSION}$" \
|| fail "metadata config_schema_version disagrees with versions.env"
printf '%s\n' "$meta" | grep -q "^target_version=${HY2XS_TARGET_OS_VERSION}$" \
|| fail "metadata target_version disagrees with versions.env"
printf '%s\n' "$meta" | grep -q "^target_arch=${HY2XS_TARGET_ARCH}$" \
|| fail "metadata target_arch disagrees with versions.env"
local packaged_schema
packaged_schema="$(grep -E '^HY2XS_CONFIG_SCHEMA_VERSION=' "$tmp/hy2xs-install/config/hy2xs.env" | head -n1 | cut -d= -f2-)"
[ "$packaged_schema" = "$HY2XS_CONFIG_SCHEMA_VERSION" ] \
|| fail "packaged hy2xs.env declares schema $packaged_schema, versions.env declares $HY2XS_CONFIG_SCHEMA_VERSION"
verify_admin_version_contract "$tmp/hy2xs-install/ui/hy2xs-admin/hy2xs-admin"
if declare -F run_fix20_acceptance_subset >/dev/null 2>&1; then
run_fix20_acceptance_subset "$tmp/hy2xs-install"
fi
rm -rf "$tmp"
}