feat(v1): Gecko-обфускация, latest-stable Hysteria на сборке и forward-compatible admin

Сквозная миграция HY2XS на современную Hysteria (2.12.2) и переход на v1.

Build:
- версия Hysteria резолвится на этапе сборки из HyNetworks/hysteria и
  замораживается в metadata пакета (version + immutable url + sha256);
- compatibility gate: реальный бинарник должен принять канонический конфиг
  HY2XS для gecko и salamander до создания пакета;
- сборка прогоняет тесты оркестратора и админки.

Конфигурационный контракт:
- HY2XS_CONFIG_SCHEMA_VERSION=2, чужая схема отклоняется fail-fast;
- obfs стал настоящим union gecko|salamander, gecko — default;
- obfs-блок рендерится оркестратором целиком, два подтипа одновременно
  структурно невозможны;
- современный baseline: congestion bbr/standard, disableLossCompensation=false,
  disableStatelessReset=false, полный quic-блок.

Исправления:
- share URI для gecko: генератор был завязан на Obfs.Salamander.Password и
  выдавал нерабочую ссылку при любой другой обфускации;
- SNI брался только из ACME-блока и уходил пустым при HY2XS_TLS_MODE=file;
- экспорт конфига выносил trafficStats.secret, access_token и obfs-пароль;
- экспорт терял неизвестные upstream-поля при round-trip через типизированную
  модель;
- renderRuntimeEnv печатал тип обфускации литералом, расходясь с конфигом;
- namedotcom удалён из ACME-реестра (нет в Hysteria с 2.11.0).

Тесты:
- 95 тестов оркестратора: env, рендер, семантика профиля, резолвер, rollover;
- тесты URI и экспорта в Go;
- tools/test/e2e-hysteria.sh с реальным клиентом Hysteria.

UX:
- подсказки и примеры в форме создания пира.

