fix(admin): связать отзыв учётных данных с идентичностью сессий и свести адрес control plane к одному
Отзыв секрета не сходился: `auth_id` при смене секрета оставался прежним, поэтому сессия, установленная по отозванным учётным данным, была неотличима от законной, и цикл учёта не имел признака, по которому её следовало завершить. У состояния есть путь без единой неудачи — Hysteria регистрирует соединение в Traffic Stats API только после возврата backend-auth, поэтому успешный /kick может пройти мимо. Новое поколение credentials получает новый auth_id, kick идёт по старому, пережившая сессия становится orphan. Адрес Traffic Stats API имел два контракта: оркестратор принимал любой IPv4, админка всегда шла на loopback. Валидная по всем гейтам конфигурация выключала лимит устройств, учёт трафика и принудительное отключение разом. Адрес зафиксирован, а расхождение файла с ним админка называет. Состояние службы стало трёхзначным: util.Exec выбрасывал вывод systemctl при ненулевом коде, поэтому «остановлена» и «спросить не удалось» приходили одним значением, а доступность Traffic Stats API выводилась из него же. Журнал Hysteria разбирается в фактическом формате upstream (time — дробное число), страница конфигурации показывает файл вместо дефолтов UI и не возит секреты в браузер, санитайзер выгрузки следует по YAML-якорям. Разбор: docs/acceptance/2026-09-02-v1.0.0-rc4-preflight-findings.md
This commit is contained in:
+86
-24
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"hy2xs-admin/util"
|
||||
)
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
|
||||
// InitHysteria2 проверяет единственное, что панели действительно нужно от
|
||||
// Hysteria на старте: наличие бинарника, версию которого показывает дашборд и
|
||||
// у которого спрашивает состояние Hysteria2IsRunning.
|
||||
// у которого спрашивает состояние HysteriaServiceState.
|
||||
//
|
||||
// Чтения HYSTERIA2_ENABLE здесь больше нет. Ключ был последним следом модели,
|
||||
// в которой панель сама запускала и останавливала Hysteria: clean install
|
||||
@@ -26,38 +27,99 @@ func InitHysteria2() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hysteria2IsRunning — ответ systemd о состоянии службы, и НЕ БОЛЬШЕ ТОГО.
|
||||
// Состояние службы имеет ТРИ значения, а не два.
|
||||
//
|
||||
// Точность этого ответа ограничена сверху устройством util.Exec: он схлопывает
|
||||
// «systemctl вернул 3, служба неактивна» и «запустить systemctl не удалось» в
|
||||
// одну ошибку. Различить их здесь нельзя, поэтому false означает «служба
|
||||
// неактивна ИЛИ спросить не получилось».
|
||||
// Что было. Ответ сводился к `bool`:
|
||||
//
|
||||
// Отсюда правило, которое стоило продукту трёх дыр: на этом значении нельзя
|
||||
// строить решения о доступе и о применении операции. Ему место в отображении
|
||||
// (дашборд, признак online в списке), где ошибочное «выключено» стоит одной
|
||||
// неверной плашки. Решения о доступе принимаются по фактическому ответу
|
||||
// Traffic Stats API — см. hysteria2Online и disconnectAuthIDs.
|
||||
// func Hysteria2IsRunning() bool {
|
||||
// _, err := util.Exec("systemctl is-active --quiet hysteria-server")
|
||||
// return err == nil
|
||||
// }
|
||||
//
|
||||
// Третья дыра была самой дорогой и жила в cron: гейт `if !Hysteria2IsRunning()
|
||||
// { return }` стоял перед всем циклом учёта, поэтому сломанный systemctl при
|
||||
// живой Hysteria молча отключал и сбор трафика, и принудительное отключение —
|
||||
// без единой строки в журнале.
|
||||
func Hysteria2IsRunning() bool {
|
||||
return hysteria2IsRunning()
|
||||
// `false` при этом означал сразу две несовместимые вещи — «служба неактивна» и
|
||||
// «спросить не получилось», — потому что util.Exec выбрасывает вывод команды,
|
||||
// как только код возврата не нулевой, а `is-active` отвечает именно кодом 3
|
||||
// вместе со словом состояния в stdout. Прочитать это слово было нечем.
|
||||
//
|
||||
// Цена такой неопределённости оказалась не в одной неверной плашке. Пока
|
||||
// «неизвестно» выглядело как «остановлена», на этом значении стояли решения:
|
||||
// цикл учёта и проверка лимита устройств пропускались при сломанном systemctl
|
||||
// и живой Hysteria. Оба гейта убраны (см. cron.go и Hysteria2Auth), и это
|
||||
// правильно, но сама неопределённость никуда не делась — она осталась в
|
||||
// диагностике, где как раз и вредна:
|
||||
//
|
||||
// Hysteria stopped
|
||||
// Traffic Stats API reachable
|
||||
// 0 online users
|
||||
//
|
||||
// Три утверждения об одной системе, из которых первое и второе не могут быть
|
||||
// верны одновременно. Оператор, увидев такое в аварии, ищет не там.
|
||||
//
|
||||
// Теперь состояние читается словом, а не кодом возврата, и неизвестность
|
||||
// называется неизвестностью.
|
||||
type HysteriaServiceState string
|
||||
|
||||
const (
|
||||
// HysteriaServiceActive — systemd сообщил "active".
|
||||
HysteriaServiceActive HysteriaServiceState = "active"
|
||||
// HysteriaServiceInactive — systemd назвал любое другое известное
|
||||
// состояние: inactive, failed, activating, deactivating.
|
||||
//
|
||||
// Различать их дальше продукту незачем: панель отвечает на вопрос «идёт ли
|
||||
// сейчас обслуживание», а не пересказывает конечный автомат systemd.
|
||||
HysteriaServiceInactive HysteriaServiceState = "inactive"
|
||||
// HysteriaServiceUnknown — спросить не удалось либо ответ не разобран.
|
||||
//
|
||||
// Это НЕ «служба остановлена». Единственное честное следствие — что о
|
||||
// состоянии службы сейчас ничего не известно.
|
||||
HysteriaServiceUnknown HysteriaServiceState = "unknown"
|
||||
)
|
||||
|
||||
// Hysteria2ServiceState — ответ systemd о состоянии службы, и НЕ БОЛЬШЕ ТОГО.
|
||||
//
|
||||
// Значение годится для отображения и ни для чего больше. Решения о доступе
|
||||
// принимаются по фактическому ответу Traffic Stats API — см. hysteria2Online и
|
||||
// disconnectAuthIDs, — и это правило стоило продукту трёх дыр: гейт
|
||||
// `if !Hysteria2IsRunning() { return }` в cron молча отключал и сбор трафика, и
|
||||
// принудительное отключение при сломанном systemctl.
|
||||
func Hysteria2ServiceState() HysteriaServiceState {
|
||||
return hysteriaServiceState()
|
||||
}
|
||||
|
||||
// hysteria2IsRunning — та же проверка в виде значения.
|
||||
// hysteriaServiceState — та же проверка в виде значения.
|
||||
//
|
||||
// Переменная нужна тестам: без неё каждая проверка, зависящая от состояния
|
||||
// службы, доказуема только на живом systemd, то есть не доказуема в сборке
|
||||
// вовсе. Продуктовый путь от этого не меняется — значение по умолчанию и есть
|
||||
// прежняя реализация.
|
||||
var hysteria2IsRunning = func() bool {
|
||||
_, err := util.Exec("systemctl is-active --quiet hysteria-server")
|
||||
return err == nil
|
||||
// реализация.
|
||||
var hysteriaServiceState = func() HysteriaServiceState {
|
||||
// `--quiet` здесь недопустим: он подавляет ровно тот вывод, ради которого
|
||||
// команда вызывается.
|
||||
output, _, err := util.ExecProbe("systemctl is-active hysteria-server")
|
||||
if err != nil {
|
||||
return HysteriaServiceUnknown
|
||||
}
|
||||
return parseSystemdActiveState(output)
|
||||
}
|
||||
|
||||
func ReleaseHysteria2() error {
|
||||
return nil
|
||||
// parseSystemdActiveState разбирает ответ `systemctl is-active`.
|
||||
//
|
||||
// Слово состояния приезжает первой строкой. Неизвестное значение считается
|
||||
// неизвестным состоянием, а не «неактивна»: список состояний systemd
|
||||
// пополнялся и раньше, и молчаливое приведение нового слова к «остановлена»
|
||||
// вернуло бы ровно ту ложь, от которой избавляет этот тип.
|
||||
func parseSystemdActiveState(output string) HysteriaServiceState {
|
||||
state := strings.TrimSpace(output)
|
||||
if idx := strings.IndexAny(state, "\r\n"); idx >= 0 {
|
||||
state = strings.TrimSpace(state[:idx])
|
||||
}
|
||||
|
||||
switch state {
|
||||
case "active":
|
||||
return HysteriaServiceActive
|
||||
case "inactive", "failed", "activating", "deactivating", "reloading", "maintenance":
|
||||
return HysteriaServiceInactive
|
||||
default:
|
||||
return HysteriaServiceUnknown
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user