e91cbb923ef143f9be8cee262c0db8b331fcf8a6
1006
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e91cbb923e | feat(listener): expose Hysteria2 Realm TLS options | ||
|
|
3d4c2a8288 | test(converter): keep Hysteria2 realm options server-side | ||
|
|
cccd13784d | fix(converter): do not export Hysteria2 server realm options to clients | ||
|
|
5792bd1978 | test(uri): cover Sudoku nested URI serialization | ||
|
|
726781b7be | fix(uri): serialize Sudoku listener options without object coercion | ||
|
|
3d0b9d40be | fix(listener): correct Sudoku server fields and custom table serialization | ||
|
|
d49940d510 |
fix(ci): restore VMess host fallback from SNI/server addr; fix test
Reverted the previous 'fix' ( |
||
|
|
233d8067f1 |
fix(ci): VMess URI host field only set when explicitly configured
The CDN access fix (
|
||
|
|
c6f7c02cdc |
fix(sub): smart TLS/SNI/fingerprint for all protocol exports
Post-process every client proxy (YAML) with access-profile SNI, CDN TLS for WS/gRPC, and fingerprint. URI path already uses applyURIAccessProfile globally; VMess Host and AnyTLS fp defaults tightened. |
||
|
|
8507b8636c |
fix(sub): audit URI and YAML export for CDN access fields
Fill security=tls, sni, fp, and ws host from AccessSNI/PublicHost for domain+WS share links and client YAML; default fingerprint chrome. |
||
|
|
b3170423ba |
fix(converter): fill servername and fingerprint for CDN VLESS/WS
Client YAML for domain+WS on 443 was missing TLS SNI/servername and client-fingerprint, which breaks Cloudflare edge handshake. |
||
|
|
d2a80f5f80 |
fix(protocol): strip ws-headers from VLESS/VMess listeners
Mihomo inbound only supports ws-path for WebSocket; headers are client-side. Keep ws-headers in panel config for share/export. |
||
|
|
0d894a7415 | fix(routing): save editor state before generate and apply | ||
|
|
aa57a08483 |
fix(warp): materialize domain list into rules and fix domain matching
WARP domains now expand into visible DOMAIN-* rules (API + UI) on save. Apply rebuilds them from the domain list, strengthens sniffer (parse-pure-ip, force-dns-mapping), and always offers WARP as a rule target. |
||
|
|
2a900f4b85 |
feat(routing): richer WARP domain syntax and optional global WARP egress
Domain lines support domain:/full:/keyword:/geosite: prefixes. Toggle warpGlobal sets MATCH,WARP while keeping Cloudflare control-plane DIRECT. |
||
|
|
a8ab56281a |
fix(warp): enable sniffer+DNS so domain rules match IP dials
Inbound clients often dial resolved IPs; DOMAIN-SUFFIX never hits without TLS SNI sniff. When WARP domains are set: mode=rule, sniffer override, redir-host DNS, CF control-plane DIRECT, DOMAIN+SUFFIX rules, dialer-proxy DIRECT. |
||
|
|
5f6ad28148 |
fix(warp): report register save errors; clarify MASQUE vs server WireGuard
WARPRegister no longer swallows SaveWARPAccount failures. UI/docs note that server egress always injects WireGuard WARP; MASQUE is preview-only. |
||
|
|
9269522194 | fix(frontend): include ruleProviders in server-routing load fallback | ||
|
|
4e4c657674 |
fix(warp): upsert panel_settings without UNIQUE key conflicts
Soft-deleted panel_settings rows still occupied UNIQUE(key), so re-registering WARP failed with constraint 2067. Use Unscoped lookup/save and hard-delete on account removal; same for server-routing. |
||
|
|
96d4a8a432 |
feat(routing): Mihomo rule-providers with hot update
Store rule-providers on server-routing, emit official rule-providers
block into serving config, and expose PUT hot-update via core
/providers/rules/{name}. Panel UI on Routing → Server egress.
|
||
|
|
95961c6cc2 |
fix(warp): make WARP domains actually work in server Mihomo
- ProxyMap always sets Cloudflare peer public-key, allowed-ips, remote-dns-resolve - Default peer key when registration omits peers - Surface ProxyMap errors at generate; clearer apply prompt for server egress - Domain rule builder hardened; unit test for warp domain injection |
||
|
|
1ddb808270 |
fix(i18n): declare WARP/routing keys; restore 3x-ui in acknowledgments
Unit test required en.ts keys used by Routing/Settings. Credits keep 3x-ui / s-ui as before. |
||
|
|
fec9733baa |
docs: remove third-party panel name references from copy
Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. docs: remove third-party panel name references from copy Drop and external m-ui product names from docs, UI strings, and code comments. Keep functional descriptions of server egress and WARP. |
||
|
|
0beaf396d1 |
docs(api): align OpenAPI with server-routing and WARP account
Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. docs(api): align OpenAPI with server-routing and WARP account Replace obsolete inject-warp; document GET/PUT server-routing, GET/POST/DELETE system/warp, and templates/warp register nosave. |
||
|
|
81df3a9e29 |
feat(warp): persist WARP account, server inject, no server templates
Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. feat(warp): persist WARP account, server inject, no server templates Persist Cloudflare WARP account, inject outbound WARP into serving Mihomo, route warpDomains on server egress. Remove client community templates from the server tab; domains editor on Routing → Server egress. |
||
|
|
727f5c0d6b |
fix(ui): server-egress templates separate from client packs
Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). fix(ui): server-egress templates separate from client packs Server tab no longer offers community client rule packs. Presets are MATCH/DIRECT, private DIRECT, and optional AI→WARP (needs WARP proxy). |
||
|
|
aa548bd5c3 |
fix(ui): compact desktop sidebar icons when collapsed
Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. fix(ui): compact desktop sidebar icons when collapsed Global menu height:auto + padding-block for long i18n labels was also applied in collapsed mode, stretching icon-only rows. Override collapsed items to fixed 40px height and pass inlineCollapsed to Menu. |
||
|
|
eefc5cc889 | docs: zh-CN routing link for server egress | ||
|
|
ca2d4a3192 |
docs: sync routing (server egress) and WARP limits
Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). docs: sync routing (server egress) and WARP limits Document Client vs Server tabs, server-routing API, and that WARP register is manual (no auto AI/streaming unlock pipeline). |
||
|
|
4d51a5704f |
feat(routing): server-side Mihomo egress for panel process
Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. feat(routing): server-side Mihomo egress for panel process Store server-routing (rules/proxies/groups) separately from client visual-config. Generator applies it to the serving core instead of a hard-coded MATCH,DIRECT. Routing UI gains Client / Server tabs. |
||
|
|
addf8aee32 |
fix(sub): align sing-box export with migration 1.11–1.15
Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. fix(sub): align sing-box export with migration 1.11–1.15 Drop deprecated DNS rule ip_is_private without match_response (1.14/1.16). Keep typed DNS servers, TUN without stack, route sniff/hijack-dns actions, and route-level private direct per official migration/deprecated docs. |
||
|
|
4e68f45c5d |
fix(sub): reduce SFA lag — local domain resolver + private direct
Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. fix(sub): reduce SFA lag — local domain resolver + private direct Avoid DNS↔proxy loops by resolving outbound hostnames via local DNS, hijack DNS with port 53, route private IPs direct, drop auto_redirect, and use DoH remote DNS after the tunnel is up. |
||
|
|
f7c6f8a0ae |
fix(sub): align sing-box TUN with v1.14 template (no stack)
Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. fix(sub): align sing-box TUN with v1.14 template (no stack) Match LongLights sing-box_v1.14 tun-in: interface_name, IPv4-only address, route_exclude_address, dns_mode=hijack, auto_redirect. Omit stack so the client chooses the default stack. |
||
|
|
d1e49a6ad3 |
Update singbox.go
Update singbox.go Update singbox.go Update singbox.go Update singbox.go Update singbox.go Update singbox.go Update singbox.go Update singbox.go Update singbox.go Update singbox.go Update singbox.go |
||
|
|
38720e14c1 |
fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages
Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. fix(sub): sing-box DNS + mobile TUN MTU to stop broken web pages Proxy-only TUN without DNS left CSS/JS CDNs failing under SFA. Add dns.servers (UDP 8.8.8.8 via proxy), MTU 1500, mixed stack, strict_route off for Android compatibility. |
||
|
|
3969d5ed68 |
fix(subpage): harden HTML profile page for SFA/SFM WebView
Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. fix(subpage): harden HTML profile page for SFA/SFM WebView Avoid CSS that breaks embedded WebViews: solid color fallbacks, no side-by-side import grid, flex instead of place-items, overflow and safe-area padding for Profile-Web-Page-Url in-app browsers. |
||
|
|
5e6cc8b604 |
fix(sub): sing-box 1.11+ route actions instead of inbound sniff
Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. fix(sub): sing-box 1.11+ route actions instead of inbound sniff Remove legacy inbound sniff (removed in 1.13). Use route rule actions sniff + hijack-dns per official migration guide. |
||
|
|
af01f1f237 | fix(share): emit VLESS WS host in URI when ws-headers empty | ||
|
|
f9f5c66fed |
fix(ci,sub): gofmt + align sing-box TUN with official schema
Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. fix(ci,sub): gofmt + align sing-box TUN with official schema Use official Tun address example (172.18.0.1/30 + ULA IPv6) and keep auto_route/strict_route/stack per sing-box docs. |
||
|
|
c604bfc6d7 | fix(sub): repair sing-box comment syntax after TUN inject | ||
|
|
9fef35e238 |
feat(sub): inject default TUN inbound into sing-box JSON (#88)
SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. feat(sub): inject default TUN inbound into sing-box JSON (#88) SFI/SFM need a tun inbound to create the system VPN interface. Subscription docs now include tun-in + route.final=proxy alongside node outbounds. |
||
|
|
b664e57117 |
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. fix(share): CDN VLESS/WS export Host + path defaults When ws-headers Host is empty, client URI/YAML/subscription now fall back to SNI or public host. Always emit WS path (default /). Panel gains an explicit WebSocket Host field for CDN setups. |
||
|
|
35f4c521c3 |
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(vless): stop exporting flow on transports that cannot carry it (#87) * fix(vless): stop exporting flow on transports that cannot carry it The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate reasons, both fixed. The clear came too late to matter. formValuesToConfig wrote `flow` into the config inside the protocol switch, and only afterwards did the "Vision is TCP-only" rule run — where it mutated the form state, not the config it had already been copied into. The transport is now resolved before anything writes, and the drop also removes a flow inherited from the previous config, so changing a listener's transport actually takes effect. The URI generators never asked. Every one of them emitted whatever flow it found: - node: vlessURIs decided flow before resolving the transport, so the type it had just computed was irrelevant to the decision. It now resolves the transport first. ClientURIsWithCredentials also merged the listener-level flow onto every user on protocol alone, which reintroduced the value the form had dropped. - protocol: the share URI and the client YAML both wrote spec.Flow unguarded, even though TransportSpec knows the network. Same for the server-side compile, which pushed the listener default onto users — Mihomo rejects that combination outright, so this one takes the listener down. - mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the listener default into users before it was known to be applicable. - converter: copyTransport resolved `network` but left a stale flow behind. The rule is now one predicate per representation: TransportSpec.CarriesFlow, VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs. TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it. Tests pin each path in both directions — a stale flow disappears on ws/grpc/ xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real form builder and fails, in three of five cases, against the previous ordering. * fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes Auditing the remaining exporters after the flow fix turned up two more places where the link disagreed with the listener. vmessURIs built its JSON by hand instead of going through transportParams, so a ws listener that pins a Host header exported only the path: the client had nothing to send as Host and the listener was unreachable through the link, even though the client YAML built from the same config worked. It also dropped allowInsecure, which vless and trojan both carry — a listener whose certificate the client cannot verify exported a link that failed the handshake. The repeated tlsParams() calls were hoisted while touching this. hysteria2URIs reads users either as rows or as a map, and the two branches disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`, the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The same listener therefore produced different links depending on how its users were stored. Both now use clientSkipCert, the helper intended for share URIs, and emit both keys. Not fixed, for the record: an SS listener with simple-obfs exports a bare ss:// link with no `plugin=` entry, so the obfs configuration is lost. The client YAML does translate it (plugin: obfs). Fixing the URI means committing to a SIP002 plugin encoding that varies between clients, which is worth deciding separately rather than guessing at here. * i18n: complete zh-CN and guard against the locale regressions already seen zh-CN was missing seven listener hints that en.ts declares, so those tooltips rendered in English for the primary UI language. Translated: client_auth_cert, client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type. The rest of the catalogue is deliberately left alone. Seventeen locales each lack 65 keys that en.ts has; the provider already resolves those from en.ts, so they degrade to English rather than breaking, and filling 1100 strings with unreviewed machine translation would be worse than the fallback. The test prints the per-locale gap so it stays visible. The new locale test guards the regressions this directory has actually hit: a duplicated key inside one object (which breaks the type check), and a key renamed in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a gap there reads as a defect rather than as translation lag. * i18n: declare the 24 keys the UI asks for but no locale ever defined Twenty-four keys were referenced from components without being declared in any locale, so t() fell through to its hardcoded fallback. Where a fallback existed that merely meant the string could never be translated; where none existed — protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip either rendered the raw key, "listeners.protocolHint", or disappeared entirely, because fieldTip() suppresses a value that equals the key it looked up. en.ts takes the text the code was already falling back to, so nothing changes on screen; the strings just become translatable. The few without a fallback were written from the surrounding context — the field they annotate, its placeholder, and the wording of the neighbouring hints. trafficMultiplierHint is the exception: its fallback was Chinese, which is wrong for the English catalogue, so it now has a real English string. zh-CN gets all twenty-four, which keeps it complete against en.ts. The locale test now reads through the TypeScript AST. It previously parsed line by line and these files contain minified regions — several "key":"value" pairs on one line — so a whole block of keys was invisible to it, which is how this gap stayed hidden. It also gained the assertion that would have caught the bug directly: every dotted key handed to t() or fieldTip() must be declared in en.ts. The remaining languages still lag, now by 89 keys rather than 65, and still fall back to English; the test prints the per-locale figure. * fix(uri): drop the vmess allowInsecure field the reference client ignores The vmess skip-certificate field was added on internal consistency alone — vless and trojan carry allowInsecure in their query strings, so vmess looked like an oversight. Checked against the client that actually parses these links, it is not one. v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy, net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field at all — neither allowInsecure nor insecure — so the field was dead weight in a payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there is no spelling that the reference clients agree on. Emitting one would have produced a link that looks configured but is not. The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the meaning of that field, so dropping it broke ws listeners that pin a Host. The conformance test now pins the emitted payload to v2rayN's field set, so a future field copied over from another protocol fails instead of being silently ignored by every client. Deliberately left alone, both confirmed against the official scheme and worth deciding separately: - hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit that the parameters "should never include client modes ... or bandwidth values". Removing them changes behaviour for anyone relying on the link, so it is flagged rather than removed. `insecure` is the official parameter; allowInsecure is kept alongside it purely for clients that read that name. - an SS listener with simple-obfs still exports a bare ss:// link with no plugin entry, losing the obfuscation the client YAML does translate. Also noted while checking: tuicURIs defaults congestion_control to bbr where sing-box documents cubic as its default. Ours is set explicitly, which is valid, but the comment claiming it is what "most TUIC clients" use is not sourced. * fix(uri): keep vmess allowInsecure after all Reverts the revert. Dropping it was correct on the evidence and wrong on the consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read allowInsecure are precisely the ones whose handshake fails without it, and an ignored extra key costs the others nothing. Removing it would have traded a working link for a tidy payload. The conformance test keeps its guard, with allowInsecure recorded as an explicit, justified exception rather than a silent inclusion: the field set is still pinned to v2rayN's model, so any further field copied over from another protocol has to argue its way in. * fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links Two leftovers from auditing the exporters end to end. The registry's VMess compiler built ws-opts with the path only, so a listener that pins a Host exported a client config with nothing to send as Host. The vless YAML path in the same file already nests it under `headers` per the mihomo wiki, so this just mirrors that — and the converter has always carried it, which is how the difference went unnoticed. The node URI share endpoint also had a tier that could not be reached. It walks three tiers — m-ui, the registry, then the legacy builders — and the second one gates the third on having neither a URI nor client YAML. But the registry implements URI export for a different protocol set than the legacy builders: it compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML without ever emitting a URI, so those listeners arrived at the third tier with a YAML in hand and the condition already false, and reported no link at all even though ClientURIsWithCredentials exports one. The third tier now runs whenever the URI is still missing, and only fills in YAML that earlier tiers did not already provide. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> |
||
|
|
232a4432d8 |
fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85)
* fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> fix(metrics): resolve the real cgroup on LXC for memory and CPU (#85) * fix(metrics): resolve the real cgroup on LXC, not just the hierarchy root On LXC the memory cards still disagreed. Path resolution assumed /sys/fs/cgroup is the whole hierarchy and simply appended the path from /proc/self/cgroup to it. LXC's default (cgroup:mixed) mounts a tmpfs there and bind-mounts only the container's own cgroup into a directory below it, so that path names directories relative to the host hierarchy and does not exist from inside the container. Resolution then failed, readServiceCgroupMemory() had no fallback, and the per-process card silently degraded to raw RSS while the system card read a container-scoped cgroup — two different sources, which is exactly the inconsistency being reported. The path is now translated through the mount's root from /proc/self/mountinfo: if /sys/fs/cgroup exposes only a subtree, the part of the reported path that the mount hides is dropped. That resolves both the container's own cgroup and any unit nested inside it, so the two cards land on one accounting domain instead of "working by accident" on hosts where the mount happens to be the root. readServiceCgroupMemory() also gained the root fallback it was missing, so a layout that cannot be resolved at all still reads the domain the process is accounted in rather than falling back to RSS. Adopting the root is gated on a finite cap being enforced there: on a bare-metal host the root is unbounded and attributing the machine's whole memory to two processes would be a fabrication, so those hosts keep the previous RSS behaviour. Both readers now share cgroupMemoryFiles(), so the v2/v1 field probing exists once instead of being copied per caller — the duplication is how the missing fallback went unnoticed. Memory path constants are dropped with it, and the v1 PAGE_COUNTER_MAX sentinel is named rather than open-coded. Tests cover the layouts directly, with /proc and /sys pointed at fixtures through named path variables: plain hierarchy root, bind-mounted container cgroup, a unit nested below it, cgroup v1, the bounded and unbounded fallback, sentinel rejection, and the invariant that both cards resolve to the same usage. A malformed mountinfo degrades to "unresolved", never to a wrong path. * fix(metrics): read CPU accounting from the resolved cgroup cpu.stat, cpu.max and cpuset.cpus.effective were read from hard-coded hierarchy-root paths, so they had the same flaw the memory files had: in a container the accounting for this process is not at the root. On LXC the root is a tmpfs holding nothing readable, so the CPU card silently fell back to host-wide /proc/stat and reported the machine instead of the container — the container could be fully throttled while the bar sat near idle. They now go through cpuAccountingDir(), which picks the process' own cgroup when the kernel enforces a cap there (a memory limit or a CPU quota) and otherwise keeps the hierarchy root. That preserves bare-metal semantics, where the card is meant to describe the machine rather than the panel's own unit, while a container — the case where the root is simply the wrong place to look — is read from the cgroup that actually confines it. Usage is taken from the first directory that has it, never mixed across two: a delta between two different cgroups would be meaningless. Caps are probed across the directories instead, since a quota on a broader domain still applies to us, and cpuCapacity() already narrows to the tightest limit found. The three fixed CPU path constants go away with the hard-coded reads; every accounting file is now read relative to a resolved directory. * test(system): stop comparing memory samples taken at different times Two tests guarded the invariant that the system and process cards read the same memory pool, and both failed intermittently — reproducibly under -race, and occasionally in a plain run, which the backend job executes: system memory used (2874142720) is smaller than this process' RSS (2875170816) panel memory 2874384384 exceeds system used 2874142720 They compared a possibly 500ms-stale cached system reading against a live process reading of the test binary itself. Where the panel is most of its own accounting domain — a container, or a sandbox running the test alone — those two numbers are nearly identical, so the comparison has no margin of its own and any allocation in between flips it. The process is now sampled first, so the reference reading is the newer one, and a tolerance absorbs the remaining skew. What these tests guard is a mismatch of sources, which was out by multiples (host /proc/meminfo against the cgroup), so a percent-wide tolerance keeps the assertion meaningful. --------- Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com> |
||
|
|
19ace66aef | fix(i18n): add listeners.quickCreateHint for all locales | ||
|
|
eb78dec2d8 |
ci(release): restore Changes-since notes like v1.3.7
Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. ci(release): restore Changes-since notes like v1.3.7 Build formal release body from conventional commits between the previous SemVer tag and this release, then append install notes. |
||
|
|
aa1b51b28c |
fix(ui): drop useless generic wiki tooltip stubs
Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. fix(ui): drop useless generic wiki tooltip stubs Remove fallback *Hint strings that only said "see wiki". Tooltips now use fieldTip() and render only when real copy exists. |
||
|
|
8c5b0692ad | fix(ui): pass t into capability renderFields (TS2304) | ||
|
|
1622893434 | fix(i18n): restore missing commas after locale key dedupe | ||
|
|
2349bd0cb8 | fix(i18n): remove duplicate locale object keys breaking tsc |