refactor: intermediate refactoring commit

This commit is contained in:
Andrey Petelin
2025-09-04 12:10:05 +05:00
parent 75fc377c22
commit f07d90a524
8 changed files with 975 additions and 1405 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

View File

@@ -220,6 +220,7 @@ function createAdditionalSection(mainSection, network) {
o.rmempty = false;
o.ucisection = 'main';
// TODO(ampetelin): Can be moved to advanced settings in luci
// Extra IPs and exclusions (main section)
o = mainSection.taboption('basic', form.Flag, 'exclude_from_ip_enabled', _('IP for exclusion'), _('Specify local IP addresses that will never use the configured route'));
o.default = '0';

View File

@@ -234,18 +234,18 @@ function createConfigSection(section, map, network) {
return true;
};
o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Lists'));
o = s.taboption('basic', form.Flag, 'community_list_enabled', _('Community Lists'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'domain_list', _('Service List'), _('Select predefined service for routing') + ' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>');
o = s.taboption('basic', form.DynamicList, 'community_list', _('Service List'), _('Select predefined service for routing') + ' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>');
o.placeholder = 'Service list';
Object.entries(constants.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
o.value(key, _(label));
});
o.depends('domain_list_enabled', '1');
o.depends('community_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
@@ -302,7 +302,7 @@ function createConfigSection(section, map, network) {
}
};
o = s.taboption('basic', form.ListValue, 'custom_domains_list_type', _('User Domain List Type'), _('Select how to add your custom domains'));
o = s.taboption('basic', form.ListValue, 'user_domains_list_type', _('User Domain List Type'), _('Select how to add your custom domains'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List'));
@@ -310,9 +310,9 @@ function createConfigSection(section, map, network) {
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
o = s.taboption('basic', form.DynamicList, 'user_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
o.placeholder = 'Domains list';
o.depends('custom_domains_list_type', 'dynamic');
o.depends('user_domains_list_type', 'dynamic');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
@@ -324,9 +324,10 @@ function createConfigSection(section, map, network) {
return true;
};
o = s.taboption('basic', form.TextValue, 'custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline. You can add comments after //'));
// TODO: Is it possible to save not as an option (but as a split list)?
o = s.taboption('basic', form.TextValue, 'user_domains', _('User Domains List'), _('Enter domain names separated by comma, space or newline. You can add comments after //'));
o.placeholder = 'example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains';
o.depends('custom_domains_list_type', 'text');
o.depends('user_domains_list_type', 'text');
o.rows = 8;
o.rmempty = false;
o.ucisection = s.section;
@@ -365,14 +366,14 @@ function createConfigSection(section, map, network) {
return true;
};
o = s.taboption('basic', form.Flag, 'custom_local_domains_list_enabled', _('Local Domain Lists'), _('Use the list from the router filesystem'));
o = s.taboption('basic', form.Flag, 'local_domains_list_enabled', _('Local Domain Lists'), _('Use the list from the router filesystem'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_local_domains', _('Local Domain Lists Path'), _('Enter the list file path'));
o = s.taboption('basic', form.DynamicList, 'local_domains_list', _('Local Domain Lists Path'), _('Enter the list file path'));
o.placeholder = '/path/file.lst';
o.depends('custom_local_domains_list_enabled', '1');
o.depends('local_domains_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
@@ -384,14 +385,14 @@ function createConfigSection(section, map, network) {
return true;
};
o = s.taboption('basic', form.Flag, 'custom_download_domains_list_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
o = s.taboption('basic', form.Flag, 'remote_domains_list_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_download_domains', _('Remote Domain URLs'), _('Enter full URLs starting with http:// or https://'));
o = s.taboption('basic', form.DynamicList, 'remote_domains_list', _('Remote Domain URLs'), _('Enter full URLs starting with http:// or https://'));
o.placeholder = 'URL';
o.depends('custom_download_domains_list_enabled', '1');
o.depends('remote_domains_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
@@ -399,7 +400,7 @@ function createConfigSection(section, map, network) {
return validateUrl(value);
};
o = s.taboption('basic', form.ListValue, 'custom_subnets_list_enabled', _('User Subnet List Type'), _('Select how to add your custom subnets'));
o = s.taboption('basic', form.ListValue, 'user_subnets_list_type', _('User Subnet List Type'), _('Select how to add your custom subnets'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List (comma/space/newline separated)'));
@@ -407,9 +408,9 @@ function createConfigSection(section, map, network) {
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_subnets', _('User Subnets'), _('Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses'));
o = s.taboption('basic', form.DynamicList, 'user_subnets', _('User Subnets'), _('Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses'));
o.placeholder = 'IP or subnet';
o.depends('custom_subnets_list_enabled', 'dynamic');
o.depends('user_subnets_list_type', 'dynamic');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
@@ -432,9 +433,10 @@ function createConfigSection(section, map, network) {
return true;
};
o = s.taboption('basic', form.TextValue, 'custom_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //'));
// TODO: Is it possible to save not as an option (but as a split list)?
o = s.taboption('basic', form.TextValue, 'user_subnets', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //'));
o.placeholder = '103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9';
o.depends('custom_subnets_list_enabled', 'text');
o.depends('user_subnets_list_type', 'text');
o.rows = 10;
o.rmempty = false;
o.ucisection = s.section;
@@ -489,14 +491,14 @@ function createConfigSection(section, map, network) {
return true;
};
o = s.taboption('basic', form.Flag, 'custom_download_subnets_list_enabled', _('Remote Subnet Lists'), _('Download and use subnet lists from remote URLs'));
o = s.taboption('basic', form.Flag, 'remote_subnets_list_enabled', _('Remote Subnet Lists'), _('Download and use subnet lists from remote URLs'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_download_subnets', _('Remote Subnet URLs'), _('Enter full URLs starting with http:// or https://'));
o = s.taboption('basic', form.DynamicList, 'remote_subnets_list', _('Remote Subnet URLs'), _('Enter full URLs starting with http:// or https://'));
o.placeholder = 'URL';
o.depends('custom_download_subnets_list_enabled', '1');
o.depends('remote_subnets_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
SB_CONFIG="/etc/sing-box/config.json"
# Log
SB_DEFAULT_LOG_LEVEL="warn"
# DNS
SB_DNS_SERVER_TAG="dns-server"
SB_SPLIT_DNS_SERVER_TAG="split-dns-server"
SB_FAKEIP_DNS_SERVER_TAG="fakeip-server"
FAKEIP="198.18.0.0/15" # TODO(ampetelin): renaming is needed
SB_DNS_DOMAIN_RESOLVER_TAG="dns-domain-resolver"
SB_FAKEIP_DNS_RULE_TAG="fakeip-dns-rule-tag"
SB_INVERT_FAKEIP_DNS_RULE_TAG="invert-fakeip-dns-rule-tag"
# Inbounds
SB_TPROXY_INBOUND_TAG="tproxy-in"
SB_TPROXY_INBOUND_ADDRESS="127.0.0.1"
SB_TPROXY_INBOUND_PORT=1602
SB_DNS_INBOUND_TAG="dns-in"
SB_DNS_INBOUND_ADDRESS="127.0.0.42"
SB_DNS_INBOUND_PORT=53
SB_MIXED_INBOUND_TAG="mixed-in"
SB_MIXED_INBOUND_ADDRESS="0.0.0.0"
SB_MIXED_INBOUND_PORT=2080
SB_SERVICE_MIXED_INBOUND_TAG="service-mixed-in"
SB_SERVICE_MIXED_INBOUND_ADDRESS="127.0.0.1"
SB_SERVICE_MIXED_INBOUND_PORT=4534
# Outbounds
SB_DIRECT_OUTBOUND_TAG="direct-out"
SB_MAIN_OUTBOUND_TAG="main-out"

View File

@@ -0,0 +1,178 @@
# Check if string is valid IPv4
is_ipv4() {
local ip="$1"
local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
[[ $ip =~ $regex ]]
}
# Check if string is valid IPv4 with CIDR mask
is_ipv4_cidr() {
local ip="$1"
local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(\/(3[0-2]|2[0-9]|1[0-9]|[0-9]))$"
[[ $ip =~ $regex ]]
}
# Checks if the given string is a valid base64-encoded sequence
is_base64() {
local str="$1"
if echo "$str" | base64 -d > /dev/null 2>&1; then
return 0
fi
return 1
}
# Checks if the given file exists
file_exists() {
local filepath="$1"
if [[ -f "$filepath" ]]; then
return 0
else
return 1
fi
}
# Extracts and returns the file extension from the given URL
get_url_file_extension() {
local url="$1"
local file_extension="${url##*.}"
echo "$file_extension"
}
# Returns the inbound tag name by appending the postfix to the given section
get_inbound_tag_by_section() {
local section="$1"
local postfix="in"
echo "$section-$postfix"
}
# Returns the outbound tag name by appending the postfix to the given section
get_outbound_tag_by_section() {
local section="$1"
local postfix="out"
echo "$section-$postfix"
}
# Constructs and returns a ruleset tag using section, name, optional type, and a fixed postfix
get_rule_set_tag() {
local section="$1"
local name="$2"
local type="${3:-}"
local postfix="ruleset"
if [ -n "$type" ]; then
echo "$section-$name-$type-$postfix"
else
echo "$section-$name-$postfix"
fi
}
# Determines the ruleset format based on the file extension (json → source, srs → binary)
get_rule_set_format_by_file_extension() {
local file_extension="$1"
local format
case "$file_extension" in
json) format="source" ;;
srs) format="binary" ;;
*)
log "Unsupported file extension: .$file_extension"
return 1
;;
esac
echo "$format"
}
# Converts a comma-separated string into a JSON array string
comma_string_to_json_array() {
local input="$1"
if [ -z "$input" ]; then
echo "[]"
return
fi
local replaced="${input//,/\",\"}"
echo "[\"$replaced\"]"
}
# Decodes a URL-encoded string
url_decode() {
local encoded="$1"
printf '%b' "$(echo "$encoded" | sed 's/+/ /g; s/%/\\x/g')"
}
# Extracts the userinfo (username[:password]) part from a URL
url_get_userinfo() {
local url="$1"
echo "$url" | sed -n 's#^[^:]*://\([^@]*\)@.*#\1#p'
}
# Extracts the host part from a URL
url_get_host() {
local url="$1"
echo "$url" | sed -n 's#^[^:]*://[^@]*@\([^:/?#]*\).*#\1#p'
}
# Extracts the port number from a URL
url_get_port() {
local url="$1"
echo "$url" | sed -n 's#^[^:]*://[^@]*@[^:/?#]*:\([0-9]*\).*#\1#p'
}
# Extracts the value of a specific query parameter from a URL
url_get_query_param() {
local url="$1"
local param="$2"
local raw
raw=$(echo "$url" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p")
[ -z "$raw" ] && echo "" && return
echo "$raw"
}
# Extracts the basename (filename without extension) from a URL
url_get_basename() {
local url="$1"
local filename="${url##*/}"
local basename="${filename%%.*}"
echo "$basename"
}
# Decodes and returns a base64-encoded string
base64_decode() {
local str="$1"
local decoded_url
decoded_url="$(echo "$str" | base64 -d 2> /dev/null)"
echo "$decoded_url"
}
# Generates a unique 16-character ID based on the current timestamp and a random number
gen_id() {
printf '%s%s' "$(date +%s)" "$RANDOM" | md5sum | cut -c1-16
}
# Migrates a configuration key in an OpenWrt config file from old_key_name to new_key_name
migrate_config_key() {
local config="$1"
local key_type="$2"
local old_key_name="$3"
local new_key_name="$4"
if grep -q "$key_type $old_key_name" "$config"; then
log "Deprecated $key_type found: $old_key_name migrating to $new_key_name" "migration"
sed -i "s/$key_type $old_key_name/$key_type $new_key_name/g" "$config"
fi
}

View File

@@ -0,0 +1,210 @@
PODKOP_LIB="/usr/lib/podkop"
. "$PODKOP_LIB/helpers.sh"
. "$PODKOP_LIB/sing_box_config_manager.sh"
sing_box_cf_add_dns_server() {
local config="$1"
local type="$2"
local tag="$3"
local server_address="$4"
local path="$5"
local headers="$6"
local domain_resolver="$7"
local detour="$8"
case "$type" in
udp)
config=$(sing_box_cm_add_udp_dns_server "$config" "$tag" "$server_address" 53 "$domain_resolver" "$detour")
;;
dot)
config=$(sing_box_cm_add_tls_dns_server "$config" "$tag" "$server_address" 853 "$domain_resolver" "$detour")
;;
doh)
config=$(
sing_box_cm_add_https_dns_server "$config" "$tag" "$server_address" 443 "$path" "$headers" \
"$domain_resolver" "$detour"
)
;;
*)
log "Unsupported DNS server type: $type" "sing-box"
exit 1
;;
esac
echo "$config"
}
sing_box_cf_add_mixed_inbound_and_route_rule() {
local config="$1"
local tag="$2"
local listen_address="$3"
local listen_port="$4"
local outbound="$5"
config=$(sing_box_cm_add_mixed_inbound "$config" "$tag" "$listen_address" "$listen_port")
config=$(sing_box_cm_add_route_rule "$config" "" "$tag" "$outbound")
echo "$config"
}
sing_box_cf_add_proxy_outbound() {
local config="$1"
local section="$2"
local url="$3"
local udp_over_tcp="$4"
url=$(url_decode "$url")
local scheme="${url%%://*}"
case "$scheme" in
vless)
local tag host port uuid flow
tag=$(get_outbound_tag_by_section "$section")
host=$(url_get_host "$url")
port=$(url_get_port "$url")
uuid=$(url_get_userinfo "$url")
flow=$(url_get_query_param "$url" "flow")
config=$(sing_box_cm_add_vless_outbound "$config" "$tag" "$host" "$port" "$uuid" "$flow")
local transport
transport=$(url_get_query_param "$url" "type")
case "$transport" in
tcp | raw) ;;
ws)
local ws_path ws_host ws_early_data
ws_path=$(url_get_query_param "$url" "path")
ws_host=$(url_get_query_param "$url" "host")
ws_early_data=$(url_get_query_param "$url" "ed")
config=$(sing_box_cm_set_vless_ws_transport "$config" "$tag" "$ws_path" "$ws_host" "$ws_early_data")
;;
grpc)
# TODO(ampetelin): Add handling of optional gRPC parameters; example links are needed.
config=$(sing_box_cm_set_vless_grpc_transport "$config" "$tag")
;;
*)
log "Unknown transport '$transport' detected." "sing-box" "error"
;;
esac
local security
security=$(url_get_query_param "$url" "security")
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")
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")
short_id=$(url_get_query_param "$url" "sid")
config=$(
sing_box_cm_set_vless_tls \
"$config" \
"$tag" \
"$sni" \
"$([ "$insecure" == "1" ] && echo true)" \
"$([ "$alpn" == "[]" ] && echo null || echo "$alpn")" \
"$fingerprint" \
"$public_key" \
"$short_id"
)
;;
none) ;;
*)
log "Unknown security '$security' detected." "sing-box" "error"
;;
esac
;;
ss)
local userinfo tag host port method password udp_over_tcp
userinfo=$(url_get_userinfo "$url")
if is_base64 "$userinfo"; then
userinfo=$(base64_decode "$userinfo")
fi
tag=$(get_outbound_tag_by_section "$section")
host=$(url_get_host "$url")
port=$(url_get_port "$url")
method="${userinfo%%:*}"
password="${userinfo#*:}"
config=$(
sing_box_cm_add_shadowsocks_outbound \
"$config" \
"$tag" \
"$host" \
"$port" \
"$method" \
"$password" \
"" \
"$([ "$udp_over_tcp" == "1" ] && echo 2)" # if udp_over_tcp is enabled, enable version 2
)
;;
*)
log "Unsupported proxy $scheme type" "sing-box"
exit 1
;;
esac
echo "$config"
}
sing_box_cf_add_json_outbound() {
local config="$1"
local section="$2"
local json_outbound="$3"
local tag
tag=$(get_outbound_tag_by_section "$section")
config=$(sing_box_cm_add_raw_outbound "$config" "$tag" "$json_outbound")
echo "$config"
}
sing_box_cf_add_interface_outbound() {
local config="$1"
local section="$2"
local interface_name="$3"
local tag
tag=$(get_outbound_tag_by_section "$section")
config=$(sing_box_cm_add_interface_outbound "$config" "$tag" "$interface_name")
echo "$config"
}
sing_box_cf_proxy_domain() {
local config="$1"
local inbound="$2"
local domain="$3"
local outbound="$4"
tag="$(gen_id)"
config=$(sing_box_cm_add_route_rule "$config" "$tag" "$inbound" "$outbound")
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "domain" "$domain")
echo "$config"
}
sing_box_cf_override_domain_port() {
local config="$1"
local domain="$2"
local port="$3"
tag="$(gen_id)"
config=$(sing_box_cm_add_options_route_rule "$config" "$tag")
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "domain" "$domain")
config=$(sing_box_cm_patch_route_rule "$config" "$tag" "override_port" "$port")
echo "$config"
}
sing_box_cf_add_remote_ruleset_with_dns_and_route_rule() {
local config="$1"
}

