Files
HY2XS_flamy/tools/dev/doctor.ps1
T
founder cf094f6e6f 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.
2026-08-30 06:48:50 +05:00

131 lines
5.8 KiB
PowerShell
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.
#Requires -Version 7.0
<#
.SYNOPSIS
Проверка среды разработки против versions.env (Windows).
.DESCRIPTION
Точный аналог tools/dev/doctor.sh: сборка релиза скачивает Go, Node и Bun
ровно тех версий, что записаны в versions.env, а машина разработчика не
проверялась никак — расхождение обнаруживалось только на Debian, внутри
release-сборки.
Расхождение не гипотетическое: локальный Go 1.25 собирал проект успешно
(директива `go` в go.mod — языковой baseline, а не выбор компилятора), а
релизный бинарь компилировался на 1.26.7 и наследовал ЕЁ stdlib. То есть
проверялся не тот код, который уезжает в production.
Скрипт ничего не устанавливает и не меняет.
.PARAMETER Quiet
Печатать только итог.
#>
[CmdletBinding()]
param(
[switch]$Quiet
)
$ErrorActionPreference = 'Stop'
$rootDir = Resolve-Path (Join-Path $PSScriptRoot '..' '..')
Set-Location $rootDir
$versionsPath = Join-Path $rootDir 'versions.env'
if (-not (Test-Path $versionsPath)) {
Write-Error 'versions.env не найден: doctor запускается из репозитория HY2XS'
}
# versions.env — POSIX shell, но по построению содержит только присваивания
# KEY=value и комментарии: это его объявленный контракт (см. шапку файла).
$contract = @{}
foreach ($line in Get-Content $versionsPath) {
$trimmed = $line.Trim()
if (-not $trimmed -or $trimmed.StartsWith('#')) { continue }
$separator = $trimmed.IndexOf('=')
if ($separator -lt 1) { continue }
$contract[$trimmed.Substring(0, $separator).Trim()] = $trimmed.Substring($separator + 1).Trim()
}
$failures = 0
function Say([string]$Text) {
if (-not $Quiet) { Write-Host $Text }
}
# Версия спрашивается у самого бинаря: что записано в PATH — не тот вопрос,
# отвечать должен тот инструмент, который реально запустится.
function Get-ToolVersion {
param([string]$Command, [string[]]$Arguments)
$resolved = Get-Command $Command -ErrorAction SilentlyContinue
if (-not $resolved) { return 'not found' }
try {
$output = & $resolved.Source @Arguments 2>$null | Select-Object -First 1
} catch {
return 'unknown'
}
$match = [regex]::Match([string]$output, '\d+\.\d+\.\d+')
if (-not $match.Success) { return 'unknown' }
return $match.Value
}
function Test-Contract {
param([string]$Label, [string]$Required, [string]$Found, [string]$Hint)
if (-not $Found) { $Found = 'unknown' }
Say ''
Say "${Label}:"
Say " required: $Required"
Say " found: $Found"
if ($Found -eq $Required) {
Say ' OK'
return
}
Say " FAIL — $Hint"
$script:failures++
}
Say 'HY2XS development environment'
Say "contract: versions.env (HY2XS $($contract.HY2XS_VERSION), release line $($contract.HY2XS_RELEASE_LINE))"
# Go попадает в production-артефакт целиком: его stdlib компилируется в
# hy2xs-admin. Несовпадение здесь — не удобство, а разная поверхность атаки у
# разработчика и у релиза.
Test-Contract 'Go' $contract.GO_VERSION (Get-ToolVersion 'go' @('version')) `
"локальный Go собирает не ту stdlib, что уедет в релиз; поставьте $($contract.GO_VERSION)"
# Node живёт только на build-хосте, но собирает код, который уезжает в production.
Test-Contract 'Node' $contract.NODE_VERSION (Get-ToolVersion 'node' @('--version')) `
"frontend-ассеты собираются другой версией Node; поставьте $($contract.NODE_VERSION)"
Test-Contract 'pnpm' $contract.PNPM_VERSION (Get-ToolVersion 'pnpm' @('--version')) `
"разрешение зависимостей frontend может разойтись с lock-файлом; поставьте $($contract.PNPM_VERSION)"
# Bun физически входит в скомпилированный оркестратор (`bun build --compile`),
# поэтому его версия — часть артефакта, а не инструмента.
Test-Contract 'Bun' $contract.BUN_VERSION (Get-ToolVersion 'bun' @('--version')) `
"runtime Bun входит в бинарь оркестратора; поставьте $($contract.BUN_VERSION)"
# go.mod обязан называть компилятор явно: директива `go` этого не делает.
$declaredToolchain = (Select-String -Path (Join-Path $rootDir 'apps' 'go.mod') -Pattern '^toolchain\s+(go[0-9][^\s]*)' |
Select-Object -First 1).Matches.Groups[1].Value
if (-not $declaredToolchain) { $declaredToolchain = 'отсутствует' }
Test-Contract 'apps/go.mod toolchain' "go$($contract.GO_VERSION)" $declaredToolchain `
'директива toolchain разошлась с versions.env; сборка отвергнет это в verify_go_toolchain_contract'
Say ''
if ($failures -eq 0) {
Say 'Итог: среда разработки совпадает с контрактом сборки.'
exit 0
}
Say "Итог: расхождений — $failures. Локальные проверки идут не по тому toolchain,"
Say 'которым собирается релиз.'
exit 1