From d72c98a2543f676f81d04bdc58356639f95242cf Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Wed, 26 Nov 2025 10:14:06 +0500 Subject: [PATCH 1/5] chore: clarify and standardize argument type annotations and optional flags --- .../files/usr/lib/sing_box_config_manager.sh | 210 +++++++++--------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/podkop/files/usr/lib/sing_box_config_manager.sh b/podkop/files/usr/lib/sing_box_config_manager.sh index 9a575d4..c6d8ef3 100644 --- a/podkop/files/usr/lib/sing_box_config_manager.sh +++ b/podkop/files/usr/lib/sing_box_config_manager.sh @@ -21,9 +21,9 @@ SERVICE_TAG="__service_tag" ####################################### # Configure the logging section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string, JSON configuration # disabled: boolean, true to disable logging -# level: string, e.g., "info", "debug", "warn" +# level: string, log level. One of: trace debug info warn error fatal panic. # timestamp: boolean, true to include timestamps # Outputs: # Writes updated JSON configuration to stdout @@ -50,7 +50,7 @@ sing_box_cm_configure_log() { ####################################### # Configure the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # final: string, default dns server tag # strategy: string, default domain strategy for resolving the domain names # independent_cache: boolean, whether to use an independent DNS cache @@ -82,12 +82,12 @@ sing_box_cm_configure_dns() { ####################################### # Add a UDP DNS server to the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the DNS server # server_address: string, IP address or hostname of the DNS server -# server_port: string or number, port of the DNS server -# domain_resolver: string, domain resolver to use for resolving domain names -# detour: string, tag of the upstream outbound +# server_port: string or integer, port of the DNS server +# domain_resolver: string, domain resolver to use for resolving domain names (optional) +# detour: string, tag of the upstream outbound (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -122,12 +122,12 @@ sing_box_cm_add_udp_dns_server() { ####################################### # Add a TLS DNS server to the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the DNS server # server_address: string, IP address or hostname of the DNS server -# server_port: string or number, port of the DNS server -# domain_resolver: string, domain resolver to use for resolving domain names -# detour: string, tag of the upstream outbound +# server_port: string or integer, port of the DNS server +# domain_resolver: string, domain resolver to use for resolving domain names (optional) +# detour: string, tag of the upstream outbound (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -162,14 +162,14 @@ sing_box_cm_add_tls_dns_server() { ####################################### # Add an HTTPS DNS server to the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the DNS server # server_address: string, IP address or hostname of the DNS server -# server_port: string or number, port of the DNS server -# path: string, optional URL path for HTTPS DNS requests -# headers: string, optional additional headers for HTTPS DNS requests -# domain_resolver: string, domain resolver to use for resolving domain names -# detour: string, tag of the upstream outbound +# server_port: string or integer, port of the DNS server +# path: string, URL path for HTTPS DNS requests (optional) +# headers: string, additional headers for HTTPS DNS requests (optional) +# domain_resolver: string, domain resolver to use for resolving domain names (optional) +# detour: string, tag of the upstream outbound (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -210,7 +210,7 @@ sing_box_cm_add_https_dns_server() { ####################################### # Add a FakeIP DNS server to the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the DNS server # inet4_range: string, IPv4 range used for fake IP mapping # Outputs: @@ -236,7 +236,7 @@ sing_box_cm_add_fakeip_dns_server() { ####################################### # Add a DNS routing rule to the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # server: string, target DNS server for the rule # tag: string, identifier for the route rule # Outputs: @@ -263,10 +263,10 @@ sing_box_cm_add_dns_route_rule() { ####################################### # Patch a DNS routing rule in the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier of the rule to patch # key: string, the key in the rule to update or add -# value: JSON value to assign to the key +# value: string, JSON value to assign to the key # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -304,9 +304,9 @@ sing_box_cm_patch_dns_route_rule() { ####################################### # Add a DNS reject rule to the DNS section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # key: string, the key to set for the reject rule -# value: JSON value to assign to the key +# value: string, JSON value to assign to the key # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -331,10 +331,10 @@ sing_box_cm_add_dns_reject_rule() { ####################################### # Add a TProxy inbound to the inbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the inbound # listen_address: string, IP address to listen on -# listen_port: number, port to listen on +# listen_port: integer, port to listen on # tcp_fast_open: boolean, enable or disable TCP Fast Open # udp_fragment: boolean, enable or disable UDP fragmentation # Outputs: @@ -369,10 +369,10 @@ sing_box_cm_add_tproxy_inbound() { ####################################### # Add a Direct inbound to the inbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the inbound # listen_address: string, IP address to listen on -# listen_port: number, port to listen on +# listen_port: integer, port to listen on # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -399,10 +399,10 @@ sing_box_cm_add_direct_inbound() { ####################################### # Add a Mixed inbound to the inbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the inbound # listen_address: string, IP address to listen on -# listen_port: number, port to listen on +# listen_port: integer, port to listen on # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -429,7 +429,7 @@ sing_box_cm_add_mixed_inbound() { ####################################### # Add a Direct outbound to the outbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the outbound # Outputs: # Writes updated JSON configuration to stdout @@ -451,15 +451,15 @@ sing_box_cm_add_direct_outbound() { ####################################### # Add a SOCKS outbound to the outbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the outbound # server_address: string, IP address or hostname of the SOCKS server -# server_port: number, port of the SOCKS server -# version: string, optional SOCKS version -# username: string, optional username for authentication -# password: string, optional password for authentication -# network: string, optional network type (e.g., "tcp") -# udp_over_tcp: number, optional version for UDP over TCP +# server_port: integer, port of the SOCKS server +# version: string, SOCKS version (optional) +# username: string, username for authentication (optional) +# password: string, password for authentication (optional) +# network: string, network type (e.g., "tcp") (optional) +# udp_over_tcp: integer, version for UDP over TCP (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -509,16 +509,16 @@ sing_box_cm_add_socks_outbound() { ####################################### # Add a Shadowsocks outbound to the outbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the outbound # server_address: string, IP address or hostname of the Shadowsocks server -# server_port: number, port of the Shadowsocks server +# server_port: integer, port of the Shadowsocks server # method: string, encryption method # password: string, password for encryption -# network: string, optional network type (e.g., "tcp") -# udp_over_tcp: number, optional version for UDP over TCP -# plugin: string, optional plugin name -# plugin_opts: string, optional plugin options +# network: string, network type (e.g., "tcp") (optional) +# udp_over_tcp: integer, version for UDP over TCP (optional) +# plugin: string, plugin name (optional) +# plugin_opts: string, plugin options (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -573,14 +573,14 @@ sing_box_cm_add_shadowsocks_outbound() { ####################################### # Add a VLESS outbound to the outbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the outbound # server_address: string, IP address or hostname of the VLESS server -# server_port: number, port of the VLESS server +# server_port: integer, port of the VLESS server # uuid: string, user UUID -# flow: string, optional flow setting -# network: string, optional network type (e.g., "tcp") -# packet_encoding: string, optional packet encoding method +# flow: string, flow setting (optional) +# network: string, network type (e.g., "tcp") (optional) +# packet_encoding: string, packet encoding method (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -624,12 +624,12 @@ sing_box_cm_add_vless_outbound() { ####################################### # Add a Trojan outbound to the outbounds section of a sing-box JSON configuration. # Arguments: -# config: string, JSON configuration +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the outbound # server_address: string, IP address or hostname of the Trojan server -# server_port: number, port of the Trojan server +# server_port: integer, port of the Trojan server # password: string, password for authentication -# network: string, optional network type (e.g., "tcp") +# network: string, network type (e.g., "tcp") (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -664,12 +664,12 @@ sing_box_cm_add_trojan_outbound() { ####################################### # Set gRPC transport settings for an outbound in a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier of the outbound to modify -# service_name: string, optional gRPC service name -# idle_timeout: string or number, optional idle timeout -# ping_timeout: string or number, optional ping timeout -# permit_without_stream: boolean, optional flag for permitting without stream +# service_name: string, gRPC service name (optional) +# idle_timeout: string or integer, idle timeout (optional) +# ping_timeout: string or integer, ping timeout (optional) +# permit_without_stream: boolean, flag for permitting without stream (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -709,12 +709,12 @@ sing_box_cm_set_grpc_transport_for_outbound() { ####################################### # Set WebSocket transport settings for an outbound in a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier of the outbound to modify # path: string, WebSocket path -# host: string, optional Host header for WebSocket -# max_early_data: number, optional maximum early data -# early_data_header_name: string, optional header name for early data +# host: string, Host header for WebSocket (optional) +# max_early_data: integer, maximum early data (optional) +# early_data_header_name: string, header name for early data (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -759,14 +759,14 @@ sing_box_cm_set_ws_transport_for_outbound() { ####################################### # Set TLS settings for an outbound in a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier of the outbound to modify -# server_name: string, optional, used to verify the hostname on the returned certificates -# insecure: boolean, accept any server certificate -# alpn: JSON value or null, optional supported application level protocols -# utls_fingerprint: string, optional uTLS fingerprint -# reality_public_key: string, optional Reality public key -# reality_short_id: string, optional Reality short ID +# server_name: string, used to verify the hostname on the returned certificates (optional) +# insecure: boolean, accept any server certificate (optional) +# alpn: string, JSON value, supported application level protocols (optional) +# utls_fingerprint: string, uTLS fingerprint (optional) +# reality_public_key: string, Reality public key (optional) +# reality_short_id: string, Reality short ID (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -825,7 +825,7 @@ sing_box_cm_set_tls_for_outbound() { ####################################### # Add a Direct outbound with a specific network interface to a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the outbound # interface: string, network interface to bind the outbound # domain_resolver: string, tag of the domain resolver to be used for this outbound (optional) @@ -857,9 +857,9 @@ sing_box_cm_add_interface_outbound() { ####################################### # Add a raw outbound JSON object to the outbounds section of a sing-box configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the outbound -# raw_outbound: JSON object, the raw outbound configuration to add +# raw_outbound: string, JSON object, the raw outbound configuration to add # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -881,14 +881,14 @@ sing_box_cm_add_raw_outbound() { ####################################### # Add a URLTest outbound to the outbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the URLTest outbound -# outbounds: JSON array of outbound tags to test -# url: URL to probe (optional) -# interval: test interval (e.g., "10s") (optional) -# tolerance: max latency difference tolerated (optional) -# idle_timeout: idle timeout duration (optional) -# interrupt_exist_connections: flag to interrupt existing connections ("true"/"false") (optional) +# outbounds: string, JSON array of outbound tags to test +# url: string, URL to probe (optional) +# interval: string, test interval (e.g., "10s") (optional) +# tolerance: string or integer, max latency difference tolerated (optional) +# idle_timeout: string or integer, idle timeout duration (optional) +# interrupt_exist_connections: boolean, flag to interrupt existing connections ("true"/"false") (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -929,11 +929,11 @@ sing_box_cm_add_urltest_outbound() { ####################################### # Add a Selector outbound to the outbounds section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the Selector outbound -# outbounds: JSON array of outbound tags to choose from -# default: default outbound tag if none selected (optional) -# interrupt_exist_connections: flag to interrupt existing connections ("true"/"false") (optional) +# outbounds: string (JSON), array of outbound tags to choose from +# default: string, default outbound tag if none selected +# interrupt_exist_connections: boolean, flag to interrupt existing connections ("true"/"false") (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -965,11 +965,11 @@ sing_box_cm_add_selector_outbound() { ####################################### # Configure the route section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # final: string, final outbound tag for unmatched traffic # auto_detect_interface: boolean, enable or disable automatic interface detection # default_domain_resolver: string, default DNS resolver for domain-based routing -# default_interface: string, default network interface to use when auto detection is disabled +# default_interface: string, default network interface to use when auto detection is disabled (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1001,7 +1001,7 @@ sing_box_cm_configure_route() { ####################################### # Add a routing rule to the route section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the route rule # inbound: string, inbound tag to match # outbound: string, outbound tag to route matched traffic to @@ -1032,10 +1032,10 @@ sing_box_cm_add_route_rule() { ####################################### # Patch a routing rule in the route section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier of the route rule to patch # key: string, the key in the rule to update or add -# value: JSON value to assign to the key +# value: string (JSON), value to assign to the key # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1071,9 +1071,9 @@ sing_box_cm_patch_route_rule() { ####################################### # Add a reject rule to the route section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # key: string, the key to set for the reject rule -# value: JSON value to assign to the key +# value: string (JSON), value to assign to the key # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1098,9 +1098,9 @@ sing_box_cm_add_reject_route_rule() { ####################################### # Add a hijack-dns rule to the route section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # key: string, the key to set for the hijack-dns rule -# value: JSON value to assign to the key +# value: string (JSON), value to assign to the key # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1125,7 +1125,7 @@ sing_box_cm_add_hijack_dns_route_rule() { ####################################### # Add a route-options rule to the route section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the route-options rule # Outputs: # Writes updated JSON configuration to stdout @@ -1148,9 +1148,9 @@ sing_box_cm_add_options_route_rule() { ####################################### # Add a sniff rule to the route section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # key: string, the key to set for the sniff rule -# value: JSON value to assign to the key +# value: string (JSON), value to assign to the key # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1176,7 +1176,7 @@ sing_box_cm_sniff_route_rule() { ####################################### # Add an inline ruleset to the route.rule_set section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the inline ruleset # Outputs: # Writes updated JSON configuration to stdout @@ -1198,10 +1198,10 @@ sing_box_cm_add_inline_ruleset() { ####################################### # Add or update a rule in an inline ruleset within the route.rule_set section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier of the inline ruleset # key: string, the key in the ruleset to update or add -# value: JSON value to assign to the key +# value: string (JSON), value to assign to the key # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1238,7 +1238,7 @@ sing_box_cm_add_inline_ruleset_rule() { ####################################### # Add a local ruleset to the route.rule_set section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the local ruleset # format: string, format of the local ruleset ("source" or "binary") # path: string, file path to the local ruleset @@ -1269,12 +1269,12 @@ sing_box_cm_add_local_ruleset() { ####################################### # Add a remote ruleset to the route.rule_set section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # tag: string, identifier for the remote ruleset # format: string, format of the remote ruleset ("source" or "binary") # url: string, URL to download the ruleset from -# download_detour: string, optional detour tag for downloading -# update_interval: string, optional update interval for the ruleset +# download_detour: string, detour tag for downloading (optional) +# update_interval: string, update interval for the ruleset (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1310,7 +1310,7 @@ sing_box_cm_add_remote_ruleset() { ####################################### # Configure the experimental cache_file section of a sing-box JSON configuration. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # enabled: boolean, enable or disable file caching # path: string, file path for cache storage # store_fakeip: boolean, whether to store fake IPs in the cache @@ -1339,10 +1339,10 @@ sing_box_cm_configure_cache_file() { ####################################### # Configure the experimental clash_api section of a sing-box JSON configuration. # Arguments: -# config: string, JSON configuration +# config: string (JSON), sing-box configuration to modify # external_controller: string, API listening address; Clash API will be disabled if empty -# external_ui: string, Optional path to static web resources to serve at http://{{external-controller}}/ui -# secret: string, Optional secret for the RESTful API Authenticate by specifying HTTP header +# external_ui: string, path to static web resources to serve at http://{{external-controller}}/ui (optional) +# secret: string, secret for the RESTful API Authenticate by specifying HTTP header (optional) # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -1368,7 +1368,7 @@ sing_box_cm_configure_clash_api() { ####################################### # Save a sing-box JSON configuration to a file, removing service-specific tags. # Arguments: -# config: JSON configuration (string) +# config: string (JSON), sing-box configuration to modify # file_path: string, path to save the configuration file # Outputs: # Writes the cleaned JSON configuration to the specified file From 0a4ed367bccfd2b7eb6b1f6fb0f87c61346d08b8 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Wed, 26 Nov 2025 21:01:33 +0500 Subject: [PATCH 2/5] refactor: add url_get_scheme and simplify url_get_host/url_get_port using parameter expansion --- podkop/files/usr/lib/helpers.sh | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/podkop/files/usr/lib/helpers.sh b/podkop/files/usr/lib/helpers.sh index 93dcf6d..c25edb8 100644 --- a/podkop/files/usr/lib/helpers.sh +++ b/podkop/files/usr/lib/helpers.sh @@ -125,6 +125,12 @@ url_decode() { printf '%b' "$(echo "$encoded" | sed 's/+/ /g; s/%/\\x/g')" } +# Returns the scheme (protocol) part of a URL +url_get_scheme() { + local url="$1" + echo "${url%%://*}" +} + # Extracts the userinfo (username[:password]) part from a URL url_get_userinfo() { local url="$1" @@ -134,13 +140,23 @@ url_get_userinfo() { # Extracts the host part from a URL url_get_host() { local url="$1" - echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#\([:/].*\|$\)##p' + + url="${url#*://}" + url="${url#*@}" + url="${url%%[/?#]*}" + + echo "${url%%:*}" } # Extracts the port number from a URL url_get_port() { local url="$1" - echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#^[^/]*:\([0-9][0-9]*\).*#\1#p' + + url="${url#*://}" + url="${url#*@}" + url="${url%%[/?#]*}" + + [[ "$url" == *:* ]] && echo "${url#*:}" || echo "" } # Extracts the path from a URL (without query or fragment; returns "/" if empty) From 82345047cb9d822fb9bb5cf643d7a76b0f53d505 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Wed, 26 Nov 2025 21:04:46 +0500 Subject: [PATCH 3/5] feat: Add Hysteria2 outbound support --- .../files/usr/lib/sing_box_config_facade.sh | 41 ++++++++++++- .../files/usr/lib/sing_box_config_manager.sh | 61 +++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/podkop/files/usr/lib/sing_box_config_facade.sh b/podkop/files/usr/lib/sing_box_config_facade.sh index ffbe854..71d2858 100644 --- a/podkop/files/usr/lib/sing_box_config_facade.sh +++ b/podkop/files/usr/lib/sing_box_config_facade.sh @@ -64,7 +64,8 @@ sing_box_cf_add_proxy_outbound() { url=$(url_decode "$url") url=$(url_strip_fragment "$url") - local scheme="${url%%://*}" + local scheme + scheme="$(url_get_scheme "$url")" case "$scheme" in socks4 | socks4a | socks5) local tag host port version userinfo username password udp_over_tcp @@ -146,6 +147,21 @@ sing_box_cf_add_proxy_outbound() { config=$(_add_outbound_security "$config" "$tag" "$url") config=$(_add_outbound_transport "$config" "$tag" "$url") ;; + hysteria2 | hy2) + local tag host port password obfuscator_type obfuscator_password upload_mbps download_mbps + tag=$(get_outbound_tag_by_section "$section") + host=$(url_get_host "$url") + port="$(url_get_port "$url")" + password=$(url_get_userinfo "$url") + obfuscator_type=$(url_get_query_param "$url" "obfs") + obfuscator_password=$(url_get_query_param "$url" "obfs-password") + upload_mbps=$(url_get_query_param "$url" "upmbps") + download_mbps=$(url_get_query_param "$url" "downmbps") + + config=$(sing_box_cm_add_hysteria2_outbound "$config" "$tag" "$host" "$port" "$password" "$obfuscator_type" \ + "$obfuscator_password" "$upload_mbps" "$download_mbps") + config=$(_add_outbound_security "$config" "$tag" "$url") + ;; *) log "Unsupported proxy $scheme type. Aborted." "fatal" exit 1 @@ -160,13 +176,20 @@ _add_outbound_security() { local outbound_tag="$2" local url="$3" - local security + local security scheme security=$(url_get_query_param "$url" "security") + if [ -z "$security" ]; then + scheme="$(url_get_scheme "$url")" + if [ "$scheme" = "hysteria2" ] || [ "$scheme" = "hy2" ]; then + security="tls" + fi + fi + case "$security" in tls | reality) local sni insecure alpn fingerprint public_key short_id sni=$(url_get_query_param "$url" "sni") - insecure=$(url_get_query_param "$url" "allowInsecure") + insecure=$(_get_insecure_query_param_from_url "$url") alpn=$(comma_string_to_json_array "$(url_get_query_param "$url" "alpn")") fingerprint=$(url_get_query_param "$url" "fp") public_key=$(url_get_query_param "$url" "pbk") @@ -193,6 +216,18 @@ _add_outbound_security() { echo "$config" } +_get_insecure_query_param_from_url() { + local url="$1" + + local insecure + insecure=$(url_get_query_param "$url" "allowInsecure") + if [ -z "$insecure" ]; then + insecure=$(url_get_query_param "$url" "insecure") + fi + + echo "$insecure" +} + _add_outbound_transport() { local config="$1" local outbound_tag="$2" diff --git a/podkop/files/usr/lib/sing_box_config_manager.sh b/podkop/files/usr/lib/sing_box_config_manager.sh index c6d8ef3..9a0af62 100644 --- a/podkop/files/usr/lib/sing_box_config_manager.sh +++ b/podkop/files/usr/lib/sing_box_config_manager.sh @@ -661,6 +661,67 @@ sing_box_cm_add_trojan_outbound() { )]' } +####################################### +# Add a Hysteria2 outbound to the outbounds section of a sing-box JSON configuration. +# Arguments: +# config: string (JSON), sing-box configuration to modify +# tag: string, identifier for the outbound +# server_address: string, IP address or hostname of the Hysteria2 server +# server_port: integer, port of the Hysteria2 server +# password: string, password for authentication +# obfuscator_type: string, obfuscation type (optional) +# obfuscator_password: string, obfuscation password (optional) +# upload_mbps: integer, upload bandwidth limit in Mbps (optional) +# download_mbps: integer, download bandwidth limit in Mbps (optional) +# network: string, network type (e.g., "udp") (optional) +# Outputs: +# Writes updated JSON configuration to stdout +# Example: +# CONFIG=$(sing_box_cm_add_hysteria2_outbound "$CONFIG" "hysteria2-out" "example.com" 443 "supersecret" \ +# "salamander" "obfs-pass" "50" "200" "udp") +####################################### +sing_box_cm_add_hysteria2_outbound() { + local config="$1" + local tag="$2" + local server_address="$3" + local server_port="$4" + local password="$5" + local obfuscator_type="$6" + local obfuscator_password="$7" + local upload_mbps="$8" + local download_mbps="$9" + local network="${10}" + + echo "$config" | jq \ + --arg tag "$tag" \ + --arg server_address "$server_address" \ + --arg server_port "$server_port" \ + --arg password "$password" \ + --arg obfuscator_type "$obfuscator_type" \ + --arg obfuscator_password "$obfuscator_password" \ + --arg upload_mbps "$upload_mbps" \ + --arg download_mbps "$download_mbps" \ + --arg network "$network" \ + '.outbounds += [( + { + type: "hysteria2", + tag: $tag, + server: $server_address, + server_port: ($server_port | tonumber), + password: $password + } + + (if $obfuscator_type != "" and $obfuscator_password != "" then { + obfs: { + type: $obfuscator_type, + password: $obfuscator_password + } + } else {} end) + + (if $upload_mbps != "" then {up_mbps: ($upload_mbps | tonumber)} else {} end) + + (if $download_mbps != "" then {down_mbps: ($download_mbps | tonumber)} else {} end) + + (if $network != "" then {network: $network} else {} end) + )]' +} + ####################################### # Set gRPC transport settings for an outbound in a sing-box JSON configuration. # Arguments: From 622e0923178ea7cc79702f07c9bfd6d7ca0e537a Mon Sep 17 00:00:00 2001 From: divocat Date: Sun, 30 Nov 2025 18:35:06 +0200 Subject: [PATCH 4/5] feat: add hy2 validator --- fe-app-podkop/locales/calls.json | 120 ++++++++++++++---- fe-app-podkop/locales/podkop.pot | 88 +++++++++---- fe-app-podkop/locales/podkop.ru.po | 40 +++++- .../tests/validateHysteriaUrl.test.js | 74 +++++++++++ .../src/validators/validateHysteriaUrl.ts | 115 +++++++++++++++++ .../src/validators/validateProxyUrl.ts | 8 ++ .../luci-static/resources/view/podkop/main.js | 87 +++++++++++++ .../resources/view/podkop/section.js | 2 +- luci-app-podkop/po/ru/podkop.po | 40 +++++- luci-app-podkop/po/templates/podkop.pot | 88 +++++++++---- 10 files changed, 581 insertions(+), 81 deletions(-) create mode 100644 fe-app-podkop/src/validators/tests/validateHysteriaUrl.test.js create mode 100644 fe-app-podkop/src/validators/validateHysteriaUrl.ts diff --git a/fe-app-podkop/locales/calls.json b/fe-app-podkop/locales/calls.json index f1bb11d..f80bfea 100644 --- a/fe-app-podkop/locales/calls.json +++ b/fe-app-podkop/locales/calls.json @@ -3,35 +3,35 @@ "call": "✔ Enabled", "key": "✔ Enabled", "places": [ - "src/podkop/tabs/dashboard/initController.ts:342" + "src/podkop/tabs/dashboard/initController.ts:345" ] }, { "call": "✔ Running", "key": "✔ Running", "places": [ - "src/podkop/tabs/dashboard/initController.ts:353" + "src/podkop/tabs/dashboard/initController.ts:356" ] }, { "call": "✘ Disabled", "key": "✘ Disabled", "places": [ - "src/podkop/tabs/dashboard/initController.ts:343" + "src/podkop/tabs/dashboard/initController.ts:346" ] }, { "call": "✘ Stopped", "key": "✘ Stopped", "places": [ - "src/podkop/tabs/dashboard/initController.ts:354" + "src/podkop/tabs/dashboard/initController.ts:357" ] }, { "call": "Active Connections", "key": "Active Connections", "places": [ - "src/podkop/tabs/dashboard/initController.ts:304" + "src/podkop/tabs/dashboard/initController.ts:307" ] }, { @@ -379,8 +379,8 @@ "call": "Downlink", "key": "Downlink", "places": [ - "src/podkop/tabs/dashboard/initController.ts:238", - "src/podkop/tabs/dashboard/initController.ts:272" + "src/podkop/tabs/dashboard/initController.ts:241", + "src/podkop/tabs/dashboard/initController.ts:275" ] }, { @@ -637,6 +637,83 @@ "src/validators/validateSubnet.ts:11" ] }, + { + "call": "Invalid HY2 URL: insecure must be 0 or 1", + "key": "Invalid HY2 URL: insecure must be 0 or 1", + "places": [ + "src/validators/validateHysteriaUrl.ts:73" + ] + }, + { + "call": "Invalid HY2 URL: invalid port number", + "key": "Invalid HY2 URL: invalid port number", + "places": [ + "src/validators/validateHysteriaUrl.ts:62" + ] + }, + { + "call": "Invalid HY2 URL: missing credentials/server", + "key": "Invalid HY2 URL: missing credentials/server", + "places": [ + "src/validators/validateHysteriaUrl.ts:32" + ] + }, + { + "call": "Invalid HY2 URL: missing host", + "key": "Invalid HY2 URL: missing host", + "places": [ + "src/validators/validateHysteriaUrl.ts:49" + ] + }, + { + "call": "Invalid HY2 URL: missing host & port", + "key": "Invalid HY2 URL: missing host & port", + "places": [ + "src/validators/validateHysteriaUrl.ts:43" + ] + }, + { + "call": "Invalid HY2 URL: missing password", + "key": "Invalid HY2 URL: missing password", + "places": [ + "src/validators/validateHysteriaUrl.ts:38" + ] + }, + { + "call": "Invalid HY2 URL: missing port", + "key": "Invalid HY2 URL: missing port", + "places": [ + "src/validators/validateHysteriaUrl.ts:53" + ] + }, + { + "call": "Invalid HY2 URL: must not contain spaces", + "key": "Invalid HY2 URL: must not contain spaces", + "places": [ + "src/validators/validateHysteriaUrl.ts:19" + ] + }, + { + "call": "Invalid HY2 URL: must start with hysteria2:// or hy2://", + "key": "Invalid HY2 URL: must start with hysteria2:// or hy2://", + "places": [ + "src/validators/validateHysteriaUrl.ts:12" + ] + }, + { + "call": "Invalid HY2 URL: parsing failed", + "key": "Invalid HY2 URL: parsing failed", + "places": [ + "src/validators/validateHysteriaUrl.ts:103" + ] + }, + { + "call": "Invalid HY2 URL: unsupported obfs type", + "key": "Invalid HY2 URL: unsupported obfs type", + "places": [ + "src/validators/validateHysteriaUrl.ts:82" + ] + }, { "call": "Invalid IP address", "key": "Invalid IP address", @@ -880,7 +957,7 @@ "call": "Memory Usage", "key": "Memory Usage", "places": [ - "src/podkop/tabs/dashboard/initController.ts:308" + "src/podkop/tabs/dashboard/initController.ts:311" ] }, { @@ -1023,7 +1100,7 @@ "call": "Podkop", "key": "Podkop", "places": [ - "src/podkop/tabs/dashboard/initController.ts:340" + "src/podkop/tabs/dashboard/initController.ts:343" ] }, { @@ -1290,7 +1367,7 @@ "call": "Services info", "key": "Services info", "places": [ - "src/podkop/tabs/dashboard/initController.ts:337" + "src/podkop/tabs/dashboard/initController.ts:340" ] }, { @@ -1312,7 +1389,7 @@ "call": "Sing-box", "key": "Sing-box", "places": [ - "src/podkop/tabs/dashboard/initController.ts:351" + "src/podkop/tabs/dashboard/initController.ts:354" ] }, { @@ -1425,7 +1502,7 @@ "call": "System info", "key": "System info", "places": [ - "src/podkop/tabs/dashboard/initController.ts:301" + "src/podkop/tabs/dashboard/initController.ts:304" ] }, { @@ -1453,13 +1530,7 @@ "call": "Text List", "key": "Text List", "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368" - ] - }, - { - "call": "Text List (comma/space/newline separated)", - "key": "Text List (comma/space/newline separated)", - "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368", "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448" ] }, @@ -1502,14 +1573,14 @@ "call": "Traffic", "key": "Traffic", "places": [ - "src/podkop/tabs/dashboard/initController.ts:235" + "src/podkop/tabs/dashboard/initController.ts:238" ] }, { "call": "Traffic Total", "key": "Traffic Total", "places": [ - "src/podkop/tabs/dashboard/initController.ts:265" + "src/podkop/tabs/dashboard/initController.ts:268" ] }, { @@ -1572,15 +1643,15 @@ "call": "Uplink", "key": "Uplink", "places": [ - "src/podkop/tabs/dashboard/initController.ts:237", - "src/podkop/tabs/dashboard/initController.ts:268" + "src/podkop/tabs/dashboard/initController.ts:240", + "src/podkop/tabs/dashboard/initController.ts:271" ] }, { "call": "URL must start with vless://, ss://, trojan://, or socks4/5://", "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", "places": [ - "src/validators/validateProxyUrl.ts:29" + "src/validators/validateProxyUrl.ts:37" ] }, { @@ -1675,6 +1746,7 @@ "src/validators/validateDns.ts:18", "src/validators/validateDomain.ts:13", "src/validators/validateDomain.ts:30", + "src/validators/validateHysteriaUrl.ts:101", "src/validators/validateIp.ts:8", "src/validators/validateOutboundJson.ts:7", "src/validators/validatePath.ts:16", diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot index 0bf57c8..00c0264 100644 --- a/fe-app-podkop/locales/podkop.pot +++ b/fe-app-podkop/locales/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 14:19+0200\n" -"PO-Revision-Date: 2025-11-06 14:19+0200\n" +"POT-Creation-Date: 2025-11-30 16:34+0200\n" +"PO-Revision-Date: 2025-11-30 16:34+0200\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -16,23 +16,23 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/podkop/tabs/dashboard/initController.ts:342 +#: src/podkop/tabs/dashboard/initController.ts:345 msgid "✔ Enabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:353 +#: src/podkop/tabs/dashboard/initController.ts:356 msgid "✔ Running" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:343 +#: src/podkop/tabs/dashboard/initController.ts:346 msgid "✘ Disabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:354 +#: src/podkop/tabs/dashboard/initController.ts:357 msgid "✘ Stopped" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:304 +#: src/podkop/tabs/dashboard/initController.ts:307 msgid "Active Connections" msgstr "" @@ -236,8 +236,8 @@ msgstr "" msgid "Dont Touch My DHCP!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:238 -#: src/podkop/tabs/dashboard/initController.ts:272 +#: src/podkop/tabs/dashboard/initController.ts:241 +#: src/podkop/tabs/dashboard/initController.ts:275 msgid "Downlink" msgstr "" @@ -390,6 +390,50 @@ msgstr "" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "" +#: src/validators/validateHysteriaUrl.ts:73 +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:62 +msgid "Invalid HY2 URL: invalid port number" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:32 +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:49 +msgid "Invalid HY2 URL: missing host" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:43 +msgid "Invalid HY2 URL: missing host & port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:38 +msgid "Invalid HY2 URL: missing password" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:53 +msgid "Invalid HY2 URL: missing port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:19 +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:12 +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:103 +msgid "Invalid HY2 URL: parsing failed" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:82 +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "" + #: src/validators/validateIp.ts:11 msgid "Invalid IP address" msgstr "" @@ -527,7 +571,7 @@ msgstr "" msgid "Main DNS" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:308 +#: src/podkop/tabs/dashboard/initController.ts:311 msgid "Memory Usage" msgstr "" @@ -613,7 +657,7 @@ msgstr "" msgid "Pending" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:340 +#: src/podkop/tabs/dashboard/initController.ts:343 msgid "Podkop" msgstr "" @@ -766,7 +810,7 @@ msgstr "" msgid "Select the WAN interfaces to be monitored" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:337 +#: src/podkop/tabs/dashboard/initController.ts:340 msgid "Services info" msgstr "" @@ -779,7 +823,7 @@ msgstr "" msgid "Show sing-box config" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:351 +#: src/podkop/tabs/dashboard/initController.ts:354 msgid "Sing-box" msgstr "" @@ -844,7 +888,7 @@ msgstr "" msgid "Successfully copied!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:301 +#: src/podkop/tabs/dashboard/initController.ts:304 msgid "System info" msgstr "" @@ -861,11 +905,8 @@ msgid "Test latency" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 -msgid "Text List" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448 -msgid "Text List (comma/space/newline separated)" +msgid "Text List" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 @@ -888,11 +929,11 @@ msgstr "" msgid "Time in seconds for DNS record caching (default: 60)" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:235 +#: src/podkop/tabs/dashboard/initController.ts:238 msgid "Traffic" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:265 +#: src/podkop/tabs/dashboard/initController.ts:268 msgid "Traffic Total" msgstr "" @@ -931,12 +972,12 @@ msgstr "" msgid "Unknown error" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:237 -#: src/podkop/tabs/dashboard/initController.ts:268 +#: src/podkop/tabs/dashboard/initController.ts:240 +#: src/podkop/tabs/dashboard/initController.ts:271 msgid "Uplink" msgstr "" -#: src/validators/validateProxyUrl.ts:29 +#: src/validators/validateProxyUrl.ts:37 msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" msgstr "" @@ -992,6 +1033,7 @@ msgstr "" #: src/validators/validateDns.ts:18 #: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:30 +#: src/validators/validateHysteriaUrl.ts:101 #: src/validators/validateIp.ts:8 #: src/validators/validateOutboundJson.ts:7 #: src/validators/validatePath.ts:16 diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po index 25971ad..60c47a0 100644 --- a/fe-app-podkop/locales/podkop.ru.po +++ b/fe-app-podkop/locales/podkop.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 16:19+0200\n" -"PO-Revision-Date: 2025-11-06 16:19+0200\n" +"POT-Creation-Date: 2025-11-30 18:34+0200\n" +"PO-Revision-Date: 2025-11-30 18:34+0200\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -281,6 +281,39 @@ msgstr "Неверный домен" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "Неверный URL Hysteria2: параметр insecure должен быть 0 или 1" + +msgid "Invalid HY2 URL: invalid port number" +msgstr "Неверный URL Hysteria2: неверный номер порта" + +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "Неверный URL Hysteria2: отсутствуют учетные данные/сервер" + +msgid "Invalid HY2 URL: missing host" +msgstr "Неверный URL Hysteria2: отсутствует хост" + +msgid "Invalid HY2 URL: missing host & port" +msgstr "Неверный URL Hysteria2: отсутствуют хост и порт" + +msgid "Invalid HY2 URL: missing password" +msgstr "Неверный URL Hysteria2: отсутствует пароль" + +msgid "Invalid HY2 URL: missing port" +msgstr "Неверный URL Hysteria2: отсутствует порт" + +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "Неверный URL Hysteria2: не должен содержать пробелов" + +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://" + +msgid "Invalid HY2 URL: parsing failed" +msgstr "Неверный URL Hysteria2: ошибка разбора" + +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs" + msgid "Invalid IP address" msgstr "Неверный IP-адрес" @@ -626,9 +659,6 @@ msgstr "Тестирование задержки" msgid "Text List" msgstr "Текстовый список" -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (через запятую, пробел или новую строку)" - msgid "The DNS server used to look up the IP address of an upstream DNS server" msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" diff --git a/fe-app-podkop/src/validators/tests/validateHysteriaUrl.test.js b/fe-app-podkop/src/validators/tests/validateHysteriaUrl.test.js new file mode 100644 index 0000000..b759fd2 --- /dev/null +++ b/fe-app-podkop/src/validators/tests/validateHysteriaUrl.test.js @@ -0,0 +1,74 @@ +import { describe, it, expect } from 'vitest'; +import { validateHysteria2Url } from '../validateHysteriaUrl.js'; + +const validUrls = [ + // Basic password-only + ['password basic', 'hysteria2://pass@example.com:443/#hy2-basic'], + + // insecure=1 + [ + 'insecure allowed', + 'hysteria2://pass@example.com:443/?insecure=1#hy2-insecure', + ], + + // SNI + ['SNI param', 'hysteria2://pass@example.com:443/?sni=google.com#hy2-sni'], + + // Obfuscation + [ + 'Obfs + password', + 'hysteria2://mypassword@1.1.1.1:8443/?obfs=salamander&obfs-password=abc123#hy2-obfs', + ], + + // All params + [ + 'All options combined', + 'hysteria2://pw@8.8.8.8:8443/?sni=example.com&obfs=salamander&obfs-password=hello&insecure=1#hy2-full', + ], + + // Explicit obfs=none (valid) + ['obfs none = ok', 'hysteria2://pw@example.com:443/?obfs=none#hy2-none'], +]; + +const invalidUrls = [ + ['No prefix', 'pw@example.com:443'], + ['Missing password', 'hysteria2://@example.com:443/'], + ['Missing host', 'hysteria2://pw@:443/'], + ['Missing port', 'hysteria2://pw@example.com/'], + ['Non-numeric port', 'hysteria2://pw@example.com:port/'], + ['Port out of range', 'hysteria2://pw@example.com:99999/'], + + // Obfuscation errors + ['Unknown obfs type', 'hysteria2://pw@example.com:443/?obfs=weird'], + [ + 'obfs without obfs-password', + 'hysteria2://pw@example.com:443/?obfs=salamander', + ], + + // insecure only accepts 0/1 + ['invalid insecure', 'hysteria2://pw@example.com:443/?insecure=5'], + + // SNI empty + ['empty sni', 'hysteria2://pw@example.com:443/?sni='], +]; + +describe('validateHysteria2Url', () => { + describe.each(validUrls)('Valid HY2 URL: %s', (_desc, url) => { + it(`returns valid=true for "${url}"`, () => { + const res = validateHysteria2Url(url); + expect(res.valid).toBe(true); + }); + }); + + describe.each(invalidUrls)('Invalid HY2 URL: %s', (_desc, url) => { + it(`returns valid=false for "${url}"`, () => { + const res = validateHysteria2Url(url); + expect(res.valid).toBe(false); + }); + }); + + it('detects invalid port range', () => { + const res = validateHysteria2Url('hysteria2://pw@example.com:70000/'); + expect(res.valid).toBe(false); + }); +}); diff --git a/fe-app-podkop/src/validators/validateHysteriaUrl.ts b/fe-app-podkop/src/validators/validateHysteriaUrl.ts new file mode 100644 index 0000000..53c7530 --- /dev/null +++ b/fe-app-podkop/src/validators/validateHysteriaUrl.ts @@ -0,0 +1,115 @@ +import { ValidationResult } from './types'; +import { parseQueryString } from '../helpers/parseQueryString'; + +export function validateHysteria2Url(url: string): ValidationResult { + try { + const isHY2 = url.startsWith('hysteria2://'); + const isHY2Short = url.startsWith('hy2://'); + + if (!isHY2 && !isHY2Short) { + return { + valid: false, + message: _('Invalid HY2 URL: must start with hysteria2:// or hy2://'), + }; + } + + if (/\s/.test(url)) { + return { + valid: false, + message: _('Invalid HY2 URL: must not contain spaces'), + }; + } + + const prefix = isHY2 ? 'hysteria2://' : 'hy2://'; + const body = url.slice(prefix.length); + + const [mainPart] = body.split('#'); + const [authHostPort, queryString] = mainPart.split('?'); + + if (!authHostPort) + return { + valid: false, + message: _('Invalid HY2 URL: missing credentials/server'), + }; + + const [passwordPart, hostPortPart] = authHostPort.split('@'); + + if (!passwordPart) + return { valid: false, message: _('Invalid HY2 URL: missing password') }; + + if (!hostPortPart) + return { + valid: false, + message: _('Invalid HY2 URL: missing host & port'), + }; + + const [host, port] = hostPortPart.split(':'); + + if (!host) { + return { valid: false, message: _('Invalid HY2 URL: missing host') }; + } + + if (!port) { + return { valid: false, message: _('Invalid HY2 URL: missing port') }; + } + + const cleanedPort = port.replace('/', ''); + const portNum = Number(cleanedPort); + + if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { + return { + valid: false, + message: _('Invalid HY2 URL: invalid port number'), + }; + } + + if (queryString) { + const params = parseQueryString(queryString); + const paramsKeys = Object.keys(params); + + if ( + paramsKeys.includes('insecure') && + !['0', '1'].includes(params.insecure) + ) { + return { + valid: false, + message: _('Invalid HY2 URL: insecure must be 0 or 1'), + }; + } + + const validObfsTypes = ['none', 'salamander']; + + if ( + paramsKeys.includes('obfs') && + !validObfsTypes.includes(params.obfs) + ) { + return { + valid: false, + message: _('Invalid HY2 URL: unsupported obfs type'), + }; + } + + if ( + paramsKeys.includes('obfs') && + params.obfs !== 'none' && + !params['obfs-password'] + ) { + return { + valid: false, + message: 'Invalid HY2 URL: obfs-password required when obfs is set', + }; + } + + if (paramsKeys.includes('sni') && !params.sni) { + return { + valid: false, + message: 'Invalid HY2 URL: sni cannot be empty', + }; + } + } + + return { valid: true, message: _('Valid') }; + } catch (_e) { + return { valid: false, message: _('Invalid HY2 URL: parsing failed') }; + } +} diff --git a/fe-app-podkop/src/validators/validateProxyUrl.ts b/fe-app-podkop/src/validators/validateProxyUrl.ts index 912bbca..24b0c19 100644 --- a/fe-app-podkop/src/validators/validateProxyUrl.ts +++ b/fe-app-podkop/src/validators/validateProxyUrl.ts @@ -3,6 +3,7 @@ import { validateShadowsocksUrl } from './validateShadowsocksUrl'; import { validateVlessUrl } from './validateVlessUrl'; import { validateTrojanUrl } from './validateTrojanUrl'; import { validateSocksUrl } from './validateSocksUrl'; +import { validateHysteria2Url } from './validateHysteriaUrl'; // TODO refactor current validation and add tests export function validateProxyUrl(url: string): ValidationResult { @@ -24,6 +25,13 @@ export function validateProxyUrl(url: string): ValidationResult { return validateSocksUrl(trimmedUrl); } + if ( + trimmedUrl.startsWith('hysteria2://') || + trimmedUrl.startsWith('hy2://') + ) { + return validateHysteria2Url(trimmedUrl); + } + return { valid: false, message: _( diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 638867e..986070c 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -448,6 +448,90 @@ function validateSocksUrl(url) { return { valid: true, message: _("Valid") }; } +// src/validators/validateHysteriaUrl.ts +function validateHysteria2Url(url) { + try { + const isHY2 = url.startsWith("hysteria2://"); + const isHY2Short = url.startsWith("hy2://"); + if (!isHY2 && !isHY2Short) { + return { + valid: false, + message: _("Invalid HY2 URL: must start with hysteria2:// or hy2://") + }; + } + if (/\s/.test(url)) { + return { + valid: false, + message: _("Invalid HY2 URL: must not contain spaces") + }; + } + const prefix = isHY2 ? "hysteria2://" : "hy2://"; + const body = url.slice(prefix.length); + const [mainPart] = body.split("#"); + const [authHostPort, queryString] = mainPart.split("?"); + if (!authHostPort) + return { + valid: false, + message: _("Invalid HY2 URL: missing credentials/server") + }; + const [passwordPart, hostPortPart] = authHostPort.split("@"); + if (!passwordPart) + return { valid: false, message: _("Invalid HY2 URL: missing password") }; + if (!hostPortPart) + return { + valid: false, + message: _("Invalid HY2 URL: missing host & port") + }; + const [host, port] = hostPortPart.split(":"); + if (!host) { + return { valid: false, message: _("Invalid HY2 URL: missing host") }; + } + if (!port) { + return { valid: false, message: _("Invalid HY2 URL: missing port") }; + } + const cleanedPort = port.replace("/", ""); + const portNum = Number(cleanedPort); + if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { + return { + valid: false, + message: _("Invalid HY2 URL: invalid port number") + }; + } + if (queryString) { + const params = parseQueryString(queryString); + const paramsKeys = Object.keys(params); + if (paramsKeys.includes("insecure") && !["0", "1"].includes(params.insecure)) { + return { + valid: false, + message: _("Invalid HY2 URL: insecure must be 0 or 1") + }; + } + const validObfsTypes = ["none", "salamander"]; + if (paramsKeys.includes("obfs") && !validObfsTypes.includes(params.obfs)) { + return { + valid: false, + message: _("Invalid HY2 URL: unsupported obfs type") + }; + } + if (paramsKeys.includes("obfs") && params.obfs !== "none" && !params["obfs-password"]) { + return { + valid: false, + message: "Invalid HY2 URL: obfs-password required when obfs is set" + }; + } + if (paramsKeys.includes("sni") && !params.sni) { + return { + valid: false, + message: "Invalid HY2 URL: sni cannot be empty" + }; + } + } + return { valid: true, message: _("Valid") }; + } catch (_e) { + return { valid: false, message: _("Invalid HY2 URL: parsing failed") }; + } +} + // src/validators/validateProxyUrl.ts function validateProxyUrl(url) { const trimmedUrl = url.trim(); @@ -463,6 +547,9 @@ function validateProxyUrl(url) { if (/^socks(4|4a|5):\/\//.test(trimmedUrl)) { return validateSocksUrl(trimmedUrl); } + if (trimmedUrl.startsWith("hysteria2://") || trimmedUrl.startsWith("hy2://")) { + return validateHysteria2Url(trimmedUrl); + } return { valid: false, message: _( diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js index 5d522fe..0529ac5 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js @@ -87,7 +87,7 @@ function createSectionContent(section) { _("URLTest Proxy Links"), ); o.depends("proxy_config_type", "urltest"); - o.placeholder = "vless://, ss://, trojan://, socks4/5:// links"; + o.placeholder = "vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links"; o.rmempty = false; o.validate = function (section_id, value) { // Optional diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 25971ad..60c47a0 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 16:19+0200\n" -"PO-Revision-Date: 2025-11-06 16:19+0200\n" +"POT-Creation-Date: 2025-11-30 18:34+0200\n" +"PO-Revision-Date: 2025-11-30 18:34+0200\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -281,6 +281,39 @@ msgstr "Неверный домен" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "Неверный URL Hysteria2: параметр insecure должен быть 0 или 1" + +msgid "Invalid HY2 URL: invalid port number" +msgstr "Неверный URL Hysteria2: неверный номер порта" + +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "Неверный URL Hysteria2: отсутствуют учетные данные/сервер" + +msgid "Invalid HY2 URL: missing host" +msgstr "Неверный URL Hysteria2: отсутствует хост" + +msgid "Invalid HY2 URL: missing host & port" +msgstr "Неверный URL Hysteria2: отсутствуют хост и порт" + +msgid "Invalid HY2 URL: missing password" +msgstr "Неверный URL Hysteria2: отсутствует пароль" + +msgid "Invalid HY2 URL: missing port" +msgstr "Неверный URL Hysteria2: отсутствует порт" + +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "Неверный URL Hysteria2: не должен содержать пробелов" + +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://" + +msgid "Invalid HY2 URL: parsing failed" +msgstr "Неверный URL Hysteria2: ошибка разбора" + +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs" + msgid "Invalid IP address" msgstr "Неверный IP-адрес" @@ -626,9 +659,6 @@ msgstr "Тестирование задержки" msgid "Text List" msgstr "Текстовый список" -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (через запятую, пробел или новую строку)" - msgid "The DNS server used to look up the IP address of an upstream DNS server" msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index 0bf57c8..00c0264 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-06 14:19+0200\n" -"PO-Revision-Date: 2025-11-06 14:19+0200\n" +"POT-Creation-Date: 2025-11-30 16:34+0200\n" +"PO-Revision-Date: 2025-11-30 16:34+0200\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -16,23 +16,23 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/podkop/tabs/dashboard/initController.ts:342 +#: src/podkop/tabs/dashboard/initController.ts:345 msgid "✔ Enabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:353 +#: src/podkop/tabs/dashboard/initController.ts:356 msgid "✔ Running" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:343 +#: src/podkop/tabs/dashboard/initController.ts:346 msgid "✘ Disabled" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:354 +#: src/podkop/tabs/dashboard/initController.ts:357 msgid "✘ Stopped" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:304 +#: src/podkop/tabs/dashboard/initController.ts:307 msgid "Active Connections" msgstr "" @@ -236,8 +236,8 @@ msgstr "" msgid "Dont Touch My DHCP!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:238 -#: src/podkop/tabs/dashboard/initController.ts:272 +#: src/podkop/tabs/dashboard/initController.ts:241 +#: src/podkop/tabs/dashboard/initController.ts:275 msgid "Downlink" msgstr "" @@ -390,6 +390,50 @@ msgstr "" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "" +#: src/validators/validateHysteriaUrl.ts:73 +msgid "Invalid HY2 URL: insecure must be 0 or 1" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:62 +msgid "Invalid HY2 URL: invalid port number" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:32 +msgid "Invalid HY2 URL: missing credentials/server" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:49 +msgid "Invalid HY2 URL: missing host" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:43 +msgid "Invalid HY2 URL: missing host & port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:38 +msgid "Invalid HY2 URL: missing password" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:53 +msgid "Invalid HY2 URL: missing port" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:19 +msgid "Invalid HY2 URL: must not contain spaces" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:12 +msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:103 +msgid "Invalid HY2 URL: parsing failed" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:82 +msgid "Invalid HY2 URL: unsupported obfs type" +msgstr "" + #: src/validators/validateIp.ts:11 msgid "Invalid IP address" msgstr "" @@ -527,7 +571,7 @@ msgstr "" msgid "Main DNS" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:308 +#: src/podkop/tabs/dashboard/initController.ts:311 msgid "Memory Usage" msgstr "" @@ -613,7 +657,7 @@ msgstr "" msgid "Pending" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:340 +#: src/podkop/tabs/dashboard/initController.ts:343 msgid "Podkop" msgstr "" @@ -766,7 +810,7 @@ msgstr "" msgid "Select the WAN interfaces to be monitored" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:337 +#: src/podkop/tabs/dashboard/initController.ts:340 msgid "Services info" msgstr "" @@ -779,7 +823,7 @@ msgstr "" msgid "Show sing-box config" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:351 +#: src/podkop/tabs/dashboard/initController.ts:354 msgid "Sing-box" msgstr "" @@ -844,7 +888,7 @@ msgstr "" msgid "Successfully copied!" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:301 +#: src/podkop/tabs/dashboard/initController.ts:304 msgid "System info" msgstr "" @@ -861,11 +905,8 @@ msgid "Test latency" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 -msgid "Text List" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:448 -msgid "Text List (comma/space/newline separated)" +msgid "Text List" msgstr "" #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 @@ -888,11 +929,11 @@ msgstr "" msgid "Time in seconds for DNS record caching (default: 60)" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:235 +#: src/podkop/tabs/dashboard/initController.ts:238 msgid "Traffic" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:265 +#: src/podkop/tabs/dashboard/initController.ts:268 msgid "Traffic Total" msgstr "" @@ -931,12 +972,12 @@ msgstr "" msgid "Unknown error" msgstr "" -#: src/podkop/tabs/dashboard/initController.ts:237 -#: src/podkop/tabs/dashboard/initController.ts:268 +#: src/podkop/tabs/dashboard/initController.ts:240 +#: src/podkop/tabs/dashboard/initController.ts:271 msgid "Uplink" msgstr "" -#: src/validators/validateProxyUrl.ts:29 +#: src/validators/validateProxyUrl.ts:37 msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" msgstr "" @@ -992,6 +1033,7 @@ msgstr "" #: src/validators/validateDns.ts:18 #: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:30 +#: src/validators/validateHysteriaUrl.ts:101 #: src/validators/validateIp.ts:8 #: src/validators/validateOutboundJson.ts:7 #: src/validators/validatePath.ts:16 From dd3e70153a474a6a8315878dbf0dd9f7dddbe2bd Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 1 Dec 2025 16:38:26 +0200 Subject: [PATCH 5/5] fix: correct small points --- fe-app-podkop/locales/calls.json | 26 ++++++++++++++----- fe-app-podkop/locales/podkop.pot | 22 +++++++++++----- fe-app-podkop/locales/podkop.ru.po | 14 +++++++--- .../src/validators/validateHysteriaUrl.ts | 6 +++-- .../src/validators/validateProxyUrl.ts | 2 +- .../luci-static/resources/view/podkop/main.js | 8 +++--- luci-app-podkop/po/ru/podkop.po | 14 +++++++--- luci-app-podkop/po/templates/podkop.pot | 22 +++++++++++----- 8 files changed, 80 insertions(+), 34 deletions(-) diff --git a/fe-app-podkop/locales/calls.json b/fe-app-podkop/locales/calls.json index f80bfea..970e80e 100644 --- a/fe-app-podkop/locales/calls.json +++ b/fe-app-podkop/locales/calls.json @@ -641,7 +641,7 @@ "call": "Invalid HY2 URL: insecure must be 0 or 1", "key": "Invalid HY2 URL: insecure must be 0 or 1", "places": [ - "src/validators/validateHysteriaUrl.ts:73" + "src/validators/validateHysteriaUrl.ts:76" ] }, { @@ -700,18 +700,32 @@ "src/validators/validateHysteriaUrl.ts:12" ] }, + { + "call": "Invalid HY2 URL: obfs-password required when obfs is set", + "key": "Invalid HY2 URL: obfs-password required when obfs is set", + "places": [ + "src/validators/validateHysteriaUrl.ts:99" + ] + }, { "call": "Invalid HY2 URL: parsing failed", "key": "Invalid HY2 URL: parsing failed", "places": [ - "src/validators/validateHysteriaUrl.ts:103" + "src/validators/validateHysteriaUrl.ts:113" + ] + }, + { + "call": "Invalid HY2 URL: sni cannot be empty", + "key": "Invalid HY2 URL: sni cannot be empty", + "places": [ + "src/validators/validateHysteriaUrl.ts:106" ] }, { "call": "Invalid HY2 URL: unsupported obfs type", "key": "Invalid HY2 URL: unsupported obfs type", "places": [ - "src/validators/validateHysteriaUrl.ts:82" + "src/validators/validateHysteriaUrl.ts:88" ] }, { @@ -1648,8 +1662,8 @@ ] }, { - "call": "URL must start with vless://, ss://, trojan://, or socks4/5://", - "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", + "call": "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://", + "key": "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://", "places": [ "src/validators/validateProxyUrl.ts:37" ] @@ -1746,7 +1760,7 @@ "src/validators/validateDns.ts:18", "src/validators/validateDomain.ts:13", "src/validators/validateDomain.ts:30", - "src/validators/validateHysteriaUrl.ts:101", + "src/validators/validateHysteriaUrl.ts:111", "src/validators/validateIp.ts:8", "src/validators/validateOutboundJson.ts:7", "src/validators/validatePath.ts:16", diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot index 00c0264..542bfbe 100644 --- a/fe-app-podkop/locales/podkop.pot +++ b/fe-app-podkop/locales/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-30 16:34+0200\n" -"PO-Revision-Date: 2025-11-30 16:34+0200\n" +"POT-Creation-Date: 2025-12-01 14:30+0200\n" +"PO-Revision-Date: 2025-12-01 14:30+0200\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -390,7 +390,7 @@ msgstr "" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "" -#: src/validators/validateHysteriaUrl.ts:73 +#: src/validators/validateHysteriaUrl.ts:76 msgid "Invalid HY2 URL: insecure must be 0 or 1" msgstr "" @@ -426,11 +426,19 @@ msgstr "" msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" msgstr "" -#: src/validators/validateHysteriaUrl.ts:103 +#: src/validators/validateHysteriaUrl.ts:99 +msgid "Invalid HY2 URL: obfs-password required when obfs is set" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:113 msgid "Invalid HY2 URL: parsing failed" msgstr "" -#: src/validators/validateHysteriaUrl.ts:82 +#: src/validators/validateHysteriaUrl.ts:106 +msgid "Invalid HY2 URL: sni cannot be empty" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:88 msgid "Invalid HY2 URL: unsupported obfs type" msgstr "" @@ -978,7 +986,7 @@ msgid "Uplink" msgstr "" #: src/validators/validateProxyUrl.ts:37 -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://" msgstr "" #: src/validators/validateUrl.ts:17 @@ -1033,7 +1041,7 @@ msgstr "" #: src/validators/validateDns.ts:18 #: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:30 -#: src/validators/validateHysteriaUrl.ts:101 +#: src/validators/validateHysteriaUrl.ts:111 #: src/validators/validateIp.ts:8 #: src/validators/validateOutboundJson.ts:7 #: src/validators/validatePath.ts:16 diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po index 60c47a0..fab7459 100644 --- a/fe-app-podkop/locales/podkop.ru.po +++ b/fe-app-podkop/locales/podkop.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-30 18:34+0200\n" -"PO-Revision-Date: 2025-11-30 18:34+0200\n" +"POT-Creation-Date: 2025-12-01 16:30+0200\n" +"PO-Revision-Date: 2025-12-01 16:30+0200\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -308,9 +308,15 @@ msgstr "Неверный URL Hysteria2: не должен содержать п msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://" +msgid "Invalid HY2 URL: obfs-password required when obfs is set" +msgstr "Неверный URL Hysteria2: требуется obfs-password, когда установлен obfs" + msgid "Invalid HY2 URL: parsing failed" msgstr "Неверный URL Hysteria2: ошибка разбора" +msgid "Invalid HY2 URL: sni cannot be empty" +msgstr "Неверный URL Hysteria2: sni не может быть пустым" + msgid "Invalid HY2 URL: unsupported obfs type" msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs" @@ -704,8 +710,8 @@ msgstr "Неизвестная ошибка" msgid "Uplink" msgstr "Исходящий" -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "URL должен начинаться с vless://, ss://, trojan:// или socks4/5://" +msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://" +msgstr "URL должен начинаться с vless://, ss://, trojan://, socks4/5:// или hysteria2:// hy2://" msgid "URL must use one of the following protocols:" msgstr "URL должен использовать один из следующих протоколов:" diff --git a/fe-app-podkop/src/validators/validateHysteriaUrl.ts b/fe-app-podkop/src/validators/validateHysteriaUrl.ts index 53c7530..464f16d 100644 --- a/fe-app-podkop/src/validators/validateHysteriaUrl.ts +++ b/fe-app-podkop/src/validators/validateHysteriaUrl.ts @@ -96,14 +96,16 @@ export function validateHysteria2Url(url: string): ValidationResult { ) { return { valid: false, - message: 'Invalid HY2 URL: obfs-password required when obfs is set', + message: _( + 'Invalid HY2 URL: obfs-password required when obfs is set', + ), }; } if (paramsKeys.includes('sni') && !params.sni) { return { valid: false, - message: 'Invalid HY2 URL: sni cannot be empty', + message: _('Invalid HY2 URL: sni cannot be empty'), }; } } diff --git a/fe-app-podkop/src/validators/validateProxyUrl.ts b/fe-app-podkop/src/validators/validateProxyUrl.ts index 24b0c19..d0be83d 100644 --- a/fe-app-podkop/src/validators/validateProxyUrl.ts +++ b/fe-app-podkop/src/validators/validateProxyUrl.ts @@ -35,7 +35,7 @@ export function validateProxyUrl(url: string): ValidationResult { return { valid: false, message: _( - 'URL must start with vless://, ss://, trojan://, or socks4/5://', + 'URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://', ), }; } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 986070c..f5204dc 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -516,13 +516,15 @@ function validateHysteria2Url(url) { if (paramsKeys.includes("obfs") && params.obfs !== "none" && !params["obfs-password"]) { return { valid: false, - message: "Invalid HY2 URL: obfs-password required when obfs is set" + message: _( + "Invalid HY2 URL: obfs-password required when obfs is set" + ) }; } if (paramsKeys.includes("sni") && !params.sni) { return { valid: false, - message: "Invalid HY2 URL: sni cannot be empty" + message: _("Invalid HY2 URL: sni cannot be empty") }; } } @@ -553,7 +555,7 @@ function validateProxyUrl(url) { return { valid: false, message: _( - "URL must start with vless://, ss://, trojan://, or socks4/5://" + "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://" ) }; } diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 60c47a0..fab7459 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-30 18:34+0200\n" -"PO-Revision-Date: 2025-11-30 18:34+0200\n" +"POT-Creation-Date: 2025-12-01 16:30+0200\n" +"PO-Revision-Date: 2025-12-01 16:30+0200\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -308,9 +308,15 @@ msgstr "Неверный URL Hysteria2: не должен содержать п msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://" +msgid "Invalid HY2 URL: obfs-password required when obfs is set" +msgstr "Неверный URL Hysteria2: требуется obfs-password, когда установлен obfs" + msgid "Invalid HY2 URL: parsing failed" msgstr "Неверный URL Hysteria2: ошибка разбора" +msgid "Invalid HY2 URL: sni cannot be empty" +msgstr "Неверный URL Hysteria2: sni не может быть пустым" + msgid "Invalid HY2 URL: unsupported obfs type" msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs" @@ -704,8 +710,8 @@ msgstr "Неизвестная ошибка" msgid "Uplink" msgstr "Исходящий" -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "URL должен начинаться с vless://, ss://, trojan:// или socks4/5://" +msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://" +msgstr "URL должен начинаться с vless://, ss://, trojan://, socks4/5:// или hysteria2:// hy2://" msgid "URL must use one of the following protocols:" msgstr "URL должен использовать один из следующих протоколов:" diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index 00c0264..542bfbe 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-30 16:34+0200\n" -"PO-Revision-Date: 2025-11-30 16:34+0200\n" +"POT-Creation-Date: 2025-12-01 14:30+0200\n" +"PO-Revision-Date: 2025-12-01 14:30+0200\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -390,7 +390,7 @@ msgstr "" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "" -#: src/validators/validateHysteriaUrl.ts:73 +#: src/validators/validateHysteriaUrl.ts:76 msgid "Invalid HY2 URL: insecure must be 0 or 1" msgstr "" @@ -426,11 +426,19 @@ msgstr "" msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://" msgstr "" -#: src/validators/validateHysteriaUrl.ts:103 +#: src/validators/validateHysteriaUrl.ts:99 +msgid "Invalid HY2 URL: obfs-password required when obfs is set" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:113 msgid "Invalid HY2 URL: parsing failed" msgstr "" -#: src/validators/validateHysteriaUrl.ts:82 +#: src/validators/validateHysteriaUrl.ts:106 +msgid "Invalid HY2 URL: sni cannot be empty" +msgstr "" + +#: src/validators/validateHysteriaUrl.ts:88 msgid "Invalid HY2 URL: unsupported obfs type" msgstr "" @@ -978,7 +986,7 @@ msgid "Uplink" msgstr "" #: src/validators/validateProxyUrl.ts:37 -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://" msgstr "" #: src/validators/validateUrl.ts:17 @@ -1033,7 +1041,7 @@ msgstr "" #: src/validators/validateDns.ts:18 #: src/validators/validateDomain.ts:13 #: src/validators/validateDomain.ts:30 -#: src/validators/validateHysteriaUrl.ts:101 +#: src/validators/validateHysteriaUrl.ts:111 #: src/validators/validateIp.ts:8 #: src/validators/validateOutboundJson.ts:7 #: src/validators/validatePath.ts:16