View File

@@ -1,4 +1,3 @@
#!/bin/ash
#
# Module: sing_box_config_manager.sh
#
@@ -272,9 +271,9 @@ sing_box_cm_add_dns_route_rule() {
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_patch_dns_route_rule "$CONFIG" "fakeip-dns-rule-id" "rule_set" '"main"')
# CONFIG=$(sing_box_cm_patch_dns_route_rule "$CONFIG" "fakeip-dns-rule-id" "rule_set" "main")
# CONFIG=$(sing_box_cm_patch_dns_route_rule "$CONFIG" "fakeip-dns-rule-id" "rule_set" '["main","second"]')
# CONFIG=$(sing_box_cm_patch_dns_route_rule "$CONFIG" "fakeip-dns-rule-id" "domain" '"example.com"')
# CONFIG=$(sing_box_cm_patch_dns_route_rule "$CONFIG" "fakeip-dns-rule-id" "domain" "example.com")
#######################################
sing_box_cm_patch_dns_route_rule() {
local config="$1"
@@ -282,12 +281,14 @@ sing_box_cm_patch_dns_route_rule() {
local key="$3"
local value="$4"
value=$(_normalize_arg "$value")
echo "$config" | jq \
--arg service_tag "$SERVICE_TAG" \
--arg tag "$tag" \
--arg key "$key" \
--argjson value "$value" \
'import "helpers" as h;
'import "helpers" as h {"search": "/usr/lib/podkop"};
.dns.rules |= map(
if .[$service_tag] == $tag then
if has($key) then
@@ -310,13 +311,15 @@ sing_box_cm_patch_dns_route_rule() {
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_add_dns_reject_rule "$CONFIG" "query_type" '"HTTPS"')
# CONFIG=$(sing_box_cm_add_dns_reject_rule "$CONFIG" "query_type" "HTTPS")
#######################################
sing_box_cm_add_dns_reject_rule() {
local config="$1"
local key="$2"
local value="$3"
value=$(_normalize_arg "$value")
echo "$config" | jq \
--arg key "$key" \
--argjson value "$value" \
@@ -760,7 +763,7 @@ sing_box_cm_set_vless_tls() {
+ (if $insecure == "true" then {insecure: true} else {} end)
+ (if $alpn != null then {alpn: $alpn} else {} end)
+ (if $utls_fingerprint != "" then {
ults: {
utls: {
enabled: true,
fingerprint: $utls_fingerprint
}
@@ -902,7 +905,7 @@ sing_box_cm_add_route_rule() {
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_patch_route_rule "$CONFIG" "main-route-rule" "rule_set" '"inline-ruleset"')
# CONFIG=$(sing_box_cm_patch_route_rule "$CONFIG" "main-route-rule" "rule_set" "inline-ruleset")
#######################################
sing_box_cm_patch_route_rule() {
local config="$1"
@@ -910,12 +913,14 @@ sing_box_cm_patch_route_rule() {
local key="$3"
local value="$4"
value=$(_normalize_arg "$value")
echo "$config" | jq \
--arg service_tag "$SERVICE_TAG" \
--arg tag "$tag" \
--arg key "$key" \
--argjson value "$value" \
'import "helpers" as h;
'import "helpers" as h {"search": "/usr/lib/podkop"};
.route.rules |= map(
if .[$service_tag] == $tag then
if has($key) then
@@ -938,13 +943,15 @@ sing_box_cm_patch_route_rule() {
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_add_reject_route_rule "$CONFIG" "protocol" '"quic"')
# CONFIG=$(sing_box_cm_add_reject_route_rule "$CONFIG" "protocol" "quic")
#######################################
sing_box_cm_add_reject_route_rule() {
local config="$1"
local key="$2"
local value="$3"
value=$(_normalize_arg "$value")
echo "$config" | jq \
--arg key "$key" \
--argjson value "$value" \
@@ -963,13 +970,15 @@ sing_box_cm_add_reject_route_rule() {
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_add_hijack_dns_route_rule "$CONFIG" "protocol" '"dns"')
# CONFIG=$(sing_box_cm_add_hijack_dns_route_rule "$CONFIG" "protocol" "dns")
#######################################
sing_box_cm_add_hijack_dns_route_rule() {
local config="$1"
local key="$2"
local value="$3"
value=$(_normalize_arg "$value")
echo "$config" | jq \
--arg key "$key" \
--argjson value "$value" \
@@ -1011,7 +1020,7 @@ sing_box_cm_add_options_route_rule() {
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_sniff_route_rule "$CONFIG" "inbound" '"tproxy-in"')
# CONFIG=$(sing_box_cm_sniff_route_rule "$CONFIG" "inbound" "tproxy-in")
# CONFIG=$(sing_box_cm_sniff_route_rule "$CONFIG" "inbound" '["tproxy-in","dns-in"]')
#######################################
sing_box_cm_sniff_route_rule() {
@@ -1019,6 +1028,8 @@ sing_box_cm_sniff_route_rule() {
local key="$2"
local value="$3"
value=$(_normalize_arg "$value")
echo "$config" | jq \
--arg key "$key" \
--argjson value "$value" \
@@ -1060,9 +1071,9 @@ sing_box_cm_add_inline_ruleset() {
# Outputs:
# Writes updated JSON configuration to stdout
# Example:
# CONFIG=$(sing_box_cm_add_inline_ruleset_rule "$CONFIG" "inline-ruleset" "domain_suffix" '"telegram.org"')
# CONFIG=$(sing_box_cm_add_inline_ruleset_rule "$CONFIG" "inline-ruleset" "domain_suffix" '"discord.com"')
# CONFIG=$(sing_box_cm_add_inline_ruleset_rule "$CONFIG" "inline-ruleset" "ip_cidr" '"111.111.111.111/32"')
# CONFIG=$(sing_box_cm_add_inline_ruleset_rule "$CONFIG" "inline-ruleset" "domain_suffix" "telegram.org")
# CONFIG=$(sing_box_cm_add_inline_ruleset_rule "$CONFIG" "inline-ruleset" "domain_suffix" "discord.com")
# CONFIG=$(sing_box_cm_add_inline_ruleset_rule "$CONFIG" "inline-ruleset" "ip_cidr" "111.111.111.111/32")
#######################################
sing_box_cm_add_inline_ruleset_rule() {
local config="$1"
@@ -1070,11 +1081,13 @@ sing_box_cm_add_inline_ruleset_rule() {
local key="$3"
local value="$4"
echo "$config" | jq \
value=$(_normalize_arg "$value")
echo "$config" | jq -L /usr/lib/podkop \
--arg tag "$tag" \
--arg key "$key" \
--argjson value "$value" \
'import "helpers" as h;
'import "helpers" as h {"search": "/usr/lib/podkop"};
.route.rule_set |= map(
if .tag == $tag then
if has($key) then
@@ -1232,4 +1245,13 @@ sing_box_cm_save_config_to_file() {
--arg tag "$SERVICE_TAG" \
'walk(if type == "object" then del(.[$tag]) else . end)' \
> "$file_path"
}
}
_normalize_arg() {
local value="$1"
if echo "$value" | jq -e . > /dev/null 2>&1; then
printf '%s' "$value"
else
printf '%s' "$value" | jq -R .
fi
}