Compare commits

...

10 Commits

Author SHA1 Message Date
Kirill Sobakin
e5eff41a0f Merge pull request #170 from itdoginfo/fix
Fix: Mask urltest_proxy_links and move sing-box config check to init config function
2025-09-17 13:04:32 +03:00
Andrey Petelin
bb1c06951c fix: Exclusion of ruleset subnets from dns rules (#148) 2025-09-17 13:31:00 +05:00
Andrey Petelin
4999840340 fix: Support comments in user domain/subnet parsing 2025-09-17 11:58:55 +05:00
Andrey Petelin
6c5a271105 fix: Move sing-box config check to after temp file creation 2025-09-16 20:11:13 +05:00
Andrey Petelin
e336bb831c fix: Mask urltest_proxy_links in config output 2025-09-16 19:45:32 +05:00
Kirill Sobakin
00db99723c Merge pull request #169 from itdoginfo/urltest
fix: Correct boolean value for interrupt_exist_connections in JSON
2025-09-16 15:14:43 +03:00
Andrey Petelin
5439504de7 fix: Correct boolean value for interrupt_exist_connections in JSON generation 2025-09-16 17:12:19 +05:00
Kirill Sobakin
c3072162de Merge pull request #168 from itdoginfo/urltest
feat: Add URLTest proxy configuration type with dynamic list support
2025-09-16 15:10:37 +03:00
Andrey Petelin
d021636f85 chore: Fix placeholder text typo in proxy links field 2025-09-16 17:09:37 +05:00
Andrey Petelin
a06aac0613 feat: Add URLTest proxy configuration type with dynamic list support 2025-09-16 16:58:39 +05:00
5 changed files with 167 additions and 51 deletions

View File

@@ -32,6 +32,7 @@ function createConfigSection(section, map, network) {
o = s.taboption('basic', form.ListValue, 'proxy_config_type', _('Configuration Type'), _('Select how to configure the proxy'));
o.value('url', _('Connection URL'));
o.value('outbound', _('Outbound Config'));
o.value('urltest', _('URLTest'));
o.default = 'url';
o.depends('mode', 'proxy');
o.ucisection = s.section;
@@ -205,6 +206,11 @@ function createConfigSection(section, map, network) {
}
};
o = s.taboption('basic', form.DynamicList, 'urltest_proxy_links', _('URLTest Proxy Links'));
o.depends('proxy_config_type', 'urltest');
o.placeholder = 'vless:// or ss:// link';
o.rmempty = false;
o = s.taboption('basic', form.Flag, 'ss_uot', _('Shadowsocks UDP over TCP'), _('Apply for SS2022'));
o.default = '0';
o.depends('mode', 'proxy');

View File

@@ -66,7 +66,6 @@ start_main() {
# sing-box
sing_box_init_config
sing_box_config_check
config_foreach add_cron_job
/etc/init.d/sing-box start
@@ -83,13 +82,14 @@ start_main() {
}
start() {
local proxy_string interface outbound_json dont_touch_dhcp
local proxy_string interface outbound_json urltest_proxy_links dont_touch_dhcp
config_get proxy_string "main" "proxy_string"
config_get interface "main" "interface"
config_get outbound_json "main" "outbound_json"
config_get urltest_proxy_links "main" "urltest_proxy_links"
if [ -z "$proxy_string" ] && [ -z "$interface" ] && [ -z "$outbound_json" ]; then
log "Podkop start aborted: required options (proxy_string, interface, outbound_json) are missing in 'main' section"
if [ -z "$proxy_string" ] && [ -z "$interface" ] && [ -z "$outbound_json" ] && [ -z "$urltest_proxy_links" ]; then
log "Required options (proxy_string, interface, outbound_json, urltest_proxy_links) are missing in 'main' section. Aborted." "fatal"
exit 1
fi
@@ -238,23 +238,29 @@ migration() {
}
validate_service() {
local domain="$1"
local service="$1"
for valid_service in $VALID_SERVICES; do
if [ "$domain" = "$valid_service" ]; then
for domain_service in $COMMUNITY_DOMAIN_SERVICES; do
if [ "$service" = "$domain_service" ]; then
return 0
fi
done
log "Invalid service in domain_list: $domain. Exiting. Check config and LuCI cache"
for subnet_service in $COMMUNITY_SUBNET_SERVICES; do
if [ "$service" = "$subnet_service" ]; then
return 0
fi
done
log "Invalid service in community lists: $service. Check config and LuCI cache. Aborted." "fatal"
exit 1
}
process_validate_service() {
local domain_list_enabled
config_get_bool domain_list_enabled "$section" "domain_list_enabled" 0
if [ "$domain_list_enabled" -eq 1 ]; then
config_list_foreach "$section" domain_list validate_service
local community_lists_enabled
config_get_bool community_lists_enabled "$section" "community_lists_enabled" 0
if [ "$community_lists_enabled" -eq 1 ]; then
config_list_foreach "$section" "community_lists" validate_service
fi
}
@@ -629,7 +635,7 @@ configure_outbound_handler() {
case "$proxy_config_type" in
url)
log "Detected proxy configuration type: url"
log "Detected proxy configuration type: url" "debug"
local proxy_string udp_over_tcp
config_get proxy_string "$section" "proxy_string"
config_get udp_over_tcp "$section" "ss_uot"
@@ -643,11 +649,42 @@ configure_outbound_handler() {
config=$(sing_box_cf_add_proxy_outbound "$config" "$section" "$active_proxy_string" "$udp_over_tcp")
;;
outbound)
log "Detected proxy configuration type: outbound"
log "Detected proxy configuration type: outbound" "debug"
local json_outbound
config_get json_outbound "$section" "outbound_json"
config=$(sing_box_cf_add_json_outbound "$config" "$section" "$json_outbound")
;;
urltest)
log "Detected proxy configuration type: urltest" "debug"
local urltest_proxy_links udp_over_tcp i urltest_tag selector_tag outbound_tag outbound_tags \
urltest_outbounds selector_outbounds
config_get urltest_proxy_links "$section" "urltest_proxy_links"
config_get udp_over_tcp "$section" "ss_uot"
if [ -z "$urltest_proxy_links" ]; then
log "URLTest proxy links is not set. Aborted." "fatal"
exit 1
fi
i=1
for link in $urltest_proxy_links; do
config="$(sing_box_cf_add_proxy_outbound "$config" "$section-$i" "$link" "$udp_over_tcp")"
outbound_tag="$(get_outbound_tag_by_section "$section-$i")"
if [ -z "$outbound_tags" ]; then
outbound_tags="$outbound_tag"
else
outbound_tags="$outbound_tags,$outbound_tag"
fi
i=$((i+1))
done
urltest_tag="$(get_outbound_tag_by_section "$section-urltest")"
selector_tag="$(get_outbound_tag_by_section "$section")"
urltest_outbounds="$(comma_string_to_json_array "$outbound_tags")"
selector_outbounds="$(comma_string_to_json_array "$outbound_tags,$urltest_tag")"
config="$(sing_box_cm_add_urltest_outbound "$config" "$urltest_tag" "$urltest_outbounds")"
config="$(sing_box_cm_add_selector_outbound "$config" "$selector_tag" "$selector_outbounds" "$urltest_tag")"
;;
*)
log "Unknown proxy configuration type: '$proxy_config_type'. Aborted." "fatal"
exit 1
@@ -939,8 +976,14 @@ configure_community_list_handler() {
config_get update_interval "main" "update_interval" "1d"
config=$(sing_box_cm_add_remote_ruleset "$config" "$ruleset_tag" "$format" "$url" "$detour" "$update_interval")
_add_ruleset_to_dns_rules "$ruleset_tag"
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag")
for service in $COMMUNITY_DOMAIN_SERVICES; do
if [ "$tag" = "$service" ]; then
_add_ruleset_to_dns_rules "$ruleset_tag"
break
fi
done
}
configure_user_domain_or_subnets_list() {
@@ -1135,6 +1178,8 @@ sing_box_save_config() {
log "Save sing-box temporary config to $temp_file_path" "debug"
sing_box_cm_save_config_to_file "$config" "$temp_file_path"
sing_box_config_check "$temp_file_path"
current_config_hash=$(md5sum "$sing_box_config_path" 2> /dev/null | awk '{print $1}')
temp_config_hash=$(md5sum "$temp_file_path" | awk '{print $1}')
log "Current sing-box config hash: $current_config_hash" "debug"
@@ -1149,10 +1194,10 @@ sing_box_save_config() {
}
sing_box_config_check() {
local sing_box_config_path
config_get sing_box_config_path "main" "config_path"
if ! sing-box -c "$sing_box_config_path" check > /dev/null 2>&1; then
log "Sing-box configuration is invalid" "fatal"
local config_path="$1"
if ! sing-box -c "$config_path" check > /dev/null 2>&1; then
log "Sing-box configuration $config_path is invalid" "fatal"
exit 1
fi
}
@@ -1720,15 +1765,14 @@ show_sing_box_config() {
}
show_config() {
if [ ! -f /etc/config/podkop ]; then
if [ ! -f "$PODKOP_CONFIG" ]; then
nolog "Configuration file not found"
return 1
fi
tmp_config=$(mktemp)
cat /etc/config/podkop | sed \
-e 's/\(option proxy_string\).*/\1 '\''MASKED'\''/g' \
sed -e 's/\(option proxy_string\).*/\1 '\''MASKED'\''/g' \
-e 's/\(option outbound_json\).*/\1 '\''MASKED'\''/g' \
-e 's/\(option second_proxy_string\).*/\1 '\''MASKED'\''/g' \
-e 's/\(option second_outbound_json\).*/\1 '\''MASKED'\''/g' \
@@ -1737,8 +1781,9 @@ show_config() {
-e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \
-e 's/\(sid=[^&]*\)/sid=MASKED/g' \
-e 's/\(option dns_server '\''[^'\'']*\.dns\.nextdns\.io'\''\)/option dns_server '\''MASKED.dns.nextdns.io'\''/g' \
-e "s|\(option dns_server 'dns\.nextdns\.io\)/[^']*|\1/MASKED|"
> "$tmp_config"
-e "s|\(option dns_server 'dns\.nextdns\.io\)/[^']*|\1/MASKED|" \
-e 's/\(list urltest_proxy_links\).*/\1 '\''MASKED'\''/g' \
"$PODKOP_CONFIG" > "$tmp_config"
cat "$tmp_config"
rm -f "$tmp_config"

View File

@@ -63,4 +63,5 @@ SUBNETS_HETZNER="${GITHUB_RAW_URL}/Subnets/IPv4/hetzner.lst"
SUBNETS_OVH="${GITHUB_RAW_URL}/Subnets/IPv4/ovh.lst"
SUBNETS_DIGITALOCEAN="${GITHUB_RAW_URL}/Subnets/IPv4/digitalocean.lst"
SUBNETS_CLOUDFRONT="${GITHUB_RAW_URL}/Subnets/IPv4/cloudfront.lst"
VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram cloudflare google_ai google_play hetzner ovh hodca digitalocean cloudfront"
COMMUNITY_DOMAIN_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube hdrezka tiktok google_ai google_play hodca"
COMMUNITY_SUBNET_SERVICES="discord meta twitter cloudflare cloudfront digitalocean hetzner ovh telegram"

View File

@@ -299,33 +299,11 @@ parse_domain_or_subnet_string_to_commas_string() {
local string="$1"
local type="$2"
local result
for item in $string; do
case "$type" in
domains)
if ! is_domain_suffix "$item"; then
log "'$item' is not a valid domain" "debug"
continue
fi
;;
subnets)
if ! is_ipv4_ip_or_ipv4_cidr "$item"; then
log "'$item' is not IPv4 or IPv4 CIDR" "debug"
continue
fi
;;
*)
log "Unknown type: $type" "error"
return 1
;;
esac
tmpfile=$(mktemp)
printf "%s\n" "$string" | sed 's/\/\/.*//' | tr ', ' '\n' | grep -v '^$' > "$tmpfile"
if [ -z "$result" ]; then
result="$item"
else
result="$result,$item"
fi
done
result="$(parse_domain_or_subnet_file_to_comma_string "$tmpfile" "$type")"
rm -f "$tmpfile"
echo "$result"
}
@@ -345,6 +323,8 @@ parse_domain_or_subnet_file_to_comma_string() {
local result
while IFS= read -r line; do
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[ -z "$line" ] && continue
case "$type" in

View File

@@ -832,6 +832,90 @@ sing_box_cm_add_raw_outbound() {
)]'
}
#######################################
# Add a URLTest outbound to the outbounds section of a sing-box JSON configuration.
# Arguments:
# config: JSON configuration
# 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)
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_add_urltest_outbound "$CONFIG" "auto-select" '["proxy1","proxy2"]')
#######################################
sing_box_cm_add_urltest_outbound() {
local config="$1"
local tag="$2"
local outbounds="$3"
local url="$4"
local interval="$5"
local tolerance="$6"
local idle_timeout="$7"
local interrupt_exist_connections="$8"
echo "$config" | jq \
--arg tag "$tag" \
--argjson outbounds "$outbounds" \
--arg url "$url" \
--arg interval "$interval" \
--arg tolerance "$tolerance" \
--arg idle_timeout "$idle_timeout" \
--arg interrupt_exist_connections "$interrupt_exist_connections" \
'.outbounds += [
{
type: "urltest",
tag: $tag,
outbounds: $outbounds
}
+ (if $url != "" then {url: $url} else {} end)
+ (if $interval != "" then {interval: $interval} else {} end)
+ (if $tolerance != "" then {tolerance: ($tolerance | tonumber)} else {} end)
+ (if $idle_timeout != "" then {idle_timeout: $idle_timeout} else {} end)
+ (if $interrupt_exist_connections == "true" then {interrupt_exist_connections: true} else {} end)
]'
}
#######################################
# Add a Selector outbound to the outbounds section of a sing-box JSON configuration.
# Arguments:
# config: JSON configuration
# 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)
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_add_selector_outbound "$CONFIG" "select-proxy" '["proxy1","proxy2"]')
#######################################
sing_box_cm_add_selector_outbound() {
local config="$1"
local tag="$2"
local outbounds="$3"
local default="$4"
local interrupt_exist_connections="$5"
echo "$config" | jq \
--arg tag "$tag" \
--argjson outbounds "$outbounds" \
--arg default "$default" \
--arg interrupt_exist_connections "$interrupt_exist_connections" \
'.outbounds += [
{
type: "selector",
tag: $tag,
outbounds: $outbounds,
default: $default
}
+ (if $interrupt_exist_connections == "true" then {interrupt_exist_connections: true} else {} end)
]'
}
#######################################
# Configure the route section of a sing-box JSON configuration.
# Arguments: