Files
HY2XS_flamy/apps/model/vo/hysteria2_profile.go
founder cb20d8d28f 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
2026-09-02 23:24:01 +05:00

133 lines
7.3 KiB
Go
Raw Permalink 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.
package vo
// Страница конфигурации показывает ТО, ЧТО НАПИСАНО В ФАЙЛЕ.
//
// Что было. Панель отдавала браузеру внутреннюю модель серверного конфига
// целиком, а frontend накладывал ответ на полный объект значений по умолчанию
// (`DeepRequired` + merge). В результате экран отвечал не на вопрос
//
// что реально написано в /etc/hysteria/config.yaml?
//
// а на вопрос
//
// как выглядел бы конфиг, если недостающие куски заполнить дефолтами UI?
//
// Разница не косметическая. Отсутствующая секция `trafficStats` показывалась
// как `:9999`; `speedTest: false` и `disableUDP: false` — валидные явные
// значения — считались отсутствующими и прятали свои вкладки;
// `ignoreClientBandwidth` (самостоятельная опция верхнего уровня) показывался
// только внутри секции bandwidth и при её отсутствии исчезал вместе с ней;
// `masquerade.string.statusCode` (число 200..599 у upstream) рисовался
// переключателем. То есть экран, существующий ради диагностики расхождений,
// эти расхождения скрывал.
//
// Второе свойство прежнего ответа: он вёз в браузер секреты. `auth` и
// `trafficStats.secret` были закрыты `json:"-"`, но пароль обфускации, токены
// ACME DNS, учётные данные outbound-прокси и masquerade — нет. Скачиваемый
// экспорт того же конфига при этом их вырезает. Читающий экран не имеет права
// быть щедрее выгрузки.
//
// Поэтому ответ описан отдельным типом. Он показывает production-профиль HY2XS
// — то, чем реально управляет оркестратор, — и отдельно перечисляет секции,
// которых в профиле нет. Полный документ по-прежнему доступен санитизированной
// выгрузкой.
//
// Указатель означает «в файле этого нет». Это единственный способ отличить
// отсутствие от значения: `false`, `0` и пустая строка — законные значения.
// Hysteria2ProfileVo — конфигурация Hysteria в терминах production-профиля.
type Hysteria2ProfileVo struct {
Listen *string `json:"listen"`
Auth *Hysteria2ProfileAuthVo `json:"auth"`
Tls *Hysteria2ProfileTlsVo `json:"tls"`
Acme *Hysteria2ProfileAcmeVo `json:"acme"`
Obfs *Hysteria2ProfileObfsVo `json:"obfs"`
Bandwidth *Hysteria2ProfileBandwidthVo `json:"bandwidth"`
IgnoreClientBandwidth *bool `json:"ignoreClientBandwidth"`
Congestion *Hysteria2ProfileCongestionVo `json:"congestion"`
Quic *Hysteria2ProfileQuicVo `json:"quic"`
TrafficStats *Hysteria2ProfileTrafficStatsVo `json:"trafficStats"`
// Drift — секции верхнего уровня, которых production-профиль не описывает.
//
// Считается по СЫРОМУ YAML, а не по типизированной модели: секция, о
// которой HY2XS не знает вовсе, обязана быть замечена именно как
// расхождение, а не потеряна при разборе.
Drift []string `json:"drift"`
}
// Hysteria2ProfileAuthVo — способ допуска пиров.
//
// URL показывается санитизированным: это единственный канал допуска, и знать
// его порт и путь оператору нужно, а machine token — нет.
type Hysteria2ProfileAuthVo struct {
Type *string `json:"type"`
Url *string `json:"url"`
Insecure *bool `json:"insecure"`
}
type Hysteria2ProfileTlsVo struct {
Cert *string `json:"cert"`
Key *string `json:"key"`
SniGuard *string `json:"sniGuard"`
ClientCA *string `json:"clientCA"`
}
// Hysteria2ProfileAcmeVo — выпуск сертификата.
//
// DnsConfigKeys перечисляет ИМЕНА параметров DNS-провайдера без значений: сам
// факт «токен задан» диагностичен, а значение — это ключ от DNS-зоны.
type Hysteria2ProfileAcmeVo struct {
Domains []string `json:"domains"`
Email *string `json:"email"`
Ca *string `json:"ca"`
Dir *string `json:"dir"`
ListenHost *string `json:"listenHost"`
Type *string `json:"type"`
DnsProvider *string `json:"dnsProvider"`
DnsConfigKeys []string `json:"dnsConfigKeys"`
}
// Hysteria2ProfileObfsVo — обфускация.
//
// Пароль не возвращается: он входит в клиентскую ссылку, и оператор получает
// его там, где он нужен. Здесь диагностичен только факт, что пароль задан.
type Hysteria2ProfileObfsVo struct {
Type *string `json:"type"`
PasswordSet bool `json:"passwordSet"`
MinPacketSize *int `json:"minPacketSize"`
MaxPacketSize *int `json:"maxPacketSize"`
}
type Hysteria2ProfileBandwidthVo struct {
Up *string `json:"up"`
Down *string `json:"down"`
DisableLossCompensation *bool `json:"disableLossCompensation"`
}
type Hysteria2ProfileCongestionVo struct {
Type *string `json:"type"`
BbrProfile *string `json:"bbrProfile"`
}
type Hysteria2ProfileQuicVo struct {
InitStreamReceiveWindow *uint64 `json:"initStreamReceiveWindow"`
MaxStreamReceiveWindow *uint64 `json:"maxStreamReceiveWindow"`
InitConnReceiveWindow *uint64 `json:"initConnReceiveWindow"`
MaxConnReceiveWindow *uint64 `json:"maxConnReceiveWindow"`
MaxIdleTimeout *string `json:"maxIdleTimeout"`
MaxIncomingStreams *int64 `json:"maxIncomingStreams"`
DisablePathMTUDiscovery *bool `json:"disablePathMTUDiscovery"`
DisableStatelessReset *bool `json:"disableStatelessReset"`
}
// Hysteria2ProfileTrafficStatsVo — внутренний control plane.
//
// `listen` показывается ровно так, как записан в файле: именно расхождение
// этого адреса с loopback выключает лимит устройств, учёт трафика и
// принудительное отключение разом, и увидеть его оператор должен здесь.
type Hysteria2ProfileTrafficStatsVo struct {
Listen *string `json:"listen"`
SecretSet bool `json:"secretSet"`
}