Files
founder ddf0ddf71e 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 файлов),
документация на русском.
2026-08-27 08:15:02 +05:00

319 lines
19 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 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"`
UDPIdleTimeout *string `yaml:"udpIdleTimeout,omitempty" json:"udpIdleTimeout" validate:"omitempty"`
Auth *ServerConfigAuth `yaml:"auth,omitempty" json:"-" validate:"omitempty"`
Resolver *serverConfigResolver `yaml:"resolver,omitempty" json:"resolver" validate:"omitempty"`
Sniff *serverConfigSniff `yaml:"sniff,omitempty" json:"sniff" validate:"omitempty"`
ACL *serverConfigACL `yaml:"acl,omitempty" json:"acl" validate:"omitempty"`
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:"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 {
// Common fields
Domains []string `yaml:"domains,omitempty" json:"domains" validate:"required"`
Email *string `yaml:"email,omitempty" json:"email" validate:"required"`
CA *string `yaml:"ca,omitempty" json:"ca" validate:"required"`
ListenHost *string `yaml:"listenHost,omitempty" json:"listenHost" validate:"required"`
Dir *string `yaml:"dir,omitempty" json:"dir" validate:"required"`
// Type selection
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
HTTP *serverConfigACMEHTTP `yaml:"http,omitempty" json:"http" validate:"omitempty"`
TLS *serverConfigACMETLS `yaml:"tls,omitempty" json:"tls" validate:"omitempty"`
DNS *serverConfigACMEDNS `yaml:"dns,omitempty" json:"dns" validate:"omitempty"`
// Legacy fields for backwards compatibility
// Only applicable when Type is empty
DisableHTTP *bool `yaml:"disableHTTP,omitempty" json:"disableHTTP" validate:"required"`
DisableTLSALPN *bool `yaml:"disableTLSALPN,omitempty" json:"disableTLSALPN" validate:"required"`
AltHTTPPort *int `yaml:"altHTTPPort,omitempty" json:"altHTTPPort" validate:"required"`
AltTLSALPNPort *int `yaml:"altTLSALPNPort,omitempty" json:"altTLSALPNPort" validate:"required"`
}
type serverConfigACMEHTTP struct {
AltPort *int `yaml:"altPort,omitempty" json:"altPort" validate:"required"`
}
type serverConfigACMETLS struct {
AltPort *int `yaml:"altPort,omitempty" json:"altPort" validate:"required"`
}
type serverConfigACMEDNS struct {
Name *string `yaml:"name,omitempty" json:"name" validate:"required"`
Config map[string]string `yaml:"config,omitempty" json:"config" validate:"required"`
}
type serverConfigQUIC struct {
InitStreamReceiveWindow *uint64 `yaml:"initStreamReceiveWindow,omitempty" json:"initStreamReceiveWindow" validate:"omitempty"`
MaxStreamReceiveWindow *uint64 `yaml:"maxStreamReceiveWindow,omitempty" json:"maxStreamReceiveWindow" validate:"omitempty"`
InitConnectionReceiveWindow *uint64 `yaml:"initConnReceiveWindow,omitempty" json:"initConnReceiveWindow" validate:"omitempty"`
MaxConnectionReceiveWindow *uint64 `yaml:"maxConnReceiveWindow,omitempty" json:"maxConnReceiveWindow" validate:"omitempty"`
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 {
URL *string `yaml:"url,omitempty" json:"url" validate:"required"`
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
}
type ServerConfigAuth struct {
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
Password *string `yaml:"password,omitempty" json:"password" validate:"omitempty"`
UserPass map[string]string `yaml:"userpass,omitempty" json:"userpass" validate:"omitempty"`
HTTP *ServerConfigAuthHTTP `yaml:"http,omitempty" json:"http" validate:"omitempty"`
Command *string `yaml:"command,omitempty" json:"command" validate:"omitempty"`
}
type serverConfigResolverTCP struct {
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
}
type serverConfigResolverUDP struct {
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
}
type serverConfigResolverTLS struct {
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
SNI *string `yaml:"sni,omitempty" json:"sni" validate:"required"`
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
}
type serverConfigResolverHTTPS struct {
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
SNI *string `yaml:"sni,omitempty" json:"sni" validate:"required"`
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
}
type serverConfigResolver struct {
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
TCP *serverConfigResolverTCP `yaml:"tcp,omitempty" json:"tcp" validate:"omitempty"`
UDP *serverConfigResolverUDP `yaml:"udp,omitempty" json:"udp" validate:"omitempty"`
TLS *serverConfigResolverTLS `yaml:"tls,omitempty" json:"tls" validate:"omitempty"`
HTTPS *serverConfigResolverHTTPS `yaml:"https,omitempty" json:"https" validate:"omitempty"`
}
type serverConfigSniff struct {
Enable *bool `yaml:"enable,omitempty" json:"enable" validate:"required"`
Timeout *string `yaml:"timeout,omitempty" json:"timeout" validate:"required"`
RewriteDomain *bool `yaml:"rewriteDomain,omitempty" json:"rewriteDomain" validate:"required"`
TCPPorts *string `yaml:"tcpPorts,omitempty" json:"tcpPorts" validate:"omitempty"`
UDPPorts *string `yaml:"udpPorts,omitempty" json:"udpPorts" validate:"omitempty"`
}
type serverConfigACL struct {
File *string `yaml:"file,omitempty" json:"file" validate:"omitempty"`
Inline []string `yaml:"inline,omitempty" json:"inline" validate:"omitempty"`
GeoIP *string `yaml:"geoip,omitempty" json:"geoip" validate:"omitempty"`
GeoSite *string `yaml:"geosite,omitempty" json:"geosite" validate:"omitempty"`
GeoUpdateInterval *string `yaml:"geoUpdateInterval,omitempty" json:"geoUpdateInterval" validate:"omitempty"`
}
type serverConfigOutboundDirect struct {
Mode *string `yaml:"mode,omitempty" json:"mode" validate:"required"`
BindIPv4 *string `yaml:"bindIPv4,omitempty" json:"bindIPv4" validate:"required"`
BindIPv6 *string `yaml:"bindIPv6,omitempty" json:"bindIPv6" validate:"required"`
BindDevice *string `yaml:"bindDevice,omitempty" json:"bindDevice" validate:"required"`
FastOpen *bool `yaml:"fastOpen,omitempty" json:"fastOpen" validate:"required"`
}
type serverConfigOutboundSOCKS5 struct {
Addr *string `yaml:"addr,omitempty" json:"addr" validate:"required"`
Username *string `yaml:"username,omitempty" json:"username" validate:"omitempty"`
Password *string `yaml:"password,omitempty" json:"password" validate:"omitempty"`
}
type serverConfigOutboundHTTP struct {
URL *string `yaml:"url,omitempty" json:"url" validate:"required"`
Insecure *bool `yaml:"insecure,omitempty" json:"insecure" validate:"required"`
}
type serverConfigOutboundEntry struct {
Name *string `yaml:"name,omitempty" json:"name" validate:"required"`
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
Direct *serverConfigOutboundDirect `yaml:"direct,omitempty" json:"direct" validate:"omitempty"`
SOCKS5 *serverConfigOutboundSOCKS5 `yaml:"socks5,omitempty" json:"socks5" validate:"omitempty"`
HTTP *serverConfigOutboundHTTP `yaml:"http,omitempty" json:"http" validate:"omitempty"`
}
type ServerConfigTrafficStats struct {
Listen *string `yaml:"listen,omitempty" json:"listen" validate:"required"`
Secret *string `yaml:"secret,omitempty" json:"-" validate:"omitempty"`
}
type serverConfigMasqueradeFile struct {
Dir *string `yaml:"dir,omitempty" json:"dir" validate:"required"`
}
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 {
Content *string `yaml:"content,omitempty" json:"content" validate:"required"`
Headers map[string]string `yaml:"headers,omitempty" json:"headers" validate:"omitempty"`
StatusCode *int `yaml:"statusCode,omitempty" json:"statusCode" validate:"omitempty"`
}
type serverConfigMasquerade struct {
Type *string `yaml:"type,omitempty" json:"type" validate:"omitempty"`
File *serverConfigMasqueradeFile `yaml:"file,omitempty" json:"file" validate:"omitempty"`
Proxy *serverConfigMasqueradeProxy `yaml:"proxy,omitempty" json:"proxy" validate:"omitempty"`
String *serverConfigMasqueradeString `yaml:"string,omitempty" json:"string" validate:"omitempty"`
ListenHTTP *string `yaml:"listenHTTP,omitempty" json:"listenHTTP" validate:"omitempty"`
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"`
}