diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..723ef36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
\ No newline at end of file
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js
index 9b96b0d..00fe10e 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js
@@ -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';
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js
index 8cbc2b6..a6b20c7 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js
@@ -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') + ' github.com/itdoginfo/allow-domains');
+ o = s.taboption('basic', form.DynamicList, 'community_list', _('Service List'), _('Select predefined service for routing') + ' github.com/itdoginfo/allow-domains');
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) {
diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop
index 79bd5d2..54447b8 100755
--- a/podkop/files/usr/bin/podkop
+++ b/podkop/files/usr/bin/podkop
@@ -1,8 +1,12 @@
#!/bin/ash
-# shellcheck shell=dash
[ -r /lib/functions.sh ] && . /lib/functions.sh
[ -r /lib/config/uci.sh ] && . /lib/config/uci.sh
+PODKOP_LIB="/usr/lib/podkop"
+. "$PODKOP_LIB/constants.sh"
+. "$PODKOP_LIB/helpers.sh"
+. "$PODKOP_LIB/sing_box_config_manager.sh"
+. "$PODKOP_LIB/sing_box_config_facade.sh"
config_load "/etc/config/podkop"
@@ -21,10 +25,9 @@ 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"
-SING_BOX_CONFIG="/etc/sing-box/config.json"
-FAKEIP="198.18.0.0/15"
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"
DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8"
+CHECK_PROXY_IP_DOMAIN="ip.podkop.fyi"
TEST_DOMAIN="fakeip.podkop.fyi"
INTERFACES_LIST=""
SRC_INTERFACE=""
@@ -40,26 +43,34 @@ COLOR_RESET="\033[0m"
log() {
local message="$1"
- local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
+ local module="$2"
+ local level="$3"
- logger -t "podkop" "$timestamp $message"
+ if [ "$level" == "" ]; then
+ level="info"
+ fi
+
+ logger -t "podkop" "[$level] [$module] $message"
}
nolog() {
local message="$1"
- local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
+ local timestamp
+ timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo -e "${COLOR_CYAN}[$timestamp]${COLOR_RESET} ${COLOR_GREEN}$message${COLOR_RESET}"
}
echolog() {
local message="$1"
- log "$message"
+ local module="$2"
+
+ log "$message" "$module"
nolog "$message"
}
build_sing_box_config() {
- cat > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+ cat > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SB_CONFIG"
}
start_main() {
@@ -70,12 +81,12 @@ start_main() {
required_version="1.11.1"
if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
- log "[critical] The version of sing-box ($sing_box_version) is lower than the minimum version. Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box"
+ log "The version of sing-box ($sing_box_version) is lower than the minimum version. Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box" "main" "critical"
exit 1
fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
- log "[critical] Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp"
+ log "Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp" "main" "warn"
fi
migration
@@ -97,73 +108,48 @@ start_main() {
sing_box_uci
# sing-box
- sing_box_inbound_proxy 1602
- sing_box_dns
- sing_box_dns_rule_fakeip
- sing_box_rule_dns
- sing_box_create_bypass_ruleset
- sing_box_add_secure_dns_probe_domain
- sing_box_cache_file
- process_socks5
-
- # sing-box outbounds and rules
- config_foreach sing_box_outdound
- config_foreach process_domains_for_section
- config_foreach sing_box_rule_preset
- config_foreach process_domains_list_local
- config_foreach process_subnet_for_section
- config_foreach configure_community_lists
- config_foreach configure_remote_domain_lists
- config_foreach configure_remote_subnet_lists
- config_foreach process_all_traffic_for_section
- config_foreach add_cron_job
-
- config_foreach prepare_custom_ruleset
- list_update &
- echo $! > /var/run/podkop_list_update.pid
-
- # Future: exclude at the fakeip?
- config_get_bool exclude_from_ip_enabled "main" "exclude_from_ip_enabled" "0"
- if [ "$exclude_from_ip_enabled" -eq 1 ]; then
- log "Adding an IP for exclusion"
- config_list_foreach main exclude_traffic_ip sing_box_rules_source_ip_cidr $exclude_traffic_ip direct-out
- fi
-
- config_get_bool yacd "main" "yacd" "0"
- if [ "$yacd" -eq 1 ]; then
- log "Yacd enable"
- jq '.experimental.clash_api = {
- "external_ui": "ui",
- "external_controller": "0.0.0.0:9090"
- }' "$SING_BOX_CONFIG" | build_sing_box_config
- fi
+ sing_box_init_config
+ sing_box_config_check
+ # TODO(ampetelin): refactoring is needed
+# config_foreach add_cron_job # need refactoring
+ /etc/init.d/sing-box start
+ log "Nice" "main"
+# sing_box_inbound_proxy 1602 #refactored
+# sing_box_dns #refactored
+# sing_box_dns_rule_fakeip #refactored
+# sing_box_rule_dns #refactored
+# sing_box_create_bypass_ruleset #refactored
+# sing_box_add_secure_dns_probe_domain #refactored
+# sing_box_cache_file #refactored
+# process_socks5 #refactored
+#
+# # sing-box outbounds and rules
+# config_foreach sing_box_outdound #refactored
+# config_foreach process_domains_for_section #refactored, implementation is needed
+# config_foreach sing_box_rule_preset #refactored
+# config_foreach process_domains_list_local #refactored, implementation is needed
+# config_foreach process_subnet_for_section #refactored, implementation is needed
+# config_foreach configure_community_lists #refactored
+# config_foreach configure_remote_domain_lists #refactored
+# config_foreach configure_remote_subnet_lists #refactored
+# config_foreach process_all_traffic_for_section #refactored
+ local exclude_ntp
config_get_bool exclude_ntp "main" "exclude_ntp" "0"
if [ "$exclude_ntp" -eq 1 ]; then
log "NTP traffic exclude for proxy"
nft insert rule inet PodkopTable mangle udp dport 123 return
fi
- config_get_bool quic_disable "main" "quic_disable" "0"
- if [ "$quic_disable" -eq 1 ]; then
- log "Rule for disable QUIC"
- sing_box_quic_reject
- fi
-
- config_get_bool detour "main" "detour" "0"
- if [ "$detour" -eq 1 ]; then
- log "Detour mixed enable"
- detour_mixed
- fi
-
- sing_box_config_check
- /etc/init.d/sing-box start
- log "Nice"
+ # TODO(ampetelin): refactoring is needed
+# list_update &
+# echo $! > /var/run/podkop_list_update.pid
}
start() {
start_main
+ local proxy_string interface outbound_json dont_touch_dhcp
config_get proxy_string "main" "proxy_string"
config_get interface "main" "interface"
config_get outbound_json "main" "outbound_json"
@@ -213,6 +199,7 @@ stop_main() {
}
stop() {
+ local dont_touch_dhcp
config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
if [ "$dont_touch_dhcp" -eq 0 ]; then
dnsmasq_restore
@@ -285,6 +272,26 @@ migration() {
# corntab init.d
(crontab -l | grep -v "/etc/init.d/podkop list_update") | crontab -
+
+ migrate_config_key "$CONFIG" "option" "domain_list_enabled" "community_list_enabled"
+ migrate_config_key "$CONFIG" "list" "domain_list" "community_list"
+
+ migrate_config_key "$CONFIG" "option" "custom_domains_list_type" "user_domains_list_type"
+ migrate_config_key "$CONFIG" "option" "custom_domains_text" "user_domains"
+ migrate_config_key "$CONFIG" "list" "custom_domains" "user_domains"
+
+ migrate_config_key "$CONFIG" "option" "custom_subnets_list_enabled" "user_subnets_list_type"
+ migrate_config_key "$CONFIG" "option" "custom_subnets_text" "user_subnets"
+ migrate_config_key "$CONFIG" "list" "custom_subnets" "user_subnets"
+
+ migrate_config_key "$CONFIG" "option" "custom_local_domains_list_enabled" "local_domains_list_enabled"
+ migrate_config_key "$CONFIG" "list" "custom_local_domains" "local_domains_list"
+
+ migrate_config_key "$CONFIG" "option" "custom_download_domains_list_enabled" "remote_domains_list_enabled"
+ migrate_config_key "$CONFIG" "list" "custom_download_domains" "remote_domains_list"
+
+ migrate_config_key "$CONFIG" "option" "custom_download_subnets_list_enabled" "remote_subnets_list_enabled"
+ migrate_config_key "$CONFIG" "list" "custom_download_subnets" "remote_subnets_list"
}
validate_service() {
@@ -301,7 +308,8 @@ validate_service() {
}
process_validate_service() {
- config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
+ 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
fi
@@ -455,7 +463,8 @@ dnsmasq_add_resolver() {
dnsmasq_restore() {
log "Removing configuration for dnsmasq"
- local cachesize=$(uci get dhcp.@dnsmasq[0].podkop_cachesize 2>/dev/null)
+ local cachesize noresolv server
+ cachesize=$(uci get dhcp.@dnsmasq[0].podkop_cachesize 2>/dev/null)
if [[ "$cachesize" == "unset" ]]; then
log "dnsmasq revert: cachesize is unset"
uci -q delete dhcp.@dnsmasq[0].cachesize
@@ -463,7 +472,7 @@ dnsmasq_restore() {
uci set dhcp.@dnsmasq[0].cachesize="$cachesize"
fi
- local noresolv=$(uci get dhcp.@dnsmasq[0].podkop_noresolv 2>/dev/null)
+ noresolv=$(uci get dhcp.@dnsmasq[0].podkop_noresolv 2>/dev/null)
if [[ "$noresolv" == "unset" ]]; then
log "dnsmasq revert: noresolv is unset"
uci -q delete dhcp.@dnsmasq[0].noresolv
@@ -471,7 +480,7 @@ dnsmasq_restore() {
uci set dhcp.@dnsmasq[0].noresolv="$noresolv"
fi
- local server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
+ server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
if [[ "$server" == "127.0.0.42" ]]; then
uci -q delete dhcp.@dnsmasq[0].server 2>/dev/null
for server in $(uci get dhcp.@dnsmasq[0].podkop_server 2>/dev/null); do
@@ -488,49 +497,7 @@ dnsmasq_restore() {
/etc/init.d/dnsmasq restart
}
-process_domains_text() {
- local text="$1"
- local name="$2"
-
- local tmp_file=$(mktemp)
- echo "$text" > "$tmp_file"
-
- # First filter out full comment lines and remove comments after domains
- grep -v "^[[:space:]]*\/\/" "$tmp_file" | sed 's/\/\/.*$//' > "${tmp_file}.filtered"
-
- sed 's/[, ]\+/\n/g' "${tmp_file}.filtered" | while IFS= read -r domain; do
- domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$domain" ]; then
- sing_box_ruleset_domains "$domain" "$name"
- fi
- done
-
- rm -f "$tmp_file" "${tmp_file}.filtered"
-}
-
-process_subnets_text() {
- local text="$1"
- local name="$2"
-
- local tmp_file=$(mktemp)
- echo "$text" > "$tmp_file"
-
- # First filter out full comment lines and remove comments after subnets
- grep -v "^[[:space:]]*\/\/" "$tmp_file" | sed 's/\/\/.*$//' > "${tmp_file}.filtered"
-
- sed 's/[, ]\+/\n/g' "${tmp_file}.filtered" | while IFS= read -r subnet; do
- subnet=$(echo "$subnet" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$subnet" ]; then
- if ! echo "$subnet" | grep -q "/"; then
- subnet="$subnet/32"
- fi
- sing_box_ruleset_subnets "$subnet" "$name"
- fi
- done
-
- rm -f "$tmp_file" "${tmp_file}.filtered"
-}
-
+# TODO(ampetelin): refactoring is needed
add_cron_job() {
## Future: make a check so that it doesn't recreate many times
config_get domain_list_enabled "$section" "domain_list_enabled"
@@ -577,36 +544,7 @@ remove_cron_job() {
log "The cron job removed"
}
-prepare_custom_ruleset() {
- config_get custom_download_domains_list_enabled "$section" "custom_download_domains_list_enabled"
- config_get custom_download_subnets_list_enabled "$section" "custom_download_subnets_list_enabled"
- if [ "$custom_download_domains_list_enabled" -eq 1 ] || [ "$custom_download_subnets_list_enabled" -eq 1 ]; then
- local file="/tmp/podkop/$section-custom-domains-subnets.json"
- local tag="custom-$section"
- rm -f $file
-
- jq -n '
- {
- "version": 3,
- "rules": []
- }' > $file
-
- jq --arg tag "$tag" \
- --arg file "$file" \
- '.route.rule_set += [{
- "tag": $tag,
- "type": "local",
- "format": "source",
- "path": $file
- }]' "$SING_BOX_CONFIG" | build_sing_box_config
-
- sing_box_rules $tag $section
- sing_box_dns_rule_fakeip_section $tag $tag
-
- log "Added $tag rule_set to sing-box config"
- fi
-}
-
+# TODO(ampetelin): refactoring is needed
list_update() {
echolog "๐ Starting lists update..."
@@ -663,7 +601,6 @@ list_update() {
}
find_working_resolver() {
- local resolver_found=""
for resolver in $DNS_RESOLVERS; do
if nslookup -timeout=2 $TEST_DOMAIN $resolver >/dev/null 2>&1; then
echo "$resolver"
@@ -693,783 +630,447 @@ sing_box_uci() {
# fi
}
-add_socks5_for_section() {
- local section="$1"
- local port="$2"
- local tag="$section-mixed-in"
+sing_box_init_config() {
+ local config='{"log":{},"dns":{},"ntp":{},"certificate":{},"endpoints":[],"inbounds":[],"outbounds":[],"route":{},"services":[],"experimental":{}}'
- log "Adding Socks5 for $section on port $port"
+ sing_box_configure_log
+ sing_box_configure_inbounds
+ sing_box_configure_outbounds
+ sing_box_configure_dns
+ sing_box_configure_route
+ sing_box_configure_experimental
+ sing_box_additional_inbounds
- jq \
- --arg tag "$tag" \
- --arg port "$port" \
- --arg section "$section" \
- '.inbounds += [{
- "tag": $tag,
- "type": "mixed",
- "listen": "0.0.0.0",
- "listen_port": ($port|tonumber),
- "set_system_proxy": false
- }] |
- .route.rules += [{
- "inbound": [$tag],
- "outbound": $section,
- "action": "route"
- }]' "$SING_BOX_CONFIG" | build_sing_box_config
+ # TODO: remove after refactoring
+ nolog "$config"
+
+ sing_box_cm_save_config_to_file "$config" "$SB_CONFIG"
}
-process_socks5() {
- config_get_bool main_socks5 "main" "socks5" "0"
- if [ "$main_socks5" -eq 1 ]; then
- add_socks5_for_section "main" "2080"
- fi
+sing_box_configure_log() {
+ log "Configure the log section of a sing-box JSON configuration" "sing-box"
- local port=2081
- for section in $(uci show podkop | awk -F'[.=]' '/=extra/ {print $2}'); do
- config_get_bool section_socks5 "$section" "socks5" "0"
- if [ "$section_socks5" -eq 1 ]; then
- add_socks5_for_section "$section" "$port"
- port=$((port + 1))
- fi
- done
+ config=$(sing_box_cm_configure_log "$config" false "$SB_DEFAULT_LOG_LEVEL" false)
}
-sing_box_inbound_proxy() {
- local listen_port="$1"
+sing_box_configure_inbounds() {
+ log "Configure the inbounds section of a sing-box JSON configuration" "sing-box"
- jq -n \
- --arg listen_port "$listen_port" \
- '{
- "log": {
- "level": "warn"
- },
- "inbounds": [
- {
- "tag": "tproxy-in",
- "type": "tproxy",
- "listen": "::",
- "listen_port": ($listen_port|tonumber),
- "tcp_fast_open": true,
- "udp_fragment": true
- },
- {
- "tag": "dns-in",
- "type": "direct",
- "listen": "127.0.0.42",
- "listen_port": 53
- }
- ],
- "outbounds": [
- {
- "tag": "direct-out",
- "type": "direct"
- }
- ]
- }' > $SING_BOX_CONFIG
+ config=$(
+ sing_box_cm_add_tproxy_inbound \
+ "$config" "$SB_TPROXY_INBOUND_TAG" "$SB_TPROXY_INBOUND_ADDRESS" "$SB_TPROXY_INBOUND_PORT" true true
+ )
+ config=$(
+ sing_box_cm_add_direct_inbound "$config" "$SB_DNS_INBOUND_TAG" "$SB_DNS_INBOUND_ADDRESS" "$SB_DNS_INBOUND_PORT"
+ )
}
-sing_box_dns() {
- local dns_type
- local dns_server
- local resolver_tag="resolver"
- local split_resolver_tag="split-resolver"
+sing_box_configure_outbounds() {
+ log "Configure the outbounds section of a sing-box JSON configuration" "sing-box"
- config_get dns_type "main" "dns_type" "doh"
- config_get dns_server "main" "dns_server" "1.1.1.1"
- config_get split_dns_enabled "main" "split_dns_enabled" "0"
- config_get split_dns_type "main" "split_dns_type" "udp"
- config_get split_dns_server "main" "split_dns_server" "1.1.1.1"
+ config=$(sing_box_cm_add_direct_outbound "$config" "$SB_DIRECT_OUTBOUND_TAG")
- local server_json
- local is_ip=$(echo "$dns_server" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' && echo "1" || echo "0")
-
- if [ "$is_ip" = "0" ]; then
- log "Finding working DNS resolver"
- local dns_resolver=$(find_working_resolver)
- if [ -z "$dns_resolver" ]; then
- log "No working resolver found, using default DNS server"
- dns_resolver="1.1.1.1"
- else
- log "Found working resolver: $dns_resolver"
- fi
- fi
-
- log "Configure DNS in sing-box"
-
- server_json=$(jq -n \
- --arg type "$dns_type" \
- --arg server "$dns_server" \
- --arg resolver "$resolver_tag" \
- --arg is_ip "$is_ip" \
- '{
- "servers": [
- {
- "tag": "dns-server",
- "address": (
- if $type == "doh" then
- "https://" + $server + "/dns-query"
- elif $type == "dot" then
- "tls://" + $server
- else
- $server
- end
- ),
- "detour": "direct-out"
- } + (
- if $is_ip == "0" then
- {"address_resolver": $resolver}
- else
- {}
- end
- )
- ]
- }')
-
- if [ "$is_ip" = "0" ]; then
- server_json=$(echo "$server_json" | jq \
- --arg resolver "$resolver_tag" \
- --arg address "$dns_resolver" \
- '.servers += [{
- "tag": $resolver,
- "address": $address
- }]')
- fi
-
- if [ "$split_dns_enabled" = "1" ]; then
- local split_is_ip=$(echo "$split_dns_server" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' && echo "1" || echo "0")
- if [ "$split_is_ip" = "0" ]; then
- log "Finding working resolver for split DNS"
- local split_dns_resolver=$(find_working_resolver)
- if [ -z "$split_dns_resolver" ]; then
- log "No working resolver found for split DNS, using default"
- split_dns_resolver="1.1.1.1"
- else
- log "Found working resolver for split DNS: $split_dns_resolver"
- fi
- fi
-
- server_json=$(echo "$server_json" | jq \
- --arg type "$split_dns_type" \
- --arg server "$split_dns_server" \
- --arg split_is_ip "$split_is_ip" \
- --arg split_resolver_tag "$split_resolver_tag" \
- ' .servers += [
- {
- "tag": "split-dns-server",
- "address": (
- if $type == "doh" then
- "https://" + $server + "/dns-query"
- elif $type == "dot" then
- "tls://" + $server
- else
- $server
- end
- ),
- "detour": "main"
- } + (
- if $split_is_ip == "0" then
- {"address_resolver": $split_resolver_tag}
- else
- {}
- end
- )
- ]')
-
- if [ "$split_is_ip" = "0" ]; then
- server_json=$(echo "$server_json" | jq \
- --arg split_resolver_tag "$split_resolver_tag" \
- --arg split_dns_resolver "$split_dns_resolver" \
- '.servers += [{
- "tag": $split_resolver_tag,
- "address": $split_dns_resolver
- }]')
- fi
- fi
-
- server_json=$(echo "$server_json" | jq '.servers += [{"tag": "fakeip-server", "address": "fakeip"}]')
-
- jq \
- --argjson dns_config "$server_json" \
- --arg fakeip "$FAKEIP" \
- --argjson split_dns_enabled "$split_dns_enabled" \
- '.dns = {
- "strategy": "ipv4_only",
- "independent_cache": true,
- "final": (
- if $split_dns_enabled == 1 then
- "split-dns-server"
- else
- "dns-server"
- end
- ),
- "fakeip": {
- "enabled": true,
- "inet4_range": $fakeip
- },
- "servers": $dns_config.servers
- }' "$SING_BOX_CONFIG" | build_sing_box_config
+ config_foreach configure_outbound_handler
}
-sing_box_create_bypass_ruleset() {
- log "Creating bypass ruleset for direct access"
-
- jq '
- .route.rule_set += [{
- "tag": "bypass",
- "type": "inline",
- "rules": [
- {
- "domain_suffix": [
- "ip.podkop.fyi"
- ]
- }
- ]
- }]' "$SING_BOX_CONFIG" | build_sing_box_config
-
- # Add a rule to route bypass domains to direct-out outbound
- jq '
- .route.rules += [{
- "inbound": ["tproxy-in"],
- "rule_set": ["bypass"],
- "outbound": "main",
- "action": "route"
- }]' "$SING_BOX_CONFIG" | build_sing_box_config
-
- # Make sure the bypass ruleset is in the fakeip DNS rule
- jq '
- .dns.rules = (.dns.rules | map(
- if (.server == "fakeip-server" or (.server == "dns-server" and .invert == true)) then
- if any(.rule_set[]?; . == "bypass") then
- .
- else
- .rule_set += ["bypass"]
- end
- else
- .
- end
- ))' "$SING_BOX_CONFIG" | build_sing_box_config
-}
-
-sing_box_dns_rule_fakeip() {
- local rewrite_ttl
- config_get rewrite_ttl "main" "dns_rewrite_ttl" "60"
- config_get split_dns_enabled "main" "split_dns_enabled" "0"
-
- log "Configure fakeip route in sing-box and set TTL to $rewrite_ttl seconds"
-
- jq \
- --arg ttl "$rewrite_ttl" \
- --argjson split_dns_enabled "$split_dns_enabled" \
- '.dns.rules = [
- {
- "query_type": [
- "HTTPS"
- ],
- "action": "reject"
- },
- {
- "domain_suffix": [
- "use-application-dns.net"
- ],
- "action": "reject"
- },
- {
- "server": "fakeip-server",
- "domain": "",
- "rewrite_ttl": ($ttl | tonumber),
- "rule_set": []
- }
- ]
- + (
- if $split_dns_enabled == 1 then
- [{
- "server": "dns-server",
- "domain": "",
- "invert": true,
- "rule_set": []
- }]
- else []
- end
- )' "$SING_BOX_CONFIG" | build_sing_box_config
-}
-
-sing_box_dns_rule_fakeip_section() {
- local rule_set=$1
-
- log "Adding section to fakeip route rules in sing-box"
-
- jq \
- --arg rule_set "$rule_set" \
- '.dns.rules |= map(
- if (.server == "fakeip-server" or (.server == "dns-server" and .invert == true)) then
- if any(.rule_set[]?; . == $rule_set) then
- .
- else
- .rule_set += [$rule_set]
- end
- else
- .
- end
- )' "$SING_BOX_CONFIG" | build_sing_box_config
-}
-
-sing_box_cache_file() {
- config_get cache_file "main" "cache_file" "/tmp/cache.db"
-
- log "Configure sing-box cache.db path"
-
- jq \
- --arg cache_file "$cache_file" \
- '.experimental = {
- "cache_file": {
- "enabled": true,
- "store_fakeip": true,
- "path": $cache_file
- }
- }' "$SING_BOX_CONFIG" | build_sing_box_config
-}
-
-sing_box_outdound() {
+configure_outbound_handler() {
local section="$1"
- config_get mode "$section" "mode"
- case "$mode" in
- "vpn")
- log "VPN mode"
- log "You are using VPN mode, make sure you have installed all the necessary packages and configured."
- config_get interface "$section" "interface"
-
- if [ -z "$interface" ]; then
- log "[critical] VPN interface is not set. Exit"
- exit 1
- fi
-
- sing_box_outbound_interface $section $interface
- ;;
- "proxy")
- log "Proxy mode"
+ local connection_mode
+ config_get connection_mode "$section" "mode"
+ case "$connection_mode" in
+ proxy)
+ log "Configuring outbound in proxy connection mode for the $section section" "sing-box"
+ local proxy_config_type
config_get proxy_config_type "$section" "proxy_config_type"
- if [ "$proxy_config_type" = "outbound" ]; then
- config_get outbound_json $section "outbound_json"
- if [ -n "$outbound_json" ]; then
- log "Using JSON outbound configuration"
- sing_box_config_outbound_json "$outbound_json" "$section"
- else
- log "Missing outbound JSON configuration"
- return
- fi
- else
- config_get proxy_string $section "proxy_string"
-
+ case "$proxy_config_type" in
+ url)
+ log "Detected proxy configuration type: url" "sing-box"
+ local proxy_string udp_over_tcp
+ config_get proxy_string "$section" "proxy_string"
+ config_get udp_over_tcp "$section" "ss_uot"
# Extract the first non-comment line as the active configuration
active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1)
if [ -z "$active_proxy_string" ]; then
- log "[critical] Proxy string is not set. Exit"
+ log "Proxy string is not set. Aborted." "sing-box" "fatal"
exit 1
fi
-
- if [[ "$active_proxy_string" =~ ^ss:// ]]; then
- config_get ss_uot $section "ss_uot"
- sing_box_config_shadowsocks "$section" "$active_proxy_string" "$ss_uot"
- elif [[ "$active_proxy_string" =~ ^vless:// ]]; then
- sing_box_config_vless "$section" "$active_proxy_string"
- else
- log "Unsupported proxy type or missing configuration"
- return
- fi
- fi
+ config=$(sing_box_cf_add_proxy_outbound "$config" "$section" "$active_proxy_string" "$udp_over_tcp")
+ ;;
+ outbound)
+ log "Detected proxy configuration type: outbound" "sing-box"
+ local json_outbound
+ config_get json_outbound "$section" "outbound_json"
+ config=$(sing_box_cf_add_json_outbound "$config" "$section" "$json_outbound")
+ ;;
+ *)
+ log "Unknown proxy configuration type: '$proxy_config_type'. Aborted." "sing-box" "fatal"
+ exit 1
+ ;;
+ esac
;;
- "block")
- log "Block mode"
+ vpn)
+ log "Configuring outbound in VPN connection mode for the $section section" "sing-box"
+ local interface_name
+ config_get interface_name "$section" "interface"
+
+ if [ -z "$interface_name" ]; then
+ log "VPN interface is not set. Aborted." "sing-box" "fatal"
+ exit 1
+ fi
+
+ config=$(sing_box_cf_add_interface_outbound "$config" "$section" "$interface_name")
+ ;;
+ block)
+ log "Connection mode 'block' detected for the $section section โ no outbound will be created (handled via reject route rules)" "sing-box"
+ # TODO(ampetelin): ะะฐะดะพ ะฝะต ะทะฐะฑััั
;;
*)
- log "Requires *vpn* or *proxy* value"
- return
+ log "Unknown connection mode '$connection_mode' for the $section section. Aborted." "sing-box" "fatal"
+ exit 1
;;
esac
}
-sing_box_outbound_interface() {
- local section="$1"
- local interface="$2"
-
- jq --arg section "$section" \
- --arg interface "$interface" \
- '. |
- .outbounds |= (
- map(
- if .tag == $section then
- . + {"type": "direct", "bind_interface": $interface}
- else . end
- ) +
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [{"tag": $section, "type": "direct", "bind_interface": $interface}]
- else [] end
- )
- )' "$SING_BOX_CONFIG" | build_sing_box_config
-
- if [ $? -eq 0 ]; then
- log "Config updated successfully"
+sing_box_configure_dns() {
+ log "Configure the DNS section of a sing-box JSON configuration" "sing-box"
+ local split_dns_enabled final_dns_server
+ config_get_bool split_dns_enabled "main" "split_dns_enabled" 0
+ if [ "$split_dns_enabled" -eq 1 ]; then
+ final_dns_server="$SB_SPLIT_DNS_SERVER_TAG"
else
- log "Error: Invalid JSON config generated"
- return 1
+ final_dns_server="$SB_DNS_SERVER_TAG"
+ fi
+ config=$(sing_box_cm_configure_dns "$config" "$final_dns_server" "ipv4_only" true)
+
+ local dns_type dns_server split_dns_type split_dns_server
+ config_get dns_type "main" "dns_type" "doh"
+ config_get dns_server "main" "dns_server" "1.1.1.1"
+ config_get split_dns_type "main" "split_dns_type" "udp"
+ config_get split_dns_server "main" "split_dns_server" "1.1.1.1"
+
+ local need_dns_domain_resolver=0
+ if ! is_ipv4 "$dns_server" || ! is_ipv4 "$split_dns_server"; then
+ need_dns_domain_resolver=1
+ fi
+
+ log "Adding DNS Servers" "sing-box"
+ config=$(sing_box_cm_add_fakeip_dns_server "$config" "$SB_FAKEIP_DNS_SERVER_TAG" "$FAKEIP")
+
+ local dns_domain_resolver
+ if [ "$need_dns_domain_resolver" -eq 1 ]; then
+ log "One of the DNS server addresses is a domain. Searching for a working DNS server..." "sing-box"
+ dns_domain_resolver=$(find_working_resolver)
+ if [ -z "$dns_domain_resolver" ]; then
+ log "Working DNS server not found, using default DNS server" "sing-box"
+ dns_domain_resolver="1.1.1.1"
+ else
+ log "Working DNS server has been found: $dns_domain_resolver" "sing-box"
+ fi
+ config=$(sing_box_cm_add_udp_dns_server "$config" "$SB_DNS_DOMAIN_RESOLVER_TAG" "$dns_domain_resolver" 53)
+ fi
+
+ config=$(
+ sing_box_cf_add_dns_server "$config" "$dns_type" "$SB_DNS_SERVER_TAG" "$dns_server" "" "" \
+ "$SB_DNS_DOMAIN_RESOLVER_TAG" "$SB_DIRECT_OUTBOUND_TAG"
+ )
+
+ if [ "$split_dns_enabled" -eq 1 ]; then
+ config=$(
+ sing_box_cf_add_dns_server "$config" "$split_dns_type" "$SB_SPLIT_DNS_SERVER_TAG" "$split_dns_server" \
+ "" "" "$SB_DNS_DOMAIN_RESOLVER_TAG" "$SB_MAIN_OUTBOUND_TAG"
+ )
+ fi
+
+ log "Adding DNS Rules" "sing-box"
+ local rewrite_ttl service_domains
+ config_get rewrite_ttl "main" "dns_rewrite_ttl" "60"
+
+ config=$(sing_box_cm_add_dns_reject_rule "$config" "query_type" "HTTPS")
+ config=$(sing_box_cm_add_dns_reject_rule "$config" "domain_suffix" '"use-application-dns.net"')
+ config=$(sing_box_cm_add_dns_route_rule "$config" "$SB_FAKEIP_DNS_SERVER_TAG" "$SB_FAKEIP_DNS_RULE_TAG")
+ config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rewrite_ttl" "$rewrite_ttl")
+ service_domains=$(comma_string_to_json_array "$TEST_DOMAIN,$CHECK_PROXY_IP_DOMAIN")
+ config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "domain" "$service_domains")
+ if [ "$split_dns_enabled" -eq 1 ]; then
+ config=$(sing_box_cm_add_dns_route_rule "$config" "$SB_DNS_SERVER_TAG" "$SB_INVERT_FAKEIP_DNS_RULE_TAG")
+ config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_INVERT_FAKEIP_DNS_RULE_TAG" "invert" true)
+ config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_INVERT_FAKEIP_DNS_RULE_TAG" "domain" "$service_domains")
fi
}
-sing_box_rule_dns() {
- log "Configure rule dns in sing-box"
- jq \
- '.route += {
- "rules": [
- {
- "inbound": [
- "dns-in",
- "tproxy-in"
- ],
- "action": "sniff"
- },
- {
- "protocol": "dns",
- "action": "hijack-dns"
- }
- ],
- "auto_detect_interface": true
- }' "$SING_BOX_CONFIG" | build_sing_box_config
+sing_box_configure_route() {
+ log "Configure the route section of a sing-box JSON configuration" "sing-box"
+
+ config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG")
+
+ local sniff_inbounds mixed_inbound_enabled
+ config_get_bool mixed_inbound_enabled "main" "socks5" 0
+ if [ "$mixed_inbound_enabled" -eq 1 ]; then
+ sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG,$SB_MIXED_INBOUND_TAG")
+ else
+ sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG")
+ fi
+ config=$(sing_box_cm_sniff_route_rule "$config" "inbound" "$sniff_inbounds")
+
+ config=$(sing_box_cm_add_hijack_dns_route_rule "$config" "protocol" "dns")
+
+ local quic_disable
+ config_get_bool quic_disable "main" "quic_disable" 0
+ if [ "$quic_disable" -eq 1 ]; then
+ config=$(sing_box_cm_add_reject_route_rule "$config" "protocol" "quic")
+ fi
+
+ config=$(
+ sing_box_cf_proxy_domain "$config" "$SB_TPROXY_INBOUND_TAG" "$CHECK_PROXY_IP_DOMAIN" "$SB_MAIN_OUTBOUND_TAG"
+ )
+ config=$(sing_box_cf_override_domain_port "$config" "$TEST_DOMAIN" 8443)
+
+ config_foreach include_source_ips_in_routing_handler
+
+ local exclude_from_ip_enabled
+ config_get_bool exclude_from_ip_enabled "main" "exclude_from_ip_enabled" 0
+ if [ "$exclude_from_ip_enabled" -eq 1 ]; then
+ rule_tag="$(gen_id)"
+ config=$(sing_box_cm_add_route_rule "$config" "$rule_tag" "$SB_TPROXY_INBOUND_TAG" "$SB_DIRECT_OUTBOUND_TAG")
+ config_list_foreach "main" "exclude_traffic_ip" exclude_source_ip_from_routing_handler "$rule_tag"
+ fi
+
+ config_foreach configure_routing_for_section_lists
+}
+
+include_source_ips_in_routing_handler() {
+ local section="$1"
+
+ local all_traffic_from_ip_enabled rule_tag
+ config_get all_traffic_from_ip_enabled "$section" "all_traffic_from_ip_enabled" 0
+ if [ "$all_traffic_from_ip_enabled" -eq 1 ]; then
+ rule_tag="$(gen_id)"
+ config=$(
+ sing_box_cm_add_route_rule \
+ "$config" "$rule_tag" "$SB_TPROXY_INBOUND_TAG" "$(get_outbound_tag_by_section "$section")"
+ )
+ config_list_foreach "$section" "all_traffic_ip" include_source_ip_in_routing_handler "$rule_tag"
+ fi
+}
+
+include_source_ip_in_routing_handler() {
+ local source_ip="$1"
+ local rule_tag="$2"
+ nft_list_all_traffic_from_ip "$source_ip"
+ config=$(sing_box_cm_patch_route_rule "$config" "$rule_tag" "source_ip_cidr" "$source_ip")
+}
+
+exclude_source_ip_from_routing_handler() {
+ local source_ip="$1"
+ local rule_tag="$2"
+
+ config=$(sing_box_cm_patch_route_rule "$config" "$rule_tag" "source_ip_cidr" "$source_ip")
+}
+
+configure_routing_for_section_lists() {
+ local section="$1"
+ local community_list_enabled local_domains_list_enabled remote_domains_list_enabled remote_subnets_list_enabled
+ local user_domains_list_type user_subnets_list_type route_rule_tag
+ config_get_bool community_list_enabled "$section" "community_list_enabled" 0
+ config_get user_domains_list_type "$section" "user_domains_list_type" "disabled"
+ config_get_bool local_domains_list_enabled "$section" "local_domains_list_enabled" 0
+ config_get_bool remote_domains_list_enabled "$section" "remote_domains_list_enabled" 0
+ config_get user_subnets_list_type "$section" "user_subnets_list_type" "disabled"
+ config_get_bool remote_subnets_list_enabled "$section" "remote_subnets_list_enabled" 0
+
+ if [ "$community_list_enabled" -eq 0 ] && \
+ [ "$user_domains_list_type" == "disabled" ] && \
+ [ "$local_domains_list_enabled" -eq 0 ] && \
+ [ "$remote_domains_list_enabled" -eq 0 ] && \
+ [ "$user_subnets_list_type" == "disabled" ] && \
+ [ "$remote_subnets_list_enabled" == 0 ] ; then
+ log "Section $section does not have any enabled list, skipping..." "sing-box" "warn"
+ return 0
+ fi
+
+ route_rule_tag="$(gen_id)"
+ outbound_tag=$(get_outbound_tag_by_section "$section")
+ config=$(sing_box_cm_add_route_rule "$config" "$route_rule_tag" "$SB_TPROXY_INBOUND_TAG" "$outbound_tag")
+
+ if [ "$community_list_enabled" -eq 1 ]; then
+ log "Processing community list routing rules for $section section" "sing-box"
+ config_list_foreach "$section" "community_list" configure_community_list_handler "$section" "$route_rule_tag"
+ fi
+
+ if [ "$user_domains_list_type" != "disabled" ]; then
+ log "Processing user domains routing rules for $section section" "sing-box"
+ # TODO(ampetelin): it is necessary to implement
+ # configure_user_domains_list_handler
+ fi
+
+ if [ "$local_domains_list_enabled" -eq 1 ]; then
+ log "Processing local domains routing rules for $section section" "sing-box"
+ # TODO(ampetelin): it is necessary to implement
+ # configure_local_domains_list_handler "$section" "$route_rule_tag"
+ fi
+
+ if [ "$remote_domains_list_enabled" -eq 1 ]; then
+ log "Processing local domains routing rules for $section section" "sing-box"
+ config_list_foreach "$section" "remote_domains_list" configure_remote_domains_or_subnets_list_handler \
+ "domains" "$section" "$route_rule_tag"
+ fi
+
+ if [ "$user_subnets_list_type" != "disabled" ]; then
+ log "Processing user subnets routing rules for $section section" "sing-box"
+ # TODO(ampetelin): it is necessary to implement
+ # configure_user_subnets_list_handler
+ fi
+
+ if [ "$remote_subnets_list_enabled" -eq 1 ]; then
+ log "Processing remote subnets routing rules for $section section" "sing-box"
+ config_list_foreach "$section" "remote_subnets_list" configure_remote_domains_or_subnets_list_handler \
+ "subnets" "$section" "$route_rule_tag"
+ fi
+}
+
+configure_community_list_handler() {
+ local tag="$1"
+ local section="$2"
+ local route_rule_tag="$3"
+
+ local rule_set_tag format url update_interval detour
+ rule_set_tag="$(get_rule_set_tag "$section" "$tag" "community")"
+ format="binary"
+ url="$SRS_MAIN_URL/$tag.srs"
+ detour="$(_get_download_detour_tag)"
+ config_get update_interval "main" "update_interval" "1d"
+
+ config=$(sing_box_cm_add_remote_ruleset "$config" "$rule_set_tag" "$format" "$url" "$detour" "$update_interval")
+ _add_rule_set_to_dns_rules "$rule_set_tag"
+ config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$rule_set_tag")
+}
+
+configure_user_domains_list_handler() {
+ local section="$1"
+ # TODO(ampetelin): it is necessary to implement
+}
+
+configure_local_domains_list_handler() {
+ local section="$1"
+ # TODO(ampetelin): it is necessary to implement
+}
+
+configure_remote_domains_or_subnets_list_handler() {
+ local url="$1"
+ local type="$2"
+ local section="$3"
+ local route_rule_tag="$4"
+
+ local file_extension
+ file_extension=$(get_url_file_extension "$url")
+ case "$file_extension" in
+ json|srs)
+ log "Detected file extension: .$file_extension โ proceeding with processing" "sing-box" "debug"
+
+ local basename rule_set_tag format detour update_interval
+ basename=$(url_get_basename "$url")
+ rule_set_tag=$(get_rule_set_tag "$section" "$basename" "remote-$type")
+ format="$(get_rule_set_format_by_file_extension "$file_extension")"
+ detour="$(_get_download_detour_tag)"
+ config_get update_interval "main" "update_interval" "1d"
+
+ config=$(sing_box_cm_add_remote_ruleset "$config" "$rule_set_tag" "$format" "$url" "$update_interval")
+ config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$rule_set_tag")
+
+ case "$type" in
+ domains) _add_rule_set_to_dns_rules "$rule_set_tag" "$route_rule_tag" ;;
+ subnets) ;;
+ *) log "Unsupported remote rule set type: $type" "sing-box" "warn" ;;
+ esac
+ ;;
+ *)
+ log "Detected file extension: .$file_extension โ no processing needed, managed on list_update" "sing-box"
+ # TODO(ampetelin): create rule set here?
+ ;;
+ esac
+}
+
+configure_user_subnets_list_handler() {
+ local section="$1"
+ # TODO(ampetelin): it is necessary to implement
+}
+
+_get_download_detour_tag() {
+ config_get_bool detour "main" "detour" 0
+ if [ "${detour:-0}" -eq 1 ]; then
+ echo "$SB_MAIN_OUTBOUND_TAG"
+ else
+ echo ""
+ fi
+}
+
+_add_rule_set_to_dns_rules() {
+ local rule_set_tag="$1"
+
+ config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$rule_set_tag")
+ local split_dns_enabled final_dns_server
+ config_get_bool split_dns_enabled "main" "split_dns_enabled" 0
+ if [ "$split_dns_enabled" -eq 1 ]; then
+ config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_INVERT_FAKEIP_DNS_RULE_TAG" "rule_set" "$rule_set_tag")
+ fi
+}
+
+sing_box_configure_experimental() {
+ log "Configure the experimental section of a sing-box JSON configuration" "sing-box"
+
+ log "Configuring cache database" "sing-box"
+ local cache_file
+ config_get cache_file "main" "cache_file" "/tmp/cache.db"
+ config=$(sing_box_cm_configure_cache_file "$config" true "$cache_file" true)
+
+ local yacd_enabled
+ config_get_bool yacd_enabled "main" "yacd" 0
+ if [ "$yacd_enabled" -eq 1 ]; then
+ log "Configuring Clash API (yacd)" "sing-box"
+ local external_controller="0.0.0.0:9090"
+ local external_controller_ui="ui"
+ config=$(sing_box_cm_configure_clash_api "$config" "$external_controller" "$external_controller_ui")
+ else
+ log "Clash API (yacd) is disabled, skipping configuration." "sing-box"
+ fi
+}
+
+sing_box_additional_inbounds() {
+ log "Configure the additional inbounds of a sing-box JSON configuration" "sing-box"
+
+ local mixed_inbound_enabled
+ config_get_bool mixed_inbound_enabled "main" "socks5" 0
+ if [ "$mixed_inbound_enabled" -eq 1 ]; then
+ config=$(
+ sing_box_cf_add_mixed_inbound_and_route_rule \
+ "$config" \
+ "$SB_MIXED_INBOUND_TAG" \
+ "$SB_MIXED_INBOUND_ADDRESS" \
+ "$SB_MIXED_INBOUND_PORT" \
+ "$SB_MAIN_OUTBOUND_TAG"
+ )
+ fi
+
+ config=$(
+ sing_box_cf_add_mixed_inbound_and_route_rule \
+ "$config" \
+ "$SB_SERVICE_MIXED_INBOUND_TAG" \
+ "$SB_SERVICE_MIXED_INBOUND_ADDRESS" \
+ "$SB_SERVICE_MIXED_INBOUND_PORT" \
+ "$SB_MAIN_OUTBOUND_TAG"
+ )
}
sing_box_config_check() {
- if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then
- log "[critical] Sing-box configuration is invalid"
+ if ! sing-box -c $SB_CONFIG check >/dev/null 2>&1; then
+ log "Sing-box configuration is invalid" "sing-box" "[fatal]"
exit 1
fi
}
-sing_box_config_outbound_json() {
- local json_config="$1"
- local section="$2"
-
- # Create new object with tag first, then merge with the rest of the config
- local modified_config=$(echo "$json_config" | jq --arg section "$section" \
- 'del(.tag) | {"tag": $section} + .')
-
- jq --argjson outbound "$modified_config" \
- --arg section "$section" \
- '. |
- .outbounds |= (
- map(
- if .tag == $section then
- $outbound
- else . end
- ) +
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [$outbound]
- else [] end
- )
- )' "$SING_BOX_CONFIG" | build_sing_box_config
-
- if [ $? -eq 0 ]; then
- log "Outbound config updated successfully"
- else
- log "Error: Outbound invalid JSON config generated"
- return 1
- fi
-}
-
-sing_box_config_shadowsocks() {
- local section="$1"
- local STRING="$2"
- ss_uot="${3:-0}"
-
- if echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null | grep -q ":"; then
- local encrypted_part=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null )
- local method=$(echo "$encrypted_part" | cut -d':' -f1)
- local password=$(echo "$encrypted_part" | cut -d':' -f2-)
- else
- local method_and_password=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1)
- local method=$(echo "$method_and_password" | cut -d':' -f1)
- local password=$(echo "$method_and_password" | cut -d':' -f2- | sed 's/%3D/=/g')
- if echo "$method" | base64 -d ; then
- method=$(echo "$method" | base64 -d)
- fi
- fi
-
- local server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1)
- local port=$(echo "$STRING" | sed -n 's|.*:\([0-9]\+\).*|\1|p')
-
- jq \
- --arg section "$section" \
- --arg server "$server" \
- --argjson port "$port" \
- --arg method "$method" \
- --arg password "$password" \
- --argjson ss_uot "$ss_uot" \
- '. |
- .outbounds |= (
- map(
- if .tag == $section then
- . + {
- "type": "shadowsocks",
- "server": $server,
- "server_port": ($port | tonumber),
- "method": $method,
- "password": $password
- } + (if $ss_uot == 1 then { "udp_over_tcp": { "enabled": true, "version": 2 } } else {} end)
- else . end
- ) +
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [{
- "tag": $section,
- "type": "shadowsocks",
- "server": $server,
- "server_port": ($port | tonumber),
- "method": $method,
- "password": $password
- } + (if $ss_uot == 1 then { "udp_over_tcp": { "enabled": true, "version": 2 } } else {} end)]
- else [] end
- )
- )' "$SING_BOX_CONFIG" | build_sing_box_config
-
- if [ $? -eq 0 ]; then
- log "Config Shadowsocks updated successfully"
- else
- log "Error: Shadowsocks invalid JSON config generated"
- return 1
- fi
-}
-
-sing_box_config_vless() {
- local section="$1"
- local STRING="$2"
-
- get_param() {
- local param="$1"
- local value=$(echo "$STRING" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p")
- value=$(echo "$value" | sed 's/%2F/\//g; s/%2C/,/g; s/%3D/=/g; s/%2B/+/g; s/%20/ /g; s/%3B/;/g' | tr -d '\n' | tr -d '\r')
- echo "$value"
- }
-
- uuid=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
- server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
- port=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f2 | cut -d'?' -f1 | cut -d'/' -f1 | cut -d'#' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
-
- jq \
- --arg server "$server" \
- --argjson port "$port" \
- --arg uuid "$uuid" \
- --arg type "$(get_param "type")" \
- --arg flow "$(get_param "flow")" \
- --arg sni "$(get_param "sni")" \
- --arg fp "$(get_param "fp")" \
- --arg security "$(get_param "security")" \
- --arg pbk "$(get_param "pbk")" \
- --arg sid "$(get_param "sid")" \
- --arg alpn "$(get_param "alpn")" \
- --arg path "$(get_param "path")" \
- --arg host "$(get_param "host")" \
- --arg spx "$(get_param "spx")" \
- --arg insecure "$(get_param "allowInsecure")" \
- --arg section "$section" \
- '. |
- # Updating an existing outbound by tag or adding a new one
- .outbounds |= (
- # If an element with the required tag is found, update it
- map(
- if .tag == $section then
- . + {
- "type": "vless",
- "server": $server,
- "server_port": ($port | tonumber),
- "uuid": $uuid,
- "packet_encoding": "",
- "domain_strategy": "",
- "flow": $flow
- }
- else . end
- ) +
- # Add a new outbound if the required tag is not present
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [{
- "tag": $section,
- "type": "vless",
- "server": $server,
- "server_port": ($port | tonumber),
- "uuid": $uuid,
- "packet_encoding": "",
- "domain_strategy": "",
- "flow": $flow
- }]
- else [] end
- )
- ) |
- # Additional parameters such as transport and tls
- if $flow != "" then
- .outbounds |= map(
- if .tag == $section then
- .flow = $flow
- else . end
- )
- else . end |
- if $type == "ws" then
- .outbounds |= map(
- if .tag == $section then
- .transport = {
- "type": "ws",
- "path": $path
- } |
- if $host != "" then
- .transport.headers = { "Host": $host }
- else . end
- else . end
- )
- elif $type == "grpc" then
- .outbounds |= map(
- if .tag == $section then
- .transport = { "type": "grpc" }
- else . end
- )
- else . end |
- if $security == "reality" or $security == "tls" then
- .outbounds |= map(
- if .tag == $section then
- .tls = {
- "enabled": true,
- "server_name": $sni,
- "utls": {
- "enabled": true,
- "fingerprint": $fp
- },
- "insecure": ($insecure == "1")
- } |
- if $alpn != "" then
- .tls.alpn = ($alpn | split(","))
- else . end |
- if $security == "reality" then
- .tls.reality = {
- "enabled": true,
- "public_key": $pbk,
- "short_id": $sid
- }
- else . end
- else . end
- )
- else . end' "$SING_BOX_CONFIG" | build_sing_box_config
-
-
- if [ $? -eq 0 ]; then
- log "Config VLESS created successfully"
- else
- log "[critical] Error: VLESS invalid JSON config generated"
- exit 1
- fi
-}
-
-# Process. Sing-box rules
-
-sing_box_ruleset_domains() {
- log "Configure ruleset domains in sing-box"
-
- local domain=$1
- local tag=$2
-
- # Check if there is a route.rule_set for the specified tag
- local tag_exists=$(jq -r --arg tag "$tag" '
- .route.rule_set[]? | select(.tag == $tag) | .tag
- ' /etc/sing-box/config.json)
-
- # If the tag exists, add the domain
- if [[ -n "$tag_exists" ]]; then
- jq \
- --arg tag "$tag" \
- --arg domain "$domain" \
- '
- .route.rule_set[] |=
- if .tag == $tag then
- .rules[0].domain_suffix += [$domain]
- else
- .
- end
- ' "$SING_BOX_CONFIG" | build_sing_box_config
-
- log "$domain added to the list for tag $tag"
- else
- # If tag does not exist, add a new set of rules
- jq \
- --arg tag "$tag" \
- --arg domain "$domain" \
- '
- .route.rule_set += [
- {
- "tag": $tag,
- "type": "inline",
- "rules": [
- {
- "domain_suffix": [$domain]
- }
- ]
- }
- ]' "$SING_BOX_CONFIG" | build_sing_box_config
-
- log "$domain added as a new rule set for tag $tag"
- fi
-}
-
-sing_box_ruleset_subnets() {
- log "Configure ruleset domains in sing-box"
-
- local subnet=$1
- local tag=$2
-
- # nft
- nft add element inet PodkopTable podkop_subnets { $subnet }
-
- # Check if there is a route.rule_set for the specified tag
- local tag_exists=$(jq -r --arg tag "$tag" '
- .route.rule_set[]? | select(.tag == $tag) | .tag
- ' /etc/sing-box/config.json)
-
- # If tag exists, add the domain
- if [[ -n "$tag_exists" ]]; then
- jq \
- --arg tag "$tag" \
- --arg subnet "$subnet" \
- '
- .route.rule_set[] |=
- if .tag == $tag then
- .rules[0].ip_cidr += [$subnet]
- else
- .
- end
- ' "$SING_BOX_CONFIG" | build_sing_box_config
-
- log "$subnet added to the list for tag $tag"
- else
- # If tag does not exist, add a new set of rules
- jq \
- --arg tag "$tag" \
- --arg subnet "$subnet" \
- '
- .route.rule_set += [
- {
- "tag": $tag,
- "type": "inline",
- "rules": [
- {
- "ip_cidr": [$subnet]
- }
- ]
- }
- ]' "$SING_BOX_CONFIG" | build_sing_box_config
-
- log "$subnet added as a new rule set for tag $tag"
- fi
-}
-
sing_box_ruleset_domains_json() {
local domain="$1"
local section="$2"
@@ -1496,349 +1097,6 @@ sing_box_ruleset_subnets_json() {
log "$subnet added to $section-custom-domains-subnets.json"
}
-#######################################
-# Adds a new remote ruleset to the sing-box configuration.
-# https://sing-box.sagernet.org/configuration/rule-set/#__tabbed_1_3
-#
-# Arguments:
-# tag: unique identifier for the ruleset.
-# format: format of the ruleset (e.g., "source" or "binary").
-# url: URL from which the ruleset can be fetched.
-# update_interval: update interval for the ruleset (e.g., "1d").
-# detour: flag indicating whether to use a download detour ("1" or "0").
-#
-# Outputs:
-# Modifies the sing-box configuration file by appending a new ruleset entry.
-#
-# Returns:
-# None. Always returns 0. If a ruleset with the same tag exists, it is skipped.
-#######################################
-sing_box_config_add_remote_ruleset() {
- local tag=$1
- local format=$2
- local url=$3
- local update_interval=$4
- local detour=$5
-
- local tag_exists
- tag_exists=$(jq -r --arg tag "$tag" '
- .route.rule_set[]? | select(.tag == $tag) | .tag
- ' "$SING_BOX_CONFIG")
-
- if [[ -n "$tag_exists" ]]; then
- log "Ruleset with tag $tag already exists. Skipping addition."
- else
- jq \
- --arg tag "$tag" \
- --arg format "$format" \
- --arg url "$url" \
- --arg update_interval "$update_interval" \
- --arg detour "$detour" \
- '
- .route.rule_set += [
- (
- {
- "tag": $tag,
- "type": "remote",
- "format": $format,
- "url": $url,
- "update_interval": $update_interval
- } +
- (if $detour == "1" then {"download_detour": "main"} else {} end)
- )
- ]' "$SING_BOX_CONFIG" | build_sing_box_config
-
- log "Added new remote ruleset with tag $tag"
- fi
-}
-
-#######################################
-# Adds a remote ruleset to the sing-box configuration and applies route and dns rules.
-#
-# Arguments:
-# url: remote ruleset URL.
-# section: configuration section where rules will be applied.
-# ruleset_content_type: Type of ruleset content (e.g., "domains" or "subnets").
-#
-# Returns:
-# 0 on success, non-zero if the file extension is unsupported.
-#######################################
-sing_box_add_remote_ruleset_and_rules() {
- local url="$1"
- local section="$2"
- local ruleset_content_type="$3"
-
- local tag
- local format
- local update_interval='1d'
- local detour
-
- case "$(get_url_file_extension "$url")" in
- json) format="source" ;;
- srs) format="binary" ;;
- *)
- log "Unsupported file extension: .$file_extension"
- return 1
- ;;
- esac
-
- tag=$(get_ruleset_tag_from_url "$url" "$section-remote-$ruleset_content_type")
- config_get_bool detour "main" "detour" "0"
-
- sing_box_config_add_remote_ruleset "$tag" "$format" "$url" "$update_interval" "$detour"
- sing_box_rules "$tag" "$section"
- if [[ "$ruleset_content_type" = "domains" ]]; then
- sing_box_dns_rule_fakeip_section "$tag"
- fi
-}
-
-process_domains_for_section() {
- local section="$1"
-
- config_get custom_domains_list_type "$section" "custom_domains_list_type" "disabled"
-
- if [ "$custom_domains_list_type" != "disabled" ]; then
- log "Adding a custom domains list for $section section"
- if [ "$custom_domains_list_type" = "dynamic" ]; then
- # Handle list domains from custom_domains
- config_list_foreach "$section" custom_domains "sing_box_ruleset_domains" "$section"
- elif [ "$custom_domains_list_type" = "text" ]; then
- # Handle domains from text
- config_get custom_domains_text "$section" "custom_domains_text"
- process_domains_text "$custom_domains_text" "$section"
- fi
- fi
-}
-
-sing_box_rules() {
- log "Configure rule in sing-box"
- local rule_set="$1"
- local outbound="$2"
-
- config_get mode "$section" "mode"
-
- if [[ "$mode" == "block" ]]; then
- # Action reject
- # Check if there is an rule with reject"
- local rule_exists=$(jq -r '.route.rules[] | select(.inbound == ["tproxy-in"] and .action == "reject")' "$SING_BOX_CONFIG")
-
- if [[ -n "$rule_exists" ]]; then
- # If a rule for rejectexists, add a new rule_set to the existing rule
- jq \
- --arg rule_set "$rule_set" \
- '(.route.rules[] | select(.inbound == ["tproxy-in"] and .action == "reject") .rule_set) += [$rule_set]' \
- "$SING_BOX_CONFIG" | build_sing_box_config
- else
- # If there is no rule for reject, create a new one with rule_set
- jq \
- --arg rule_set "$rule_set" \
- '.route.rules += [{
- "inbound": ["tproxy-in"],
- "rule_set": [$rule_set],
- "action": "reject"
- }]' "$SING_BOX_CONFIG" | build_sing_box_config
- fi
- return
- else
- # Action route
- # Check if there is an outbound rule for "tproxy-in"
- local rule_exists=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .inbound == ["tproxy-in"])' "$SING_BOX_CONFIG")
-
- if [[ -n "$rule_exists" ]]; then
- # If a rule for tproxy-in exists, add a new rule_set to the existing rule
- jq \
- --arg rule_set "$rule_set" \
- --arg outbound "$outbound" \
- '(.route.rules[] | select(.outbound == $outbound and .inbound == ["tproxy-in"]) .rule_set) += [$rule_set]' \
- "$SING_BOX_CONFIG" | build_sing_box_config
- else
- # If there is no rule for tproxy-in, create a new one with rule_set
- jq \
- --arg rule_set "$rule_set" \
- --arg outbound "$outbound" \
- '.route.rules += [{
- "inbound": ["tproxy-in"],
- "rule_set": [$rule_set],
- "outbound": $outbound,
- "action": "route"
- }]' "$SING_BOX_CONFIG" | build_sing_box_config
- fi
- fi
-}
-
-sing_box_quic_reject() {
- local quic_rule_exists=$(jq -e '.route.rules[] | select(.protocol == "quic" and .action == "reject")' "$SING_BOX_CONFIG")
-
- if [[ -z "$quic_rule_exists" ]]; then
- jq '
- .route.rules |= (
- reduce .[] as $rule ([];
- if $rule.protocol == "dns" and $rule.action == "hijack-dns" then
- . + [$rule, {"protocol": "quic", "action": "reject"}]
- else
- . + [$rule]
- end
- )
- )' "$SING_BOX_CONFIG" | build_sing_box_config
-
- log "QUIC reject rule added successfully"
- fi
-}
-
-# TODO(ampetelin): function needs refactoring
-sing_box_rule_preset() {
- config_get custom_domains_list_type "$section" "custom_domains_list_type"
- config_get custom_subnets_list_enabled "$section" "custom_subnets_list_enabled"
- config_get custom_local_domains_list_enabled "$section" "custom_local_domains_list_enabled"
-
- if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_subnets_list_enabled" != "disabled" ] ||
- [ "$custom_local_domains_list_enabled" = "1" ]; then
- sing_box_rules "$section" "$section"
- fi
-
- if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_local_domains_list_enabled" = "1" ]; then
- sing_box_dns_rule_fakeip_section "$section" "$section"
- fi
-
- config_get domain_list_enabled "$section" "domain_list_enabled"
- config_get domain_list "$section" "domain_list"
- if [ "$domain_list_enabled" -eq 1 ]; then
- config_list_foreach $section domain_list sing_box_rules $section
- config_list_foreach $section domain_list sing_box_dns_rule_fakeip_section domain_list
- fi
-}
-
-list_custom_local_domains_create() {
- local section="$2"
- local local_file="$1"
- local filename=$(basename "$local_file" | cut -d. -f1)
-
- while IFS= read -r domain; do
- domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$domain" ] && echo "$domain" | grep -E -q '^([a-zA-Z0-9][-a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$'; then
- log "Added $domain from local file"
- sing_box_ruleset_domains "$domain" "$section"
- else
- log "Invalid domain skipped: $domain"
- fi
- done <"$local_file"
-}
-
-process_domains_list_local() {
- local section="$1"
-
- config_get custom_local_domains_list_enabled "$section" "custom_local_domains_list_enabled"
- if [ "$custom_local_domains_list_enabled" -eq 1 ]; then
- log "Adding a custom domains list from file in $section"
- config_list_foreach "$section" "custom_local_domains" list_custom_local_domains_create "$section"
- fi
-}
-
-process_subnet_for_section() {
- local section="$1"
-
- config_get custom_subnets_list_enabled "$section" "custom_subnets_list_enabled" "disabled"
- if [ "$custom_subnets_list_enabled" != "disabled" ]; then
- log "Adding a custom subnet list for $section section"
- if [ "$custom_subnets_list_enabled" = "dynamic" ]; then
- # Handle list domains from custom_domains
- config_list_foreach "$section" custom_subnets "sing_box_ruleset_subnets" "$section"
- elif [ "$custom_subnets_list_enabled" = "text" ]; then
- # Handle domains from text
- config_get custom_subnets_text "$section" "custom_subnets_text"
- process_subnets_text "$custom_subnets_text" "$section"
- fi
- fi
-}
-
-configure_community_lists() {
- config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
- if [ "$domain_list_enabled" -eq 1 ]; then
- log "Configuring community lists for $section section"
- config_list_foreach "$section" domain_list configure_community_list_handler
- fi
-}
-
-configure_community_list_handler() {
- local tag=$1
-
- local format="binary"
- local update_interval="1d"
- config_get_bool detour "main" "detour" "0"
- local url="$SRS_MAIN_URL/$tag.srs"
-
- sing_box_config_add_remote_ruleset "$tag" "$format" "$url" "$update_interval" "$detour"
-}
-
-configure_remote_domain_lists() {
- local section="$1"
-
- config_get custom_download_domains_list_enabled "$section" custom_download_domains_list_enabled
- if [ "$custom_download_domains_list_enabled" -eq 1 ]; then
- log "Configuring remote domain lists for $section section"
- config_list_foreach "$section" custom_download_domains configure_remote_domain_list_handler "$section"
- fi
-}
-
-configure_remote_domain_list_handler() {
- local url="$1"
- local section="$2"
-
- log "Configuring remote domain list from URL: $url"
-
- local file_extension
- file_extension=$(get_url_file_extension "$url")
- case "$file_extension" in
- json|srs)
- log "Detected file extension: .$file_extension โ proceeding with processing"
- sing_box_add_remote_ruleset_and_rules "$url" "$section" "domains"
- ;;
- *)
- log "Detected file extension: .$file_extension โ no processing needed, managed on list_update"
- ;;
- esac
-}
-
-configure_remote_subnet_lists() {
- local section="$1"
-
- config_get custom_download_subnets_list_enabled "$section" custom_download_subnets_list_enabled disabled
- if [ "$custom_download_subnets_list_enabled" -eq "1" ]; then
- log "Configuring remote subnet lists for $section section"
- config_list_foreach "$section" custom_download_subnets configure_remote_subnet_list_handler "$section"
- fi
-}
-
-configure_remote_subnet_list_handler() {
- local url="$1"
- local section="$2"
-
- log "Configuring remote subnet list from URL: $url"
-
- local file_extension
- file_extension=$(get_url_file_extension "$url")
- case "$file_extension" in
- json|srs)
- log "Detected file extension: .$file_extension โ proceeding with processing"
- sing_box_add_remote_ruleset_and_rules "$url" "$section" "subnets"
- ;;
- *)
- log "Detected file extension: .$file_extension โ no processing needed, managed on list_update"
- ;;
- esac
-}
-
-process_all_traffic_for_section() {
- local section="$1"
-
- config_get all_traffic_from_ip_enabled "$section" "all_traffic_from_ip_enabled"
- if [ "$all_traffic_from_ip_enabled" -eq "1" ]; then
- log "Adding an IP to redirect all traffic"
- config_list_foreach $section all_traffic_ip nft_list_all_traffic_from_ip
- config_list_foreach $section all_traffic_ip sing_box_rules_source_ip_cidr $all_traffic_ip $section
- fi
-}
-
import_community_subnet_lists() {
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
if [ "$domain_list_enabled" -eq 1 ]; then
@@ -2054,58 +1312,6 @@ decompile_srs_file() {
fi
}
-sing_box_rules_source_ip_cidr() {
- log "Configure source_ip_cidr rule in sing-box"
- local source_ip_cidr="$1"
- local outbound="$2"
-
- local current_source_ip_cidr=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .action == "route" and .source_ip_cidr and (.inbound // [] | contains(["tproxy-in"])))' $SING_BOX_CONFIG)
-
- if [[ -n "$current_source_ip_cidr" ]]; then
- jq \
- --arg source_ip_cidr "$source_ip_cidr" \
- --arg outbound "$outbound" \
- '(.route.rules[] | select(.outbound == $outbound and .action == "route" and .source_ip_cidr and (.inbound // [] | contains(["tproxy-in"]))) | .source_ip_cidr) += [$source_ip_cidr]' "$SING_BOX_CONFIG" | build_sing_box_config
- else
- jq \
- --arg source_ip_cidr "$source_ip_cidr" \
- --arg outbound "$outbound" \
- '.route.rules = [
- {
- "inbound": ["tproxy-in"],
- "source_ip_cidr": [$source_ip_cidr],
- "outbound": $outbound,
- "action": "route"
- }
- ] + .route.rules' "$SING_BOX_CONFIG" | build_sing_box_config
- fi
-}
-
-detour_mixed() {
- local section="main"
- local port="4534"
- local tag="detour"
-
- log "Adding detour Socks5 for $section on port $port"
-
- jq \
- --arg tag "$tag" \
- --arg port "$port" \
- --arg section "$section" \
- '.inbounds += [{
- "tag": $tag,
- "type": "mixed",
- "listen": "127.0.0.1",
- "listen_port": ($port|tonumber),
- "set_system_proxy": false
- }] |
- .route.rules += [{
- "inbound": [$tag],
- "outbound": $section,
- "action": "route"
- }]' "$SING_BOX_CONFIG" | build_sing_box_config
-}
-
## nftables
nft_list_all_traffic_from_ip() {
local ip="$1"
@@ -2135,14 +1341,14 @@ check_proxy() {
return 1
fi
- if [ ! -f $SING_BOX_CONFIG ]; then
+ if [ ! -f $SB_CONFIG ]; then
nolog "Configuration file not found"
return 1
fi
nolog "Checking sing-box configuration..."
- if ! sing-box -c $SING_BOX_CONFIG check >/dev/null; then
+ if ! sing-box -c $SB_CONFIG check >/dev/null; then
nolog "Invalid configuration"
return 1
fi
@@ -2170,7 +1376,7 @@ check_proxy() {
else . end
)
else . end
- )' $SING_BOX_CONFIG
+ )' $SB_CONFIG
nolog "Checking proxy connection..."
@@ -2347,6 +1553,7 @@ check_sing_box_logs() {
echo "$logs"
}
+# TODO(ampetelin): need fix after refactoring
check_fakeip() {
# Not used
nolog "Checking fakeip functionality..."
@@ -2395,14 +1602,14 @@ check_fakeip() {
nolog "sing-box is running, but FakeIP might not be configured correctly"
nolog "Checking DNS configuration in sing-box..."
- if [ -f "$SING_BOX_CONFIG" ]; then
- local fakeip_enabled=$(jq -r '.dns.fakeip.enabled' "$SING_BOX_CONFIG")
- local fakeip_range=$(jq -r '.dns.fakeip.inet4_range' "$SING_BOX_CONFIG")
+ if [ -f "$SB_CONFIG" ]; then
+ local fakeip_enabled=$(jq -r '.dns.fakeip.enabled' "$SB_CONFIG")
+ local fakeip_range=$(jq -r '.dns.fakeip.inet4_range' "$SB_CONFIG")
nolog "FakeIP enabled: $fakeip_enabled"
nolog "FakeIP range: $fakeip_range"
- local dns_rules=$(jq -r '.dns.rules[] | select(.server == "fakeip-server") | .domain' "$SING_BOX_CONFIG")
+ local dns_rules=$(jq -r '.dns.rules[] | select(.server == "fakeip-server") | .domain' "$SB_CONFIG")
nolog "FakeIP domain: $dns_rules"
else
nolog "sing-box config file not found"
@@ -2439,7 +1646,7 @@ check_logs() {
show_sing_box_config() {
nolog "Current sing-box configuration:"
- if [ ! -f "$SING_BOX_CONFIG" ]; then
+ if [ ! -f "$SB_CONFIG" ]; then
nolog "Configuration file not found"
return 1
fi
@@ -2467,7 +1674,7 @@ show_sing_box_config() {
else . end
)
else . end
- )' "$SING_BOX_CONFIG"
+ )' "$SB_CONFIG"
}
show_config() {
@@ -2662,35 +1869,6 @@ check_dns_available() {
echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"is_available\":$is_available,\"status\":\"$status\",\"local_dns_working\":$local_dns_working,\"local_dns_status\":\"$local_dns_status\"}"
}
-sing_box_add_secure_dns_probe_domain() {
- local domain="$TEST_DOMAIN"
- local override_port=8443
-
- log "Adding DNS probe domain ${domain} to fakeip-server configuration"
-
- jq \
- --arg domain "$domain" \
- --argjson override_port "$override_port" \
- '.dns.rules |= map(
- if (.server == "fakeip-server" or (.server == "dns-server" and .invert == true)) then
- . + {
- "domain": $domain
- }
- else
- .
- end
- ) |
- .route.rules |= . + [
- {
- "domain": $domain,
- "action": "route-options",
- "override_port": $override_port
- }
- ]' "$SING_BOX_CONFIG" | build_sing_box_config
-
- log "DNS probe domain ${domain} configured with override to port ${override_port}"
-}
-
print_global() {
local message="$1"
echo "$message"
@@ -2835,10 +2013,10 @@ global_check() {
else
print_global " ๐ค sing-box is running, checking configuration"
- if [ -f "$SING_BOX_CONFIG" ]; then
- local fakeip_enabled=$(jq -r '.dns.fakeip.enabled' "$SING_BOX_CONFIG")
- local fakeip_range=$(jq -r '.dns.fakeip.inet4_range' "$SING_BOX_CONFIG")
- local dns_rules=$(jq -r '.dns.rules[] | select(.server == "fakeip-server") | .domain' "$SING_BOX_CONFIG")
+ if [ -f "$SB_CONFIG" ]; then
+ local fakeip_enabled=$(jq -r '.dns.fakeip.enabled' "$SB_CONFIG")
+ local fakeip_range=$(jq -r '.dns.fakeip.inet4_range' "$SB_CONFIG")
+ local dns_rules=$(jq -r '.dns.rules[] | select(.server == "fakeip-server") | .domain' "$SB_CONFIG")
print_global " ๐ฆ FakeIP enabled: $fakeip_enabled"
print_global " ๐ฆ FakeIP range: $fakeip_range"
@@ -2850,6 +2028,7 @@ global_check() {
fi
}
+# TODO: create helper functon
# Download URL content directly
download_to_stream() {
local url="$1"
@@ -2862,6 +2041,7 @@ download_to_stream() {
fi
}
+# TODO: create helper functon
# Download URL to temporary file
download_to_tempfile() {
local url="$1"
@@ -2880,57 +2060,6 @@ download_to_tempfile() {
fi
}
-# helper function
-
-# check if file exists
-file_exists() {
- local filepath="$1"
-
- if [[ -f "$filepath" ]]; then
- return 0 # success
- else
- return 1 # failure
- fi
-}
-
-# extracts file extension from URL
-get_url_file_extension() {
- local url="$1"
-
- local file_extension="${url##*.}"
-
- echo "$file_extension"
-}
-
-# extracts file extension from URL
-get_ruleset_tag_from_url() {
- local url="$1"
- local prefix="${2:-}"
- local postfix="${3:-}"
-
- local filename="${url##*/}"
- local basename="${filename%%.*}"
-
- local tag="$basename"
-
- if [ -n "$prefix" ]; then
- tag="${prefix}-${tag}"
- fi
-
- if [ -n "$postfix" ]; then
- tag="${tag}-${postfix}"
- fi
-
- echo "$tag"
-}
-
-# 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 ]]
-}
-
show_help() {
cat << EOF
Usage: $0 COMMAND
diff --git a/podkop/files/usr/lib/constants.sh b/podkop/files/usr/lib/constants.sh
new file mode 100644
index 0000000..ee60ebf
--- /dev/null
+++ b/podkop/files/usr/lib/constants.sh
@@ -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"
diff --git a/podkop/files/usr/lib/helpers.sh b/podkop/files/usr/lib/helpers.sh
new file mode 100644
index 0000000..3de5bfc
--- /dev/null
+++ b/podkop/files/usr/lib/helpers.sh
@@ -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
+}
diff --git a/podkop/files/usr/lib/sing_box_config_facade.sh b/podkop/files/usr/lib/sing_box_config_facade.sh
new file mode 100644
index 0000000..c76ce25
--- /dev/null
+++ b/podkop/files/usr/lib/sing_box_config_facade.sh
@@ -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"
+}
diff --git a/podkop/files/usr/lib/sing_box_config_manager.sh b/podkop/files/usr/lib/sing_box_config_manager.sh
index 565203b..0cb3e0d 100644
--- a/podkop/files/usr/lib/sing_box_config_manager.sh
+++ b/podkop/files/usr/lib/sing_box_config_manager.sh
@@ -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"
-}
\ No newline at end of file
+}
+
+_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
+}