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:
@@ -44,8 +44,18 @@ export default {
|
||||
download: "Download",
|
||||
upload: "Upload",
|
||||
total: "Total",
|
||||
serviceStateLabel: "Hysteria service",
|
||||
serviceState: {
|
||||
active: "Running",
|
||||
inactive: "Stopped",
|
||||
unknown: "State unknown",
|
||||
},
|
||||
trafficApiLabel: "Traffic Stats API",
|
||||
trafficApiReachable: "Reachable",
|
||||
trafficApiUnreachable: "Unreachable",
|
||||
security: {
|
||||
hysteriaStopped: "Hysteria2 is stopped",
|
||||
hysteriaStateUnknown: "Hysteria2 service state is unknown: systemd did not answer",
|
||||
trafficApiUnavailable: "Traffic API is unavailable",
|
||||
},
|
||||
error: {
|
||||
@@ -238,8 +248,11 @@ export default {
|
||||
onlineStatus: "Online Status",
|
||||
online: "Online",
|
||||
offline: "Offline",
|
||||
onlineUnknown: "Online unknown",
|
||||
onlineUnavailable: "Live connection state is currently unavailable",
|
||||
onlineUnavailableHint:
|
||||
"The Hysteria Traffic Stats API did not answer, so online status and device counts are unknown. Stored peer state in the table is accurate.",
|
||||
device: "Online Devices",
|
||||
unit: "Unit",
|
||||
loginAt: "Last login time",
|
||||
conAt: "Last connection time",
|
||||
createTime: "Create Time",
|
||||
@@ -274,17 +287,9 @@ export default {
|
||||
resetTrafficMonth: "Run once a month, midnight, first of month",
|
||||
resetTrafficWeek: "Run once a week, midnight between Sat/Sun",
|
||||
},
|
||||
monitor: {
|
||||
cpuPercent: "CPU Usage",
|
||||
memPercent: "Memory Usage",
|
||||
diskPercent: "Disk Usage",
|
||||
hysteria2UserTotal: "Number of online users",
|
||||
hysteria2DeviceTotal: "Number of online devices",
|
||||
hysteria2Version: "Hysteria2 Version",
|
||||
hysteria2Running: "Hysteria2 Status",
|
||||
hysteria2RunningTrue: "Running",
|
||||
hysteria2RunningFalse: "Stop",
|
||||
},
|
||||
// The `monitor` section is gone together with its only consumer — the
|
||||
// "Hysteria2 Status" tag on the config page. The service state has three
|
||||
// values, not two, and its phrases live in `dashboard.serviceState`.
|
||||
log: {
|
||||
numLine: "Number of lines",
|
||||
level: "Level",
|
||||
@@ -305,201 +310,32 @@ export default {
|
||||
notFoundBackHome: "Back to home",
|
||||
},
|
||||
hysteria: {
|
||||
enable: "Enable",
|
||||
disable: "Disable",
|
||||
addConfigItem: "Add Config Item",
|
||||
hysteria2Version: "Hysteria2 Version",
|
||||
hysteria2Running: "Hysteria2 Status",
|
||||
outboundsEmpty: "No outbounds are configured on the server",
|
||||
listen: "Listen",
|
||||
ownedByOrchestrator: "The Hysteria configuration is owned by the installer",
|
||||
ownedByOrchestratorHint:
|
||||
"The panel shows /etc/hysteria/config.yaml read-only. Changes are applied by `hy2xs-orchestrator reconfigure`.",
|
||||
listen: "Listen address",
|
||||
auth: "Peer authentication",
|
||||
tls: "TLS",
|
||||
obfs: "Obfuscation",
|
||||
quic: "QUIC parameters",
|
||||
bandwidth: "Bandwidth",
|
||||
congestion: "Congestion control",
|
||||
speedTest: "Speed Test",
|
||||
udp: "UDP",
|
||||
resolver: "Resolver",
|
||||
sniff: "Protocol Sniffing",
|
||||
acl: "ACL",
|
||||
outbounds: "Outbounds",
|
||||
http: "Traffic Stats API (HTTP)",
|
||||
masquerade: "Masquerade",
|
||||
config: {
|
||||
listen:
|
||||
"When the IP address is omitted, the server will listen on all interfaces, both IPv4 and IPv6. To listen on IPv4 only, you can use 0.0.0.0:443. To listen on IPv6 only, you can use [::]:443.",
|
||||
tlsType: "TLS type",
|
||||
tls: {
|
||||
cert: "The path to the Cert file.",
|
||||
key: "The path to the Key file.",
|
||||
sniGuard:
|
||||
'Verify the SNI provided by the client. Accept the connection only when it matches what\'s in the certificate. Terminate the TLS handshake otherwise. Set to strict to enforce this behavior. Set to disable to disable this entirely. The default is dns-san, which enables this feature only when the certificate contains the "Subject Alternative Name" extension with a domain name in it.',
|
||||
},
|
||||
acme: {
|
||||
domains: "Domains",
|
||||
email: "Email",
|
||||
ca: "The CA to use. Can be letsencrypt or zerossl.",
|
||||
listenHost:
|
||||
"The host address (not including the port) to listen on for the ACME challenge. If omitted, the server will listen on all interfaces.",
|
||||
dir: "The directory to store the ACME account key and certificates.",
|
||||
type: "ACME challenge type. Can be http, tls, or dns.",
|
||||
http: {
|
||||
altPort:
|
||||
"Listening port for HTTP challenges. (Note: Changing to a port other than 80 requires port forwarding or HTTP reverse proxy, or the challenge will fail!)",
|
||||
},
|
||||
tls: {
|
||||
altPort:
|
||||
"Listening port for TLS-ALPN challenges. (Note: Changing to a port other than 443 requires port forwarding or TLS reverse proxy, or the challenge will fail!)",
|
||||
},
|
||||
dns: {
|
||||
name: "DNS provider. For details, refer to ACME DNS Configuration.",
|
||||
config: "ACME DNS Configuration",
|
||||
},
|
||||
disableHTTP: "Disable HTTP challenge.",
|
||||
disableTLSALPN: "Disable TLS-ALPN challenge.",
|
||||
altHTTPPort:
|
||||
"Alternate HTTP challenge port. (Note: If you want to use anything other than 80, you must set up port forward/HTTP reverse proxy from 80 to that port, otherwise ACME will not be able to issue the certificate.)",
|
||||
altTLSALPNPort:
|
||||
"Alternate TLS-ALPN challenge port. (Note: If you want to use anything other than 443, you must set up port forward/SNI proxy from 443 to that port, otherwise ACME will not be able to issue the certificate.)",
|
||||
},
|
||||
obfs: {
|
||||
type: "Obfuscation type: gecko (HY2XS production default) or salamander (compatibility fallback).",
|
||||
salamander: {
|
||||
password: "Replace with a strong password of your choice.",
|
||||
},
|
||||
gecko: {
|
||||
password: "Replace with a strong password of your choice.",
|
||||
minPacketSize:
|
||||
"Minimum QUIC handshake fragment size. HY2XS baseline: 512.",
|
||||
maxPacketSize:
|
||||
"Maximum QUIC handshake fragment size. HY2XS baseline: 1200, upstream limit: 2048.",
|
||||
},
|
||||
},
|
||||
quic: {
|
||||
initStreamReceiveWindow: "The initial QUIC stream receive window size.",
|
||||
maxStreamReceiveWindow: "The maximum QUIC stream receive window size.",
|
||||
initConnReceiveWindow:
|
||||
"The initial QUIC connection receive window size.",
|
||||
maxConnReceiveWindow:
|
||||
"The maximum QUIC connection receive window size.",
|
||||
maxIdleTimeout:
|
||||
"The maximum idle timeout. How long the server will consider the client still connected without any activity.",
|
||||
maxIncomingStreams:
|
||||
"The maximum number of concurrent incoming streams.",
|
||||
disablePathMTUDiscovery: "Disable QUIC path MTU discovery.",
|
||||
disableStatelessReset:
|
||||
"Disable QUIC stateless reset. Kept off in the HY2XS baseline: stateless reset lets a client with a stale connection reconnect immediately after a server restart or device sleep.",
|
||||
},
|
||||
bandwidth: {
|
||||
up: "Up",
|
||||
down: "Down",
|
||||
disableLossCompensation:
|
||||
"Disable loss compensation. Kept off in the HY2XS baseline, so compensation stays active.",
|
||||
},
|
||||
congestion: {
|
||||
type: "Fallback congestion controller: bbr or reno. Used when Brutal bandwidth is not negotiated by both sides.",
|
||||
bbrProfile: "BBR profile: standard, conservative or aggressive.",
|
||||
},
|
||||
ignoreClientBandwidth:
|
||||
"When enabled, makes the server to disregard any bandwidth hints set by clients",
|
||||
speedTest:
|
||||
"speedTest enables the built-in speed test server. When enabled, clients can test their download and upload speeds with the server. For more information, see the Speed Test documentation.",
|
||||
disableUDP:
|
||||
"disableUDP disables UDP forwarding, only allowing TCP connections.",
|
||||
udpIdleTimeout:
|
||||
"udpIdleTimeout specifies the amount of time the server will keep a local UDP port open for each UDP session that has no activity. This is conceptually similar to the NAT UDP session timeout.",
|
||||
resolver: {
|
||||
type: "Type",
|
||||
tcp: {
|
||||
addr: "The address of the TCP resolver.",
|
||||
timeout: "The timeout for DNS queries.",
|
||||
},
|
||||
udp: {
|
||||
addr: "The address of the UDP resolver.",
|
||||
timeout: "The timeout for DNS queries.",
|
||||
},
|
||||
tls: {
|
||||
addr: "The address of the TLS resolver.",
|
||||
timeout: "The timeout for DNS queries.",
|
||||
sni: "The SNI to use for the TLS resolver.",
|
||||
insecure: "Disable TLS verification for the TLS resolver.",
|
||||
},
|
||||
https: {
|
||||
addr: "The address of the HTTPS resolver.",
|
||||
timeout: "The timeout for DNS queries.",
|
||||
sni: "The SNI to use for the TLS resolver.",
|
||||
insecure: "Disable TLS verification for the TLS resolver.",
|
||||
},
|
||||
},
|
||||
sniff: {
|
||||
enable: "Whether to enable protocol sniffing.",
|
||||
timeout:
|
||||
"Sniffing timeout. If the protocol/domain cannot be determined within this time, the original address will be used to initiate the connection.",
|
||||
rewriteDomain:
|
||||
"Whether to rewrite requests that are already in domain name form. If enabled, requests with the target address already in domain name form will still be sniffed.",
|
||||
tcpPorts:
|
||||
"List of TCP ports. Only TCP requests on these ports will be sniffed.",
|
||||
udpPorts:
|
||||
"List of UDP ports. Only UDP requests on these ports will be sniffed.",
|
||||
},
|
||||
aclType: "ACL type",
|
||||
acl: {
|
||||
file: "The path to the ACL file.",
|
||||
inline: "The list of inline ACL rules.",
|
||||
geoip:
|
||||
"Optional. Uncomment to enable. The path to the GeoIP database file. If this field is omitted, Hysteria will automatically download the latest database to your working directory.",
|
||||
geosite:
|
||||
"Optional. Uncomment to enable. The path to the GeoSite database file. If this field is omitted, Hysteria will automatically download the latest database to your working directory.",
|
||||
geoUpdateInterval:
|
||||
"Optional. The interval at which to refresh the GeoIP/GeoSite databases. 168 hours (1 week) by default. Only applies if the GeoIP/GeoSite databases are automatically downloaded. (Check the note below for more information.)",
|
||||
},
|
||||
outbounds: {
|
||||
name: "The name of the outbound. This is used in ACL rules.",
|
||||
type: "Type",
|
||||
socks5: {
|
||||
addr: "The address of the SOCKS5 proxy.",
|
||||
username:
|
||||
"Optional. The username for the SOCKS5 proxy, if authentication is required.",
|
||||
password:
|
||||
"Optional. The password for the SOCKS5 proxy, if authentication is required.",
|
||||
},
|
||||
http: {
|
||||
url: "The URL of the HTTP/HTTPS proxy. (Can be http:// or https://)",
|
||||
insecure:
|
||||
"Optional. Whether to disable TLS verification. Applies to HTTPS proxies only.",
|
||||
},
|
||||
direct: {
|
||||
mode: "Type",
|
||||
bindIPv4: "The local IPv4 address to bind to.",
|
||||
bindIPv6: "The local IPv6 address to bind to.",
|
||||
bindDevice: "The local network interface to bind to.",
|
||||
fastOpen: "Enable TCP fast open.",
|
||||
},
|
||||
},
|
||||
trafficStats: {
|
||||
listen: "The address to listen on.",
|
||||
},
|
||||
masquerade: {
|
||||
type: "Type",
|
||||
file: {
|
||||
dir: "The directory to serve files from.",
|
||||
},
|
||||
proxy: {
|
||||
url: "The URL of the website to proxy.",
|
||||
rewriteHost:
|
||||
"Whether to rewrite the Host header to match the proxied website. This is required if the target web server uses Host to determine which site to serve.",
|
||||
insecure: "Disable TLS verification for the proxied website.",
|
||||
},
|
||||
string: {
|
||||
content: "The string to return.",
|
||||
headers: "Optional. The headers to return.",
|
||||
statusCode: "Optional. The status code to return. 200 by default.",
|
||||
},
|
||||
listenHTTP: "HTTP (TCP) listen address.",
|
||||
listenHTTPS: "HTTPS (TCP) listen address.",
|
||||
forceHTTPS:
|
||||
"Whether to force HTTPS. If enabled, all HTTP requests will be redirected to HTTPS.",
|
||||
},
|
||||
},
|
||||
trafficStats: "Traffic Stats API",
|
||||
notSet: "not set",
|
||||
sectionMissing: "The section is absent from the configuration",
|
||||
tlsMissing: "Neither tls nor acme is configured",
|
||||
valuesHidden: "values are not shown",
|
||||
secretSet: "set",
|
||||
secretMissing: "not set",
|
||||
obfsPasswordHint: "handed out in the peer share link",
|
||||
driftTitle: "Configuration drift",
|
||||
driftHint:
|
||||
"The file contains sections outside the HY2XS production profile. The orchestrator neither creates nor supports them: the configuration was most likely edited by hand.",
|
||||
trafficStatsMissing: "Traffic Stats API is not configured",
|
||||
trafficStatsMissingHint:
|
||||
"Without the trafficStats section the device limit, traffic accounting and forced disconnect do not work: the panel reaches Hysteria only through this API.",
|
||||
trafficStatsNotLoopback:
|
||||
"the address is not loopback: the panel reaches the Traffic Stats API over 127.0.0.1 only",
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user