feat(v1): Gecko-обфускация, latest-stable Hysteria на сборке и forward-compatible admin
Сквозная миграция HY2XS на современную Hysteria (2.12.2) и переход на v1. Build: - версия Hysteria резолвится на этапе сборки из HyNetworks/hysteria и замораживается в metadata пакета (version + immutable url + sha256); - compatibility gate: реальный бинарник должен принять канонический конфиг HY2XS для gecko и salamander до создания пакета; - сборка прогоняет тесты оркестратора и админки. Конфигурационный контракт: - HY2XS_CONFIG_SCHEMA_VERSION=2, чужая схема отклоняется fail-fast; - obfs стал настоящим union gecko|salamander, gecko — default; - obfs-блок рендерится оркестратором целиком, два подтипа одновременно структурно невозможны; - современный baseline: congestion bbr/standard, disableLossCompensation=false, disableStatelessReset=false, полный quic-блок. Исправления: - share URI для gecko: генератор был завязан на Obfs.Salamander.Password и выдавал нерабочую ссылку при любой другой обфускации; - SNI брался только из ACME-блока и уходил пустым при HY2XS_TLS_MODE=file; - экспорт конфига выносил trafficStats.secret, access_token и obfs-пароль; - экспорт терял неизвестные upstream-поля при round-trip через типизированную модель; - renderRuntimeEnv печатал тип обфускации литералом, расходясь с конфигом; - namedotcom удалён из ACME-реестра (нет в Hysteria с 2.11.0). Тесты: - 95 тестов оркестратора: env, рендер, семантика профиля, резолвер, rollover; - тесты URI и экспорта в Go; - tools/test/e2e-hysteria.sh с реальным клиентом Hysteria. UX: - подсказки и примеры в форме создания пира. Прочее: CHANGELOG.md, .gitattributes (LF для target-side файлов), документация на русском.
This commit is contained in:
@@ -30,6 +30,87 @@ run_fix20_acceptance_subset() {
|
||||
log_step "Acceptance: force password change production default"
|
||||
grep -q '^HY2XS_FORCE_PASSWORD_CHANGE=false$' "$package_dir/config/hy2xs.env" || fail "acceptance: HY2XS_FORCE_PASSWORD_CHANGE must default to false"
|
||||
|
||||
log_step "Acceptance: config schema version is declared"
|
||||
grep -q '^HY2XS_CONFIG_SCHEMA_VERSION=2$' "$package_dir/config/hy2xs.env" \
|
||||
|| fail "acceptance: HY2XS_CONFIG_SCHEMA_VERSION must be 2 in the packaged baseline"
|
||||
|
||||
log_step "Acceptance: fresh install defaults to Gecko obfuscation"
|
||||
grep -q '^HY2XS_HYSTERIA_OBFS_TYPE=gecko$' "$package_dir/config/hy2xs.env" \
|
||||
|| fail "acceptance: fresh installations must default to HY2XS_HYSTERIA_OBFS_TYPE=gecko"
|
||||
|
||||
log_step "Acceptance: obfs type is not hardcoded in the Hysteria template"
|
||||
grep -q '{{OBFS_BLOCK}}' "$package_dir/templates/hysteria/config.yaml.tpl" \
|
||||
|| fail "acceptance: hysteria template must render the obfs block from the orchestrator"
|
||||
! grep -Eq '^\s*type:\s*(gecko|salamander)\s*$' "$package_dir/templates/hysteria/config.yaml.tpl" \
|
||||
|| fail "acceptance: hysteria template must not hardcode an obfs type"
|
||||
|
||||
log_step "Acceptance: modern server baseline is present in the template"
|
||||
grep -q 'disableLossCompensation: {{DISABLE_LOSS_COMPENSATION}}' "$package_dir/templates/hysteria/config.yaml.tpl" \
|
||||
|| fail "acceptance: bandwidth.disableLossCompensation missing from hysteria template"
|
||||
grep -q '{{CONGESTION_BLOCK}}' "$package_dir/templates/hysteria/config.yaml.tpl" \
|
||||
|| fail "acceptance: congestion block missing from hysteria template"
|
||||
grep -q '{{QUIC_BLOCK}}' "$package_dir/templates/hysteria/config.yaml.tpl" \
|
||||
|| fail "acceptance: quic block missing from hysteria template"
|
||||
|
||||
log_step "Acceptance: post-install env derives obfs type from resolved context"
|
||||
grep -q '^HY2_OBFS_TYPE={{OBFS_TYPE}}$' "$package_dir/templates/env/post-install.env.tpl" \
|
||||
|| fail "acceptance: post-install env must render the resolved obfs type, not a second set of defaults"
|
||||
! grep -Eq '^HY2_OBFS_TYPE=(gecko|salamander)$' "$package_dir/templates/env/post-install.env.tpl" \
|
||||
|| fail "acceptance: post-install env must not hardcode an obfs type"
|
||||
|
||||
log_step "Acceptance: production defaults are declared in exactly one module"
|
||||
grep -q 'DEFAULT_HYSTERIA_OBFS_TYPE' orchestrator/src/config/profile.ts \
|
||||
|| fail "acceptance: the default obfs type must be declared in orchestrator/src/config/profile.ts"
|
||||
local default_declarations
|
||||
default_declarations="$(grep -rl 'DEFAULT_HYSTERIA_OBFS_TYPE\s*[:=]' orchestrator/src \
|
||||
| grep -v 'orchestrator/src/config/profile.ts' || true)"
|
||||
[ -z "$default_declarations" ] \
|
||||
|| fail "acceptance: the default obfs type must not be re-declared in: $default_declarations"
|
||||
|
||||
log_step "Acceptance: runtime env is derived from config, not literals"
|
||||
! grep -Eq 'HY2XS_HYSTERIA_OBFS_TYPE=(gecko|salamander)' orchestrator/src/config/env.ts \
|
||||
|| fail "acceptance: renderRuntimeEnv must not print a hardcoded obfs type"
|
||||
grep -q 'HY2XS_HYSTERIA_OBFS_TYPE=\${config.hysteriaObfsType}' orchestrator/src/config/env.ts \
|
||||
|| fail "acceptance: renderRuntimeEnv must derive the obfs type from the parsed config"
|
||||
! grep -Eq '\|\|\s*"(gecko|salamander)"' orchestrator/src/config/env.ts \
|
||||
|| fail "acceptance: env.ts must not carry its own obfs fallback default"
|
||||
|
||||
log_step "Acceptance: package metadata records how the Hysteria version was chosen"
|
||||
grep -q '^hysteria_resolution=' "$package_dir/metadata/package.env" \
|
||||
|| fail "acceptance: metadata must record hysteria_resolution"
|
||||
grep -q '^hysteria_resolved_at=' "$package_dir/metadata/package.env" \
|
||||
|| fail "acceptance: metadata must record hysteria_resolved_at"
|
||||
grep -q '^hysteria_compat_gate=true$' "$package_dir/metadata/package.env" \
|
||||
|| fail "acceptance: release packages must be built with the Hysteria compatibility gate enabled"
|
||||
grep -Eq '^hysteria_artifact_url=https://github\.com/HyNetworks/hysteria/' "$package_dir/metadata/package.env" \
|
||||
|| fail "acceptance: Hysteria artifact must come from the canonical HyNetworks upstream"
|
||||
|
||||
log_step "Acceptance: install-time never resolves a moving latest"
|
||||
! grep -rq 'api.github.com' orchestrator/src/commands orchestrator/src/steps \
|
||||
|| fail "acceptance: install-time code must not query the upstream release API"
|
||||
! grep -rq 'download.hysteria.network' orchestrator/src \
|
||||
|| fail "acceptance: install-time code must not use the moving latest download URL"
|
||||
|
||||
log_step "Acceptance: smoke verifies config semantics, not substrings"
|
||||
grep -q 'assertHysteriaConfigMatchesProfile' orchestrator/src/steps/smoke.ts \
|
||||
|| fail "acceptance: smoke must verify the effective config semantically"
|
||||
|
||||
log_step "Acceptance: admin export preserves unknown upstream fields and strips secrets"
|
||||
grep -q 'ExportHysteria2ConfigYaml' apps/controller/config.go \
|
||||
|| fail "acceptance: hysteria config export must go through the sanitizing raw-YAML path"
|
||||
grep -q 'GetRawHysteria2Config' apps/service/hysteria2_export.go \
|
||||
|| fail "acceptance: export must read the raw YAML instead of the typed model"
|
||||
|
||||
log_step "Acceptance: frontend ACME registry matches current upstream"
|
||||
# Ищем именно предлагаемое значение, а не упоминание в комментарии.
|
||||
! grep -q '"namedotcom"' apps/frontend/src/views/hysteria/list/index.vue \
|
||||
|| fail "acceptance: namedotcom was removed upstream in Hysteria 2.11.0 and must not be offered"
|
||||
local provider
|
||||
for provider in cloudflare duckdns gandi godaddy namecheap njalla porkbun vultr; do
|
||||
grep -q "\"${provider}\"" apps/frontend/src/views/hysteria/list/index.vue \
|
||||
|| fail "acceptance: ACME DNS provider ${provider} is missing from the UI registry"
|
||||
done
|
||||
|
||||
log_step "Acceptance: systemd unit production env"
|
||||
grep -q '^Environment=GIN_MODE=release$' "$package_dir/systemd/hy2xs-admin.service" || fail "acceptance: GIN_MODE=release missing"
|
||||
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Разрешение upstream-версии Hysteria и compatibility gate.
|
||||
#
|
||||
# Ключевой инвариант: "latest" вычисляется на build machine и замораживается
|
||||
# в metadata пакета. Target-сервер никогда не обращается к moving "latest".
|
||||
|
||||
HYSTERIA_LOCK_FILE="tools/build/hysteria-lock.env"
|
||||
HYSTERIA_WORK_DIR="tools/build/output/hysteria"
|
||||
|
||||
# stable — определить последнюю стабильную версию через upstream API (default);
|
||||
# pinned — использовать закоммиченный tools/build/hysteria-lock.env без сети.
|
||||
HYSTERIA_CHANNEL="${HYSTERIA_CHANNEL:-stable}"
|
||||
# Явный пин конкретной версии вида v2.12.2. Побеждает автоматический выбор.
|
||||
HYSTERIA_VERSION_OVERRIDE="${HYSTERIA_VERSION_OVERRIDE:-}"
|
||||
# Обновлять ли tools/build/hysteria-lock.env резолвнутыми значениями.
|
||||
HYSTERIA_WRITE_LOCK="${HYSTERIA_WRITE_LOCK:-false}"
|
||||
# Compatibility gate обязателен для production-сборки.
|
||||
HYSTERIA_COMPAT_GATE="${HYSTERIA_COMPAT_GATE:-true}"
|
||||
|
||||
HYSTERIA_GATE_PORT="${HYSTERIA_GATE_PORT:-34443}"
|
||||
HYSTERIA_GATE_STATS_PORT="${HYSTERIA_GATE_STATS_PORT:-34712}"
|
||||
HYSTERIA_GATE_TIMEOUT_SECONDS="${HYSTERIA_GATE_TIMEOUT_SECONDS:-15}"
|
||||
|
||||
validate_hysteria_version_format() {
|
||||
local value="$1"
|
||||
printf '%s' "$value" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|
||||
|| fail "invalid Hysteria version format: $value (expected vX.Y.Z)"
|
||||
}
|
||||
|
||||
validate_hysteria_sha256_format() {
|
||||
local value="$1"
|
||||
case "$value" in
|
||||
replace-with-*|"")
|
||||
fail "HYSTERIA_ARTIFACT_SHA256 must be a real release sha256"
|
||||
;;
|
||||
esac
|
||||
printf '%s' "$value" | grep -Eq '^[a-fA-F0-9]{64}$' \
|
||||
|| fail "HYSTERIA_ARTIFACT_SHA256 must be a 64-char hex SHA256, got: $value"
|
||||
}
|
||||
|
||||
load_pinned_hysteria_lock() {
|
||||
[ -f "$HYSTERIA_LOCK_FILE" ] || fail "missing Hysteria lock file: $HYSTERIA_LOCK_FILE"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "$HYSTERIA_LOCK_FILE"
|
||||
|
||||
[ -n "${HYSTERIA_VERSION:-}" ] || fail "HYSTERIA_VERSION is required in $HYSTERIA_LOCK_FILE"
|
||||
[ -n "${HYSTERIA_ARTIFACT_URL:-}" ] || fail "HYSTERIA_ARTIFACT_URL is required in $HYSTERIA_LOCK_FILE"
|
||||
[ -n "${HYSTERIA_ARTIFACT_SHA256:-}" ] || fail "HYSTERIA_ARTIFACT_SHA256 is required in $HYSTERIA_LOCK_FILE"
|
||||
|
||||
validate_hysteria_version_format "$HYSTERIA_VERSION"
|
||||
validate_hysteria_sha256_format "$HYSTERIA_ARTIFACT_SHA256"
|
||||
|
||||
HYSTERIA_RESOLUTION="pinned"
|
||||
HYSTERIA_RESOLVED_AT="${HYSTERIA_RESOLVED_AT:-unknown}"
|
||||
|
||||
export HYSTERIA_VERSION HYSTERIA_ARTIFACT_URL HYSTERIA_ARTIFACT_SHA256 HYSTERIA_RESOLUTION HYSTERIA_RESOLVED_AT
|
||||
}
|
||||
|
||||
resolve_hysteria_from_upstream() {
|
||||
local resolver_args=(--channel stable)
|
||||
if [ -n "$HYSTERIA_VERSION_OVERRIDE" ]; then
|
||||
validate_hysteria_version_format "$HYSTERIA_VERSION_OVERRIDE"
|
||||
resolver_args+=(--version "$HYSTERIA_VERSION_OVERRIDE")
|
||||
log_info "Resolving pinned Hysteria version override: $HYSTERIA_VERSION_OVERRIDE"
|
||||
else
|
||||
log_info "Resolving latest stable Hysteria release from upstream"
|
||||
fi
|
||||
|
||||
local resolver_output
|
||||
resolver_output="$(cd orchestrator && "$BUN_BIN" run tools/resolve-hysteria.ts "${resolver_args[@]}")" \
|
||||
|| fail "Hysteria release resolution failed"
|
||||
|
||||
local key value line
|
||||
HYSTERIA_VERSION=""
|
||||
HYSTERIA_TAG=""
|
||||
HYSTERIA_ARTIFACT_URL=""
|
||||
HYSTERIA_PUBLISHED_AT=""
|
||||
HYSTERIA_RESOLUTION=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
[ -n "$line" ] || continue
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
case "$key" in
|
||||
HYSTERIA_VERSION) HYSTERIA_VERSION="$value" ;;
|
||||
HYSTERIA_TAG) HYSTERIA_TAG="$value" ;;
|
||||
HYSTERIA_ARTIFACT_URL) HYSTERIA_ARTIFACT_URL="$value" ;;
|
||||
HYSTERIA_PUBLISHED_AT) HYSTERIA_PUBLISHED_AT="$value" ;;
|
||||
HYSTERIA_RESOLUTION) HYSTERIA_RESOLUTION="$value" ;;
|
||||
*) fail "unexpected resolver output line: $line" ;;
|
||||
esac
|
||||
done <<<"$resolver_output"
|
||||
|
||||
[ -n "$HYSTERIA_VERSION" ] || fail "resolver did not return HYSTERIA_VERSION"
|
||||
[ -n "$HYSTERIA_ARTIFACT_URL" ] || fail "resolver did not return HYSTERIA_ARTIFACT_URL"
|
||||
validate_hysteria_version_format "$HYSTERIA_VERSION"
|
||||
|
||||
HYSTERIA_RESOLVED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
log_info "Resolved Hysteria $HYSTERIA_VERSION (tag ${HYSTERIA_TAG:-unknown}, published ${HYSTERIA_PUBLISHED_AT:-unknown})"
|
||||
|
||||
export HYSTERIA_VERSION HYSTERIA_TAG HYSTERIA_ARTIFACT_URL HYSTERIA_PUBLISHED_AT
|
||||
export HYSTERIA_RESOLUTION HYSTERIA_RESOLVED_AT
|
||||
}
|
||||
|
||||
download_hysteria_artifact() {
|
||||
mkdir -p "$HYSTERIA_WORK_DIR"
|
||||
HYSTERIA_BINARY="$HYSTERIA_WORK_DIR/hysteria-linux-amd64"
|
||||
|
||||
log_info "Downloading $HYSTERIA_ARTIFACT_URL"
|
||||
rm -f "$HYSTERIA_BINARY"
|
||||
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
|
||||
"$HYSTERIA_ARTIFACT_URL" -o "$HYSTERIA_BINARY" \
|
||||
|| fail "failed to download Hysteria artifact: $HYSTERIA_ARTIFACT_URL"
|
||||
[ -s "$HYSTERIA_BINARY" ] || fail "downloaded Hysteria artifact is empty"
|
||||
|
||||
local actual_sha
|
||||
actual_sha="$(sha256sum "$HYSTERIA_BINARY" | awk '{print $1}')"
|
||||
|
||||
if [ -n "${HYSTERIA_ARTIFACT_SHA256:-}" ]; then
|
||||
validate_hysteria_sha256_format "$HYSTERIA_ARTIFACT_SHA256"
|
||||
[ "$actual_sha" = "$HYSTERIA_ARTIFACT_SHA256" ] \
|
||||
|| fail "Hysteria artifact sha256 mismatch: expected $HYSTERIA_ARTIFACT_SHA256, got $actual_sha"
|
||||
else
|
||||
HYSTERIA_ARTIFACT_SHA256="$actual_sha"
|
||||
log_info "Computed Hysteria artifact sha256: $HYSTERIA_ARTIFACT_SHA256"
|
||||
fi
|
||||
|
||||
export HYSTERIA_ARTIFACT_SHA256 HYSTERIA_BINARY
|
||||
|
||||
chmod 0755 "$HYSTERIA_BINARY"
|
||||
|
||||
local reported_version
|
||||
reported_version="$("$HYSTERIA_BINARY" version 2>/dev/null | grep -Eo 'v[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || true)"
|
||||
[ -n "$reported_version" ] || fail "unable to read version from downloaded Hysteria binary"
|
||||
[ "$reported_version" = "$HYSTERIA_VERSION" ] \
|
||||
|| fail "downloaded Hysteria binary reports $reported_version, expected $HYSTERIA_VERSION"
|
||||
|
||||
log_info "Verified Hysteria binary: $HYSTERIA_VERSION ($HYSTERIA_ARTIFACT_SHA256)"
|
||||
}
|
||||
|
||||
assert_gate_port_is_free() {
|
||||
local port="$1"
|
||||
local proto_flag="$2"
|
||||
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
if ss -H "$proto_flag" 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${port}$"; then
|
||||
fail "compatibility gate port $port is already in use; override HYSTERIA_GATE_PORT/HYSTERIA_GATE_STATS_PORT"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
gate_generate_self_signed_cert() {
|
||||
local dir="$1"
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 1 \
|
||||
-subj /CN=hy2xs-compat-gate \
|
||||
-keyout "$dir/gate.key" -out "$dir/gate.crt" >/dev/null 2>&1 \
|
||||
|| fail "compatibility gate could not generate a self-signed certificate"
|
||||
}
|
||||
|
||||
gate_run_single_profile() {
|
||||
local obfs_type="$1"
|
||||
local gate_dir="$2"
|
||||
local config_path="$gate_dir/config-$obfs_type.yaml"
|
||||
local log_path="$gate_dir/hysteria-$obfs_type.log"
|
||||
|
||||
log_info "Compatibility gate: rendering canonical config (obfs=$obfs_type)"
|
||||
(
|
||||
cd orchestrator
|
||||
"$BUN_BIN" run tools/render-canonical-config.ts \
|
||||
--package-dir ../package \
|
||||
--obfs "$obfs_type" \
|
||||
--tls-mode file \
|
||||
--cert "$gate_dir/gate.crt" \
|
||||
--key "$gate_dir/gate.key" \
|
||||
--port "$HYSTERIA_GATE_PORT" \
|
||||
--traffic-stats-port "$HYSTERIA_GATE_STATS_PORT" \
|
||||
--out "$config_path"
|
||||
) || fail "BUILD FAILED: HY2XS could not render canonical config for obfs=$obfs_type"
|
||||
|
||||
grep -q "type: $obfs_type" "$config_path" \
|
||||
|| fail "compatibility gate rendered a config without obfs type $obfs_type"
|
||||
|
||||
log_info "Compatibility gate: starting Hysteria $HYSTERIA_VERSION with generated config (obfs=$obfs_type)"
|
||||
|
||||
HYSTERIA_DISABLE_UPDATE_CHECK=1 "$HYSTERIA_BINARY" server -c "$config_path" >"$log_path" 2>&1 &
|
||||
local gate_pid=$!
|
||||
|
||||
local ready="false"
|
||||
local waited=0
|
||||
while [ "$waited" -lt "$HYSTERIA_GATE_TIMEOUT_SECONDS" ]; do
|
||||
if ! kill -0 "$gate_pid" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
if command -v ss >/dev/null 2>&1 \
|
||||
&& ss -H -lun 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${HYSTERIA_GATE_PORT}$"; then
|
||||
ready="true"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
|
||||
if [ "$ready" != "true" ]; then
|
||||
kill "$gate_pid" 2>/dev/null || true
|
||||
wait "$gate_pid" 2>/dev/null || true
|
||||
log_info "--- Hysteria compatibility gate output (obfs=$obfs_type) ---"
|
||||
cat "$log_path" >&2 || true
|
||||
fail "BUILD FAILED: unsupported Hysteria stable $HYSTERIA_VERSION (obfs=$obfs_type config was rejected)"
|
||||
fi
|
||||
|
||||
kill "$gate_pid" 2>/dev/null || true
|
||||
wait "$gate_pid" 2>/dev/null || true
|
||||
|
||||
log_info "Compatibility gate passed for obfs=$obfs_type"
|
||||
}
|
||||
|
||||
run_hysteria_compatibility_gate() {
|
||||
if [ "$HYSTERIA_COMPAT_GATE" != "true" ]; then
|
||||
log_info "WARNING: Hysteria compatibility gate is disabled (HYSTERIA_COMPAT_GATE=$HYSTERIA_COMPAT_GATE)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
require_tool openssl
|
||||
[ -x "${HYSTERIA_BINARY:-}" ] || fail "compatibility gate requires a verified Hysteria binary"
|
||||
|
||||
assert_gate_port_is_free "$HYSTERIA_GATE_PORT" "-lun"
|
||||
assert_gate_port_is_free "$HYSTERIA_GATE_STATS_PORT" "-ltn"
|
||||
|
||||
local gate_dir
|
||||
gate_dir="$(mktemp -d)"
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -rf '$gate_dir'" RETURN
|
||||
|
||||
gate_generate_self_signed_cert "$gate_dir"
|
||||
|
||||
gate_run_single_profile "gecko" "$gate_dir"
|
||||
gate_run_single_profile "salamander" "$gate_dir"
|
||||
|
||||
log_info "Compatibility gate: Hysteria $HYSTERIA_VERSION accepts the HY2XS production config"
|
||||
}
|
||||
|
||||
write_hysteria_lock_file() {
|
||||
if [ "$HYSTERIA_WRITE_LOCK" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Updating $HYSTERIA_LOCK_FILE with resolved values"
|
||||
cat >"$HYSTERIA_LOCK_FILE" <<EOF
|
||||
# Файл генерируется tools/build/lib/hysteria.sh.
|
||||
# HYSTERIA_CHANNEL=pinned использует эти значения без обращения к сети.
|
||||
HYSTERIA_VERSION=$HYSTERIA_VERSION
|
||||
HYSTERIA_ARTIFACT_URL=$HYSTERIA_ARTIFACT_URL
|
||||
HYSTERIA_ARTIFACT_SHA256=$HYSTERIA_ARTIFACT_SHA256
|
||||
HYSTERIA_RESOLUTION=$HYSTERIA_RESOLUTION
|
||||
HYSTERIA_RESOLVED_AT=$HYSTERIA_RESOLVED_AT
|
||||
EOF
|
||||
}
|
||||
|
||||
resolve_and_verify_hysteria() {
|
||||
case "$HYSTERIA_CHANNEL" in
|
||||
stable)
|
||||
resolve_hysteria_from_upstream
|
||||
# sha256 считается локально от скачанного артефакта
|
||||
HYSTERIA_ARTIFACT_SHA256=""
|
||||
;;
|
||||
pinned)
|
||||
log_info "Using pinned Hysteria lock file (offline): $HYSTERIA_LOCK_FILE"
|
||||
load_pinned_hysteria_lock
|
||||
;;
|
||||
*)
|
||||
fail "unsupported HYSTERIA_CHANNEL: $HYSTERIA_CHANNEL (use stable or pinned)"
|
||||
;;
|
||||
esac
|
||||
|
||||
download_hysteria_artifact
|
||||
run_hysteria_compatibility_gate
|
||||
write_hysteria_lock_file
|
||||
}
|
||||
+44
-14
@@ -3,7 +3,6 @@ set -euo pipefail
|
||||
|
||||
STAGE_DIR="tools/build/output/hy2xs-install"
|
||||
ADMIN_BUILD_DIR="tools/build/output/hy2xs-admin-build"
|
||||
HYSTERIA_LOCK_FILE="tools/build/hysteria-lock.env"
|
||||
|
||||
prepare_stage() {
|
||||
local version="$1"
|
||||
@@ -27,6 +26,38 @@ prepare_stage() {
|
||||
printf '%s\n' "$build_id" >"$STAGE_DIR/metadata/package.build_id"
|
||||
}
|
||||
|
||||
run_orchestrator_tests() {
|
||||
if [ "${SKIP_TESTS:-false}" = "true" ]; then
|
||||
log_info "WARNING: orchestrator tests skipped (SKIP_TESTS=true)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
(
|
||||
cd orchestrator
|
||||
"$BUN_BIN" install --frozen-lockfile
|
||||
"$BUN_BIN" x tsc --noEmit
|
||||
"$BUN_BIN" test
|
||||
) || fail "orchestrator contract tests failed"
|
||||
}
|
||||
|
||||
run_admin_tests() {
|
||||
if [ "${SKIP_TESTS:-false}" = "true" ]; then
|
||||
log_info "WARNING: HY2XS admin tests skipped (SKIP_TESTS=true)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# `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"
|
||||
}
|
||||
|
||||
build_orchestrator() {
|
||||
local bun_compile_target="${BUN_COMPILE_TARGET:-bun-linux-x64}"
|
||||
|
||||
@@ -88,19 +119,13 @@ write_metadata() {
|
||||
dirty_tree="true"
|
||||
fi
|
||||
|
||||
[ -f "$HYSTERIA_LOCK_FILE" ] || fail "missing Hysteria lock file: $HYSTERIA_LOCK_FILE"
|
||||
# shellcheck disable=SC1090
|
||||
. "$HYSTERIA_LOCK_FILE"
|
||||
[ -n "${HYSTERIA_VERSION:-}" ] || fail "HYSTERIA_VERSION is required in $HYSTERIA_LOCK_FILE"
|
||||
[ -n "${HYSTERIA_ARTIFACT_URL:-}" ] || fail "HYSTERIA_ARTIFACT_URL is required in $HYSTERIA_LOCK_FILE"
|
||||
[ -n "${HYSTERIA_ARTIFACT_SHA256:-}" ] || fail "HYSTERIA_ARTIFACT_SHA256 is required in $HYSTERIA_LOCK_FILE"
|
||||
case "$HYSTERIA_ARTIFACT_SHA256" in
|
||||
replace-with-*|"")
|
||||
fail "HYSTERIA_ARTIFACT_SHA256 must be a real release sha256"
|
||||
;;
|
||||
esac
|
||||
printf '%s' "$HYSTERIA_ARTIFACT_SHA256" | grep -Eq '^[a-fA-F0-9]{64}$' \
|
||||
|| fail "HYSTERIA_ARTIFACT_SHA256 must be a 64-char hex SHA256"
|
||||
# Значения приходят из 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"
|
||||
|
||||
{
|
||||
printf 'name=HY2XS\n'
|
||||
@@ -127,11 +152,16 @@ write_metadata() {
|
||||
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_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"
|
||||
|
||||
@@ -54,6 +54,10 @@ verify_archive() {
|
||||
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"
|
||||
|
||||
local license_text
|
||||
license_text="$(tar -xOzf "$archive" hy2xs-install/LICENSE)"
|
||||
@@ -70,6 +74,29 @@ verify_archive() {
|
||||
local hysteria_tpl
|
||||
hysteria_tpl="$(tar -xOzf "$archive" hy2xs-install/templates/hysteria/config.yaml.tpl)"
|
||||
printf '%s\n' "$hysteria_tpl" | grep -q '/hui/hysteria2/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)"
|
||||
|
||||
Reference in New Issue
Block a user