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:
@@ -13,7 +13,8 @@ function usage(): never {
|
||||
console.error(" hy2xs-orchestrator install --package-dir <path> [--config <path>] [--skip-firewall] [--skip-service-start] [--skip-smoke] [--non-interactive]");
|
||||
console.error(" hy2xs-orchestrator reconfigure --package-dir <path> [--config <path>] [--dry-run|--apply] [--skip-firewall] [--skip-service-start] [--skip-smoke]");
|
||||
console.error(" hy2xs-orchestrator repair --package-dir <path> [--config <path>] [--allow-partial-state] [--skip-firewall] [--skip-service-start] [--skip-smoke]");
|
||||
console.error(" hy2xs-orchestrator doctor --package-dir <path> [--config <path>] [--skip-firewall] [--skip-service-start] [--skip-smoke]");
|
||||
console.error(" hy2xs-orchestrator doctor --package-dir <path> [--config <path>] [--skip-firewall] [--skip-smoke]");
|
||||
console.error(" note: doctor never restarts services — it diagnoses the running installation");
|
||||
console.error(" hy2xs-orchestrator status --package-dir <path> [--config <path>] [--skip-firewall] [--skip-service-start] [--skip-smoke]");
|
||||
console.error(" hy2xs-orchestrator diagnostics collect --package-dir <path> [--config <path>] [--skip-firewall] [--skip-service-start] [--skip-smoke]");
|
||||
console.error(" hy2xs-orchestrator redact-config --config <path> [--in-place | --out <path>] [--format auto|env|yaml]");
|
||||
|
||||
@@ -57,6 +57,29 @@ async function checkSshForwardingForLocalUi(uiBindHost: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* doctor — диагностика работающей установки, а не её починка.
|
||||
*
|
||||
* skipServiceStart здесь выставляется ПРИНУДИТЕЛЬНО, независимо от того, что
|
||||
* пришло из командной строки.
|
||||
*
|
||||
* Что было. doctor собирал контекст с параметрами по умолчанию
|
||||
* (`skipServiceStart: false`) и звал общий smoke, а тот первым же действием
|
||||
* выполняет `systemctl restart hysteria-server hy2xs-admin`. То есть команда,
|
||||
* которую docs/12 и docs/13 предлагают запускать ПРИ ПОДОЗРЕНИИ на проблему,
|
||||
* гарантированно обрывала все живые VPN-соединения на сервере — включая
|
||||
* ситуацию, когда с сервисом всё в порядке и оператор просто проверял состояние.
|
||||
*
|
||||
* Диагностика, меняющая то, что диагностирует, отвечает не на заданный вопрос:
|
||||
* после рестарта проверяется уже другое состояние системы. Флаги
|
||||
* --skip-service-start/--skip-smoke оставлены разбираемыми, чтобы вызов
|
||||
* `doctor --skip-service-start` не падал на неизвестном аргументе, но повлиять
|
||||
* они могут только в сторону сужения проверок.
|
||||
*
|
||||
* Остальная часть smoke — слушатели, права на файлы, machine auth, семантика
|
||||
* /etc/hysteria/config.yaml, версия бинаря — состояние сервера не меняет и
|
||||
* выполняется полностью.
|
||||
*/
|
||||
export async function doctor(options: ReconfigureOptions): Promise<void> {
|
||||
setOperationContext(`doctor-${Date.now().toString(36)}`);
|
||||
const configRaw = await readText(options.sourceConfigPath);
|
||||
@@ -64,7 +87,7 @@ export async function doctor(options: ReconfigureOptions): Promise<void> {
|
||||
|
||||
const context: ReconfigureContext = {
|
||||
mode: "reconfigure",
|
||||
options: { ...options, dryRun: true, apply: false },
|
||||
options: { ...options, dryRun: true, apply: false, skipServiceStart: true },
|
||||
config,
|
||||
packageVersion: await readPackageValue(options.packageDir, "package.version", "unknown"),
|
||||
packageBuildId: await readPackageValue(options.packageDir, "package.build_id", "unknown"),
|
||||
|
||||
Reference in New Issue
Block a user