Прочее: CHANGELOG.md, .gitattributes (LF для target-side файлов),
документация на русском.
This commit is contained in:
2026-08-27 08:15:02 +05:00
parent 0205334cd8
commit ddf0ddf71e
53 changed files with 4827 additions and 291 deletions
+114 -1
View File
@@ -1,12 +1,55 @@
package bo
// ObfsShareConfig — та часть обфускации, которую способна описать официальная
// схема hysteria2:// URI: только type и password.
//
// Gecko minPacketSize/maxPacketSize в ссылку не помещаются. Именно поэтому
// HY2XS фиксирует их на upstream defaults 512/1200: иначе сгенерированная
// ссылка не описывала бы подключение полностью.
type ObfsShareConfig struct {
Type string
Password string
}
// ObfsShare приводит любой поддерживаемый тип обфускации к единому виду для
// генерации клиентской ссылки. Знание о подтипах живёт рядом с моделью, чтобы
// добавление нового типа не требовало правок в слое share URI.
func (c *Hysteria2ServerConfig) ObfsShare() ObfsShareConfig {
if c == nil || c.Obfs == nil || c.Obfs.Type == nil {
return ObfsShareConfig{}
}
switch *c.Obfs.Type {
case "salamander":
if c.Obfs.Salamander != nil && c.Obfs.Salamander.Password != nil {
return ObfsShareConfig{Type: "salamander", Password: *c.Obfs.Salamander.Password}
}
case "gecko":
if c.Obfs.Gecko != nil && c.Obfs.Gecko.Password != nil {
return ObfsShareConfig{Type: "gecko", Password: *c.Obfs.Gecko.Password}
}
}
return ObfsShareConfig{}
}
// AcmeDomain возвращает первый ACME-домен, если он есть.
func (c *Hysteria2ServerConfig) AcmeDomain() string {
if c == nil || c.ACME == nil || len(c.ACME.Domains) == 0 {
return ""
}
return c.ACME.Domains[0]
}
type Hysteria2ServerConfig struct {
Listen *string `yaml:"listen,omitempty" json:"listen" validate:"required"`
Obfs *serverConfigObfs `yaml:"obfs,omitempty" json:"obfs" validate:"omitempty"`
TLS *serverConfigTLS `yaml:"tls,omitempty" json:"tls" validate:"omitempty"`
ACME *serverConfigACME `yaml:"acme,omitempty" json:"acme" validate:"omitempty"`
ECH *serverConfigECH `yaml:"ech,omitempty" json:"ech" validate:"omitempty"`
QUIC *serverConfigQUIC `yaml:"quic,omitempty" json:"quic" validate:"omitempty"`
Bandwidth *serverConfigBandwidth `yaml:"bandwidth,omitempty" json:"bandwidth" validate:"omitempty"`
Congestion *ServerConfigCongestion `yaml:"congestion,omitempty" json:"congestion" validate:"omitempty"`
IgnoreClientBandwidth *bool `yaml:"ignoreClientBandwidth,omitempty" json:"ignoreClientBandwidth" validate:"omitempty"`
SpeedTest *bool `yaml:"speedTest,omitempty" json:"speedTest" validate:"omitempty"`
DisableUDP *bool `yaml:"disableUDP,omitempty" json:"disableUDP" validate:"omitempty"`
@@ -18,21 +61,43 @@ type Hysteria2ServerConfig struct {
Outbounds []serverConfigOutboundEntry `yaml:"outbounds,omitempty" json:"outbounds" validate:"omitempty"`
TrafficStats *ServerConfigTrafficStats `yaml:"trafficStats,omitempty" json:"trafficStats" validate:"required"`
Masquerade *serverConfigMasquerade `yaml:"masquerade,omitempty" json:"masquerade" validate:"omitempty"`
Mimic *serverConfigMimic `yaml:"mimic,omitempty" json:"mimic" validate:"omitempty"`
Realm *serverConfigRealm `yaml:"realm,omitempty" json:"realm" validate:"omitempty"`
}
type serverConfigObfsSalamander struct {
Password *string `yaml:"password,omitempty" json:"password" validate:"required"`
}
// serverConfigObfsGecko описывает Gecko-обфускацию (Hysteria 2.9.2+).
// Gecko достраивается поверх Salamander и дополнительно фрагментирует
// QUIC handshake на пакеты случайного размера в диапазоне min..max.
type serverConfigObfsGecko struct {
Password *string `yaml:"password,omitempty" json:"password" validate:"required"`
MinPacketSize *int `yaml:"minPacketSize,omitempty" json:"minPacketSize" validate:"omitempty"`
MaxPacketSize *int `yaml:"maxPacketSize,omitempty" json:"maxPacketSize" validate:"omitempty"`
}
// Обе ветки опциональны: в конфиге присутствует ровно одна из них,
// соответствующая Type.
type serverConfigObfs struct {
Type *string `yaml:"type,omitempty" json:"type" validate:"required"`
Salamander *serverConfigObfsSalamander `yaml:"salamander,omitempty" json:"salamander" validate:"required"`
Salamander *serverConfigObfsSalamander `yaml:"salamander,omitempty" json:"salamander" validate:"omitempty"`
Gecko *serverConfigObfsGecko `yaml:"gecko,omitempty" json:"gecko" validate:"omitempty"`
}
type serverConfigTLS struct {
Cert *string `yaml:"cert,omitempty" json:"cert" validate:"required"`
Key *string `yaml:"key,omitempty" json:"key" validate:"required"`
SNIGuard *string `yaml:"sniGuard,omitempty" json:"sniGuard" validate:"omitempty"`
ClientCA *string `yaml:"clientCA,omitempty" json:"clientCA" validate:"omitempty"`
}
// serverConfigECH — Encrypted Client Hello (Hysteria 2.10.0+).
// HY2XS не генерирует ECH keypair и не включает ECH в default-профиле,
// но обязан корректно читать и сохранять этот блок.
type serverConfigECH struct {
KeyPath *string `yaml:"keyPath,omitempty" json:"keyPath" validate:"omitempty"`
}
type serverConfigACME struct {
@@ -78,11 +143,25 @@ type serverConfigQUIC struct {
MaxIdleTimeout *string `yaml:"maxIdleTimeout,omitempty" json:"maxIdleTimeout" validate:"omitempty"`
MaxIncomingStreams *int64 `yaml:"maxIncomingStreams,omitempty" json:"maxIncomingStreams" validate:"omitempty"`
DisablePathMTUDiscovery *bool `yaml:"disablePathMTUDiscovery,omitempty" json:"disablePathMTUDiscovery" validate:"omitempty"`
// DisableStatelessReset появился в Hysteria 2.12.2. HY2XS оставляет
// stateless reset включённым: клиент после перезапуска сервера или сна
// устройства переподключается сразу, а не по таймауту.
DisableStatelessReset *bool `yaml:"disableStatelessReset,omitempty" json:"disableStatelessReset" validate:"omitempty"`
}
type serverConfigBandwidth struct {
Up *string `yaml:"up,omitempty" json:"up" validate:"required"`
Down *string `yaml:"down,omitempty" json:"down" validate:"required"`
// DisableLossCompensation появился в Hysteria 2.10.0.
DisableLossCompensation *bool `yaml:"disableLossCompensation,omitempty" json:"disableLossCompensation" validate:"omitempty"`
}
// ServerConfigCongestion — fallback congestion controller. Используется, когда
// Brutal bandwidth не согласован сторонами. Это не то же самое, что host-level
// BBR ядра Linux: у Hysteria собственный congestion-control контур.
type ServerConfigCongestion struct {
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
BBRProfile *string `yaml:"bbrProfile,omitempty" json:"bbrProfile" validate:"omitempty"`
}
type ServerConfigAuthHTTP struct {
@@ -183,9 +262,11 @@ type serverConfigMasqueradeFile struct {
}
type serverConfigMasqueradeProxy struct {
// URL с Hysteria 2.12.2 может указывать и на unix socket.
URL *string `yaml:"url,omitempty" json:"url" validate:"required"`
RewriteHost *bool `yaml:"rewriteHost,omitempty" json:"rewriteHost" validate:"required"`
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
XForwarded *bool `yaml:"xForwarded,omitempty" json:"xForwarded" validate:"omitempty"`
}
type serverConfigMasqueradeString struct {
@@ -203,3 +284,35 @@ type serverConfigMasquerade struct {
ListenHTTPS *string `yaml:"listenHTTPS,omitempty" json:"listenHTTPS" validate:"omitempty"`
ForceHTTPS *bool `yaml:"forceHTTPS,omitempty" json:"forceHTTPS" validate:"omitempty"`
}
// serverConfigMimic — Mimic / fake TCP (Hysteria 2.12.0+).
// Требует отдельный сторонний бинарник, root и eBPF/XDP, поэтому в
// production-профиле HY2XS выключен: текущий systemd-контракт запускает
// Hysteria под непривилегированным пользователем.
type serverConfigMimic struct {
Enabled *bool `yaml:"enabled,omitempty" json:"enabled" validate:"omitempty"`
Interface *string `yaml:"interface,omitempty" json:"interface" validate:"omitempty"`
XDPMode *string `yaml:"xdpMode,omitempty" json:"xdpMode" validate:"omitempty"`
Path *string `yaml:"path,omitempty" json:"path" validate:"omitempty"`
ExtraArgs []string `yaml:"extraArgs,omitempty" json:"extraArgs" validate:"omitempty"`
}
type serverConfigRealmPortMapping struct {
Enabled *bool `yaml:"enabled,omitempty" json:"enabled" validate:"omitempty"`
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"omitempty"`
Lifetime *string `yaml:"lifetime,omitempty" json:"lifetime" validate:"omitempty"`
}
// serverConfigRealm — Hysteria Realms (2.9.0+), запуск сервера за NAT через
// STUN/hole punching. Меняет фундаментальный сетевой контракт HY2XS
// (выделенный сервер, публичный IPv4, UDP/443, own nftables), поэтому в
// default-профиле не используется, но должен корректно читаться.
type serverConfigRealm struct {
StunServers []string `yaml:"stunServers,omitempty" json:"stunServers" validate:"omitempty"`
StunTimeout *string `yaml:"stunTimeout,omitempty" json:"stunTimeout" validate:"omitempty"`
PunchTimeout *string `yaml:"punchTimeout,omitempty" json:"punchTimeout" validate:"omitempty"`
HeartbeatInterval *string `yaml:"heartbeatInterval,omitempty" json:"heartbeatInterval" validate:"omitempty"`
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"omitempty"`
IPMode *string `yaml:"ipMode,omitempty" json:"ipMode" validate:"omitempty"`
PortMapping *serverConfigRealmPortMapping `yaml:"portMapping,omitempty" json:"portMapping" validate:"omitempty"`
}