fix(v1): сделать отзыв доступа, бэкап и диагностику соответствующими своим именам
Проход по операциям, которые делают не то, что обещает их имя. P0. Удаление bootstrap-admin-peer не было отзывом доступа. Признаком «создавать пир или нет» служило наличие строки в таблице, а HY2XS_ADMIN_CON_PASS продолжает жить в /etc/hy2xs/hy2xs.env — его читает systemd-юнит. Оператор удалял пира, доступ исчезал, и ближайший restart возвращал того же пира с тем же секретом. Молча. Признаком стала отметка BOOTSTRAP_PEER_SEEDED в таблице config: «создавался когда-либо», а не «существует сейчас». Отметка и пир пишутся одной транзакцией. P1. Резервная копия с includeSecrets=true проглатывала и ошибку расшифровки, и отсутствие шифртекста, отдавая пира с пустым secret и успешный ответ. Теперь недоступный секрет любого пира отклоняет весь запрос с указанием имени. P1. DecryptPeerSecret возвращала содержимое колонки как расшифрованный секрет, если оно не начиналось с v1: — остаток поколения с открытыми секретами. P1. doctor перезапускал hysteria-server и hy2xs-admin: диагностика подозрения на проблему обрывала все живые соединения. P1. Админка сама генерировала HYSTERIA2_TRAFFIC_STATS_SECRET, записать который в /etc/hysteria/config.yaml она не может. Сервис объявлял себя здоровым, а machine auth переставал совпадать. P1. Обходы проверки зависимостей (accepted-risk/skipped) не могли произвести артефакт: приёмка требует dependency_security_gate=true. Удалены из сборки и документации, отсутствие проверяется приёмкой. P2. UPDATE по отсутствующей строке config считался успехом, и cron перепланировался при несохранённом значении. Решение по RowsAffected. P2. Слой данных не отличал «записи нет» от «база не ответила»: sentinel-значения ErrPeerNotFound / ErrAdminUserNotFound / ErrConfigNotFound / ErrStorage. P2. Удалены алиасы /:id/client-url и /:id/qr. Контракт разработки: apps/go.mod объявляет toolchain go1.26.7 (директива go — языковой baseline, а не выбор компилятора), tools/dev/doctor.sh|.ps1 сверяют среду с versions.env.
This commit is contained in:
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Проверка среды разработки против versions.env.
|
||||
#
|
||||
# Зачем.
|
||||
#
|
||||
# versions.env — единый контракт «продукт / платформа / toolchain», и сборка
|
||||
# релиза его соблюдает: она скачивает Go, Node и Bun ровно тех версий, что
|
||||
# записаны здесь, сверяя контрольные суммы. Машина разработчика при этом не
|
||||
# проверялась НИКАК, поэтому расхождение обнаруживалось только на Debian, внутри
|
||||
# release-сборки, и выглядело как «у меня работало».
|
||||
#
|
||||
# Расхождение не гипотетическое: локальный Go 1.25 собирал проект успешно (`go`
|
||||
# в go.mod — языковой baseline, а не выбор компилятора), а релизный бинарь
|
||||
# компилировался на 1.26.7 и наследовал ЕЁ stdlib. То есть проверялся не тот
|
||||
# код, который уезжает в production.
|
||||
#
|
||||
# Скрипт ничего не устанавливает и не меняет: он отвечает на один вопрос —
|
||||
# совпадает ли эта машина с контрактом сборки.
|
||||
#
|
||||
# Использование:
|
||||
# ./tools/dev/doctor.sh проверить и вернуть код выхода
|
||||
# ./tools/dev/doctor.sh --quiet только итог
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
QUIET=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--quiet) QUIET=true ;;
|
||||
-h|--help)
|
||||
printf 'usage: %s [--quiet]\n' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
printf 'unknown argument: %s\n' "$arg" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -f versions.env ] || {
|
||||
printf 'versions.env не найден: doctor запускается из репозитория HY2XS\n' >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. ./versions.env
|
||||
|
||||
FAILURES=0
|
||||
|
||||
say() {
|
||||
[ "$QUIET" = true ] || printf '%s\n' "$1"
|
||||
}
|
||||
|
||||
# Версия извлекается из вывода самого инструмента: спрашивать `которая версия
|
||||
# записана в PATH` бессмысленно, отвечать должен тот бинарь, который реально
|
||||
# запустится.
|
||||
tool_version() {
|
||||
local command="$1"
|
||||
shift
|
||||
command -v "$command" >/dev/null 2>&1 || {
|
||||
printf 'not found'
|
||||
return 0
|
||||
}
|
||||
"$command" "$@" 2>/dev/null | head -n1 | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -n1
|
||||
}
|
||||
|
||||
check() {
|
||||
local label="$1" required="$2" found="$3" hint="$4"
|
||||
|
||||
if [ -z "$found" ]; then
|
||||
found="unknown"
|
||||
fi
|
||||
|
||||
say ""
|
||||
say "$label:"
|
||||
say " required: $required"
|
||||
say " found: $found"
|
||||
|
||||
if [ "$found" = "$required" ]; then
|
||||
say " OK"
|
||||
return 0
|
||||
fi
|
||||
|
||||
say " FAIL — $hint"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
say "HY2XS development environment"
|
||||
say "contract: versions.env (HY2XS $HY2XS_VERSION, release line $HY2XS_RELEASE_LINE)"
|
||||
|
||||
# Go попадает в production-артефакт целиком: его stdlib компилируется в
|
||||
# hy2xs-admin. Несовпадение здесь — не удобство, а разная поверхность атаки у
|
||||
# разработчика и у релиза.
|
||||
check "Go" "$GO_VERSION" "$(tool_version go version)" \
|
||||
"локальный Go собирает не ту stdlib, что уедет в релиз; поставьте $GO_VERSION"
|
||||
|
||||
# Node живёт только на build-хосте (сборка frontend-ассетов), но собирает код,
|
||||
# который уезжает в production.
|
||||
check "Node" "$NODE_VERSION" "$(tool_version node --version)" \
|
||||
"frontend-ассеты собираются другой версией Node; поставьте $NODE_VERSION"
|
||||
|
||||
check "pnpm" "$PNPM_VERSION" "$(tool_version pnpm --version)" \
|
||||
"разрешение зависимостей frontend может разойтись с lock-файлом; поставьте $PNPM_VERSION"
|
||||
|
||||
# Bun физически входит в скомпилированный оркестратор (`bun build --compile`),
|
||||
# поэтому его версия — часть артефакта, а не инструмента.
|
||||
check "Bun" "$BUN_VERSION" "$(tool_version bun --version)" \
|
||||
"runtime Bun входит в бинарь оркестратора; поставьте $BUN_VERSION"
|
||||
|
||||
# go.mod обязан называть компилятор явно: директива `go` этого не делает.
|
||||
DECLARED_TOOLCHAIN="$(grep -E '^toolchain[[:space:]]+go[0-9]' apps/go.mod | head -n1 | awk '{print $2}' || true)"
|
||||
check "apps/go.mod toolchain" "go$GO_VERSION" "${DECLARED_TOOLCHAIN:-отсутствует}" \
|
||||
"директива toolchain разошлась с versions.env; сборка отвергнет это в verify_go_toolchain_contract"
|
||||
|
||||
say ""
|
||||
if [ "$FAILURES" -eq 0 ]; then
|
||||
say "Итог: среда разработки совпадает с контрактом сборки."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
say "Итог: расхождений — $FAILURES. Локальные проверки идут не по тому toolchain,"
|
||||
say "которым собирается релиз."
|
||||
exit 1
|
||||
Reference in New Issue
Block a user