mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-06 11:36:50 +03:00
2393 lines
82 KiB
Bash
Executable File
2393 lines
82 KiB
Bash
Executable File
#!/bin/ash
|
||
|
||
check_required_file() {
|
||
local file="$1"
|
||
|
||
if [ ! -r "$file" ]; then
|
||
echo "Error: required file '$file' is missing or not readable" >&2
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
PODKOP_LIB="/usr/lib/podkop"
|
||
check_required_file /lib/functions.sh
|
||
check_required_file /lib/config/uci.sh
|
||
check_required_file "$PODKOP_LIB/constants.sh"
|
||
check_required_file "$PODKOP_LIB/nft.sh"
|
||
check_required_file "$PODKOP_LIB/helpers.sh"
|
||
check_required_file "$PODKOP_LIB/sing_box_config_manager.sh"
|
||
check_required_file "$PODKOP_LIB/sing_box_config_facade.sh"
|
||
check_required_file "$PODKOP_LIB/logging.sh"
|
||
. /lib/config/uci.sh
|
||
. /lib/functions.sh
|
||
. "$PODKOP_LIB/constants.sh"
|
||
. "$PODKOP_LIB/nft.sh"
|
||
. "$PODKOP_LIB/helpers.sh"
|
||
. "$PODKOP_LIB/sing_box_config_manager.sh"
|
||
. "$PODKOP_LIB/sing_box_config_facade.sh"
|
||
. "$PODKOP_LIB/logging.sh"
|
||
|
||
config_load "$PODKOP_CONFIG"
|
||
|
||
check_requirements() {
|
||
log "Check Requirements"
|
||
|
||
local sing_box_version jq_version coreutils_base64_version
|
||
sing_box_version="$(sing-box version | head -n1 | awk '{print $3}')"
|
||
jq_version="$(jq --version | awk -F- '{print $2}')"
|
||
coreutils_base64_version="$(base64 --version | head -n1 | awk '{print $4}')"
|
||
|
||
if [ -z "$sing_box_version" ]; then
|
||
log "Package 'sing-box' is not installed." "error"
|
||
exit 1
|
||
else
|
||
if ! is_min_package_version "$sing_box_version" "$SB_REQUIRED_VERSION"; then
|
||
log "Package 'sing-box' version ($sing_box_version) is lower than the required minimum ($SB_REQUIRED_VERSION). Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box" "error"
|
||
exit 1
|
||
fi
|
||
|
||
if ! service_exists "sing-box"; then
|
||
log "Service 'sing-box' is missing. Please install the official package to ensure the service is available." "error"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
if [ -z "$jq_version" ]; then
|
||
log "Package 'jq' is not installed." "error"
|
||
exit 1
|
||
elif ! is_min_package_version "$jq_version" "$JQ_REQUIRED_VERSION"; then
|
||
log "Package 'jq' version ($jq_version) is lower than the required minimum ($JQ_REQUIRED_VERSION)." "error"
|
||
exit 1
|
||
fi
|
||
|
||
if [ -z "$coreutils_base64_version" ]; then
|
||
log "Package 'coreutils-base64' is not installed." "error"
|
||
exit 1
|
||
elif ! is_min_package_version "$coreutils_base64_version" "$COREUTILS_BASE64_REQUIRED_VERSION"; then
|
||
log "Package 'coreutils-base64' version ($coreutils_base64_version) is lower than the required minimum ($COREUTILS_BASE64_REQUIRED_VERSION). This may cause issues when decoding base64 streams with missing padding, as automatic padding support is not available in older versions." "warn"
|
||
fi
|
||
|
||
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
|
||
log "Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp" "warn"
|
||
fi
|
||
|
||
local proxy_string interface outbound_json urltest_proxy_links
|
||
config_get proxy_string "main" "proxy_string"
|
||
config_get interface "main" "interface"
|
||
config_get outbound_json "main" "outbound_json"
|
||
config_get urltest_proxy_links "main" "urltest_proxy_links"
|
||
|
||
if [ -z "$proxy_string" ] && [ -z "$interface" ] && [ -z "$outbound_json" ] && [ -z "$urltest_proxy_links" ]; then
|
||
log "Required options (proxy_string, interface, outbound_json, urltest_proxy_links) are missing in 'main' section. Aborted." "error"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
start_main() {
|
||
log "Starting podkop"
|
||
|
||
check_requirements
|
||
|
||
migration
|
||
|
||
config_foreach process_validate_service "section"
|
||
|
||
br_netfilter_disable
|
||
|
||
# Sync time for DoH/DoT
|
||
/usr/sbin/ntpd -q -p 194.190.168.1 -p 216.239.35.0 -p 216.239.35.4 -p 162.159.200.1 -p 162.159.200.123
|
||
|
||
sleep 1
|
||
|
||
mkdir -p "$TMP_SING_BOX_FOLDER"
|
||
mkdir -p "$TMP_RULESET_FOLDER"
|
||
|
||
# base
|
||
route_table_rule_mark
|
||
create_nft_table
|
||
sing_box_uci
|
||
|
||
# sing-box
|
||
sing_box_init_config
|
||
config_foreach add_cron_job "section"
|
||
/etc/init.d/sing-box start
|
||
|
||
local exclude_ntp
|
||
config_get_bool exclude_ntp "settings" "exclude_ntp" "0"
|
||
if [ "$exclude_ntp" -eq 1 ]; then
|
||
log "NTP traffic exclude for proxy"
|
||
nft insert rule inet "$NFT_TABLE_NAME" mangle udp dport 123 return
|
||
fi
|
||
|
||
log "Nice"
|
||
list_update &
|
||
echo $! > /var/run/podkop_list_update.pid
|
||
}
|
||
|
||
start() {
|
||
start_main
|
||
config_get_bool dont_touch_dhcp "settings" "dont_touch_dhcp" 0
|
||
if [ "$dont_touch_dhcp" -eq 0 ]; then
|
||
dnsmasq_add_resolver
|
||
fi
|
||
uci_set "podkop" "settings" "shutdown_correctly" 0
|
||
uci commit "podkop" && config_load "$PODKOP_CONFIG"
|
||
}
|
||
|
||
stop_main() {
|
||
log "Stopping the podkop"
|
||
|
||
if [ -f /var/run/podkop_list_update.pid ]; then
|
||
pid=$(cat /var/run/podkop_list_update.pid)
|
||
if kill -0 "$pid" 2> /dev/null; then
|
||
kill "$pid" 2> /dev/null
|
||
log "Stopped list_update"
|
||
fi
|
||
rm -f /var/run/podkop_list_update.pid
|
||
fi
|
||
|
||
remove_cron_job
|
||
|
||
rm -f "$TMP_RULESET_FOLDER"/*
|
||
|
||
log "Flush nft"
|
||
if nft list table inet "$NFT_TABLE_NAME" > /dev/null 2>&1; then
|
||
nft delete table inet "$NFT_TABLE_NAME"
|
||
fi
|
||
|
||
log "Flush ip rule"
|
||
if ip rule list | grep -q "podkop"; then
|
||
ip rule del fwmark 0x105 table podkop priority 105
|
||
fi
|
||
|
||
log "Flush ip route"
|
||
if ip route list table podkop > /dev/null 2>&1; then
|
||
ip route flush table podkop
|
||
fi
|
||
|
||
log "Stop sing-box"
|
||
/etc/init.d/sing-box stop
|
||
}
|
||
|
||
stop() {
|
||
local dont_touch_dhcp
|
||
config_get_bool dont_touch_dhcp "settings" "dont_touch_dhcp" 0
|
||
if [ "$dont_touch_dhcp" -eq 0 ]; then
|
||
dnsmasq_restore
|
||
fi
|
||
stop_main
|
||
uci_set "podkop" "settings" "shutdown_correctly" 1
|
||
uci commit "podkop" && config_load "$PODKOP_CONFIG"
|
||
}
|
||
|
||
reload() {
|
||
log "Podkop reload"
|
||
stop_main
|
||
start_main
|
||
}
|
||
|
||
restart() {
|
||
log "Podkop restart"
|
||
stop
|
||
start
|
||
}
|
||
|
||
# Migrations and validation funcs
|
||
migration() {
|
||
:
|
||
}
|
||
|
||
validate_service() {
|
||
local service="$1"
|
||
|
||
for community_service in $COMMUNITY_SERVICES; do
|
||
if [ "$service" = "$community_service" ]; then
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
log "Invalid service in community lists: $service. Check config and LuCI cache. Aborted." "fatal"
|
||
exit 1
|
||
}
|
||
|
||
process_validate_service() {
|
||
local section="$1"
|
||
local community_lists
|
||
config_get community_lists "$section" "community_lists"
|
||
if [ -n "$community_lists" ]; then
|
||
config_list_foreach "$section" "community_lists" validate_service
|
||
fi
|
||
}
|
||
|
||
br_netfilter_disable() {
|
||
if lsmod | grep -q br_netfilter && [ "$(sysctl -n net.bridge.bridge-nf-call-iptables 2> /dev/null)" = "1" ]; then
|
||
log "br_netfilter enabled detected. Disabling"
|
||
sysctl -w net.bridge.bridge-nf-call-iptables=0
|
||
sysctl -w net.bridge.bridge-nf-call-ip6tables=0
|
||
fi
|
||
}
|
||
|
||
# Main funcs
|
||
|
||
route_table_rule_mark() {
|
||
local table=podkop
|
||
|
||
grep -q "105 $table" /etc/iproute2/rt_tables || echo "105 $table" >> /etc/iproute2/rt_tables
|
||
|
||
if ! ip route list table $table | grep -q "local default dev lo scope host"; then
|
||
log "Added route for tproxy"
|
||
ip route add local 0.0.0.0/0 dev lo table $table
|
||
else
|
||
log "Route for tproxy exists"
|
||
fi
|
||
|
||
if ! ip rule list | grep -q "from all fwmark 0x105 lookup $table"; then
|
||
log "Create marking rule"
|
||
ip -4 rule add fwmark 0x105 table $table priority 105
|
||
else
|
||
log "Marking rule exist"
|
||
fi
|
||
}
|
||
|
||
nft_init_interfaces_set() {
|
||
nft_create_ifname_set "$NFT_TABLE_NAME" "$NFT_INTERFACE_SET_NAME"
|
||
|
||
local source_network_interfaces
|
||
config_get source_network_interfaces "settings" "source_network_interfaces" "br-lan"
|
||
|
||
for interface in $source_network_interfaces; do
|
||
nft add element inet "$NFT_TABLE_NAME" "$NFT_INTERFACE_SET_NAME" "{ $interface }"
|
||
done
|
||
}
|
||
|
||
create_nft_table() {
|
||
log "Create nft table"
|
||
nft_create_table "$NFT_TABLE_NAME"
|
||
|
||
nft_init_interfaces_set
|
||
|
||
log "Create localv4 set"
|
||
nft_create_ipv4_set "$NFT_TABLE_NAME" "$NFT_LOCALV4_SET_NAME"
|
||
nft add element inet "$NFT_TABLE_NAME" localv4 '{
|
||
0.0.0.0/8,
|
||
10.0.0.0/8,
|
||
127.0.0.0/8,
|
||
169.254.0.0/16,
|
||
172.16.0.0/12,
|
||
192.0.0.0/24,
|
||
192.0.2.0/24,
|
||
192.88.99.0/24,
|
||
192.168.0.0/16,
|
||
198.51.100.0/24,
|
||
203.0.113.0/24,
|
||
224.0.0.0/4,
|
||
240.0.0.0-255.255.255.255
|
||
}'
|
||
|
||
log "Create common set"
|
||
nft_create_ipv4_set "$NFT_TABLE_NAME" "$NFT_COMMON_SET_NAME"
|
||
|
||
log "Create interface set"
|
||
nft_init_interfaces_set
|
||
|
||
log "Create nft rules"
|
||
nft add chain inet "$NFT_TABLE_NAME" mangle '{ type filter hook prerouting priority -150; policy accept; }'
|
||
nft add chain inet "$NFT_TABLE_NAME" mangle_output '{ type route hook output priority -150; policy accept; }'
|
||
nft add chain inet "$NFT_TABLE_NAME" proxy '{ type filter hook prerouting priority -100; policy accept; }'
|
||
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto tcp meta mark set 0x105 counter
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto udp meta mark set 0x105 counter
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "$SB_FAKEIP_INET4_RANGE" meta l4proto tcp meta mark set 0x105 counter
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "$SB_FAKEIP_INET4_RANGE" meta l4proto udp meta mark set 0x105 counter
|
||
|
||
nft add rule inet "$NFT_TABLE_NAME" proxy meta mark 0x105 meta l4proto tcp tproxy ip to 127.0.0.1:1602 counter
|
||
nft add rule inet "$NFT_TABLE_NAME" proxy meta mark 0x105 meta l4proto udp tproxy ip to 127.0.0.1:1602 counter
|
||
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "@$NFT_LOCALV4_SET_NAME" return
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto tcp meta mark set 0x105 counter
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto udp meta mark set 0x105 counter
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "$SB_FAKEIP_INET4_RANGE" meta l4proto tcp meta mark set 0x105 counter
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle_output ip daddr "$SB_FAKEIP_INET4_RANGE" meta l4proto tcp meta mark set 0x105 counter
|
||
}
|
||
|
||
backup_dnsmasq_config_option() {
|
||
local key="$1"
|
||
local backup_key="$2"
|
||
local value
|
||
value="$(uci_get "dhcp" "@dnsmasq[0]" "$key")"
|
||
|
||
if [ -n "$value" ]; then
|
||
uci_set "dhcp" "@dnsmasq[0]" "$backup_key" "$value"
|
||
fi
|
||
}
|
||
|
||
dnsmasq_add_resolver() {
|
||
local shutdown_correctly
|
||
config_get shutdown_correctly "settings" "shutdown_correctly"
|
||
if [ "$shutdown_correctly" -eq 0 ]; then
|
||
log "Previous shutdown of podkop was not correct, reconfiguration of dnsmasq is not required"
|
||
return 0
|
||
fi
|
||
|
||
log "Backup dnsmasq configuration"
|
||
current_servers="$(uci_get "dhcp" "@dnsmasq[0]" "server")"
|
||
if [ -n "$current_servers" ]; then
|
||
for server in $(uci_get "dhcp" "@dnsmasq[0]" "server"); do
|
||
if ! [ "$server" == "$SB_DNS_INBOUND_ADDRESS" ]; then
|
||
uci_add_list "dhcp" "@dnsmasq[0]" "podkop_server" "$server"
|
||
fi
|
||
done
|
||
uci_remove "dhcp" "@dnsmasq[0]" "server"
|
||
fi
|
||
|
||
backup_dnsmasq_config_option "noresolv" "podkop_noresolv"
|
||
backup_dnsmasq_config_option "cachesize" "podkop_cachesize"
|
||
|
||
log "Configure dnsmasq for sing-box"
|
||
uci_add_list "dhcp" "@dnsmasq[0]" "server" "$SB_DNS_INBOUND_ADDRESS"
|
||
uci_set "dhcp" "@dnsmasq[0]" "noresolv" 1
|
||
uci_set "dhcp" "@dnsmasq[0]" "cachesize" 0
|
||
uci_commit "dhcp"
|
||
|
||
/etc/init.d/dnsmasq restart
|
||
}
|
||
|
||
dnsmasq_restore() {
|
||
log "Restoring the dnsmasq configuration"
|
||
local shutdown_correctly
|
||
config_get shutdown_correctly "settings" "shutdown_correctly"
|
||
if [ "$shutdown_correctly" -eq 1 ]; then
|
||
log "Previous shutdown of podkop was correct, reconfiguration of dnsmasq is not required"
|
||
return 0
|
||
fi
|
||
|
||
local cachesize noresolv backup_servers resolvfile
|
||
log "Restoring cachesize" "debug"
|
||
cachesize="$(uci_get "dhcp" "@dnsmasq[0]" "podkop_cachesize")"
|
||
if [ -z "$cachesize" ]; then
|
||
uci_remove "dhcp" "@dnsmasq[0]" "cachesize"
|
||
uci_set "dhcp" "@dnsmasq[0]" "cachesize" 150
|
||
else
|
||
uci_set "dhcp" "@dnsmasq[0]" "cachesize" "$cachesize"
|
||
uci_remove "dhcp" "@dnsmasq[0]" "podkop_cachesize"
|
||
fi
|
||
|
||
log "Restoring noresolv" "debug"
|
||
noresolv="$(uci_get "dhcp" "@dnsmasq[0]" "podkop_noresolv")"
|
||
if [ -z "$noresolv" ]; then
|
||
uci_remove "dhcp" "@dnsmasq[0]" "noresolv"
|
||
uci_set "dhcp" "@dnsmasq[0]" "noresolv" 0
|
||
else
|
||
uci_set "dhcp" "@dnsmasq[0]" "noresolv" "$noresolv"
|
||
uci_remove "dhcp" "@dnsmasq[0]" "podkop_noresolv"
|
||
fi
|
||
|
||
log "Restoring DNS servers" "debug"
|
||
uci_remove "dhcp" "@dnsmasq[0]" "server"
|
||
resolvfile="/tmp/resolv.conf.d/resolv.conf.auto"
|
||
backup_servers="$(uci_get "dhcp" "@dnsmasq[0]" "podkop_server")"
|
||
if [ -n "$backup_servers" ]; then
|
||
for server in $backup_servers; do
|
||
uci_add_list "dhcp" "@dnsmasq[0]" "server" "$server"
|
||
done
|
||
uci_remove "dhcp" "@dnsmasq[0]" "podkop_server"
|
||
elif file_exists "$resolvfile"; then
|
||
log "Backup DNS servers not found, using default resolvfile" "debug"
|
||
uci_set "dhcp" "@dnsmasq[0]" "resolvfile" "$resolvfile"
|
||
else
|
||
log "Backup DNS servers and default resolvfile not found, possible resolving issues" "warn"
|
||
fi
|
||
|
||
uci_commit "dhcp"
|
||
|
||
/etc/init.d/dnsmasq restart
|
||
}
|
||
|
||
add_cron_job() {
|
||
## Future: make a check so that it doesn't recreate many times
|
||
local community_lists remote_domain_lists remote_subnet_lists update_interval
|
||
config_get community_lists "$section" "community_lists"
|
||
config_get remote_domain_lists "$section" "remote_domain_lists"
|
||
config_get remote_subnet_lists "$section" "remote_subnet_lists"
|
||
config_get update_interval "settings" "update_interval"
|
||
|
||
case "$update_interval" in
|
||
"1h")
|
||
cron_job="13 * * * * /usr/bin/podkop list_update"
|
||
;;
|
||
"3h")
|
||
cron_job="13 */3 * * * /usr/bin/podkop list_update"
|
||
;;
|
||
"12h")
|
||
cron_job="13 */12 * * * /usr/bin/podkop list_update"
|
||
;;
|
||
"1d")
|
||
cron_job="13 9 * * * /usr/bin/podkop list_update"
|
||
;;
|
||
"3d")
|
||
cron_job="13 9 */3 * * /usr/bin/podkop list_update"
|
||
;;
|
||
*)
|
||
log "Invalid update_interval value: $update_interval"
|
||
return
|
||
;;
|
||
esac
|
||
|
||
if [ -n "$community_lists" ] ||
|
||
[ -n "$remote_domain_lists" ] ||
|
||
[ -n "$remote_subnet_lists" ]; then
|
||
remove_cron_job
|
||
crontab -l | {
|
||
cat
|
||
echo "$cron_job"
|
||
} | crontab -
|
||
log "The cron job has been created: $cron_job"
|
||
fi
|
||
}
|
||
|
||
remove_cron_job() {
|
||
(crontab -l | grep -v "/usr/bin/podkop list_update") | crontab -
|
||
log "The cron job removed"
|
||
}
|
||
|
||
list_update() {
|
||
echolog "🔄 Starting lists update..."
|
||
|
||
local i
|
||
|
||
for i in $(seq 1 60); do
|
||
if nslookup -timeout=1 openwrt.org > /dev/null 2>&1; then
|
||
echolog "✅ DNS check passed"
|
||
break
|
||
fi
|
||
log "DNS is unavailable [$i/60]"
|
||
sleep 3
|
||
done
|
||
|
||
if [ "$i" -eq 60 ]; then
|
||
echolog "❌ DNS check failed after 60 attempts"
|
||
return 1
|
||
fi
|
||
|
||
for i in $(seq 1 60); do
|
||
config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" "0"
|
||
if [ "$download_lists_via_proxy" -eq 1 ]; then
|
||
if http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" curl -s -m 3 https://github.com > /dev/null; then
|
||
echolog "✅ GitHub connection check passed (via proxy)"
|
||
break
|
||
fi
|
||
else
|
||
if curl -s -m 3 https://github.com > /dev/null; then
|
||
echolog "✅ GitHub connection check passed"
|
||
break
|
||
fi
|
||
fi
|
||
|
||
echolog "GitHub is unavailable [$i/60]"
|
||
sleep 3
|
||
done
|
||
|
||
if [ "$i" -eq 60 ]; then
|
||
echolog "❌ GitHub connection check failed after 60 attempts"
|
||
return 1
|
||
fi
|
||
|
||
echolog "📥 Downloading and processing lists..."
|
||
|
||
config_foreach import_community_subnet_lists "section"
|
||
config_foreach import_domains_from_remote_domain_lists "section"
|
||
config_foreach import_subnets_from_remote_subnet_lists "section"
|
||
|
||
if [ $? -eq 0 ]; then
|
||
echolog "✅ Lists update completed successfully"
|
||
else
|
||
echolog "❌ Lists update failed"
|
||
fi
|
||
}
|
||
|
||
# sing-box funcs
|
||
|
||
sing_box_uci() {
|
||
local sing_box_enabled sing_box_user sing_box_config_path sing_box_conffile
|
||
sing_box_enabled=$(uci get "sing-box.main.enabled")
|
||
sing_box_user=$(uci get "sing-box.main.user")
|
||
if [ "$sing_box_enabled" -ne 1 ]; then
|
||
uci set "sing-box.main.enabled=1"
|
||
uci commit "sing-box"
|
||
log "sing-box service has been enabled"
|
||
fi
|
||
|
||
if [ "$sing_box_user" != "root" ]; then
|
||
uci set "sing-box.main.user=root"
|
||
uci commit "sing-box"
|
||
log "sing-box service user has been changed to root"
|
||
fi
|
||
|
||
config_get sing_box_config_path "settings" "config_path"
|
||
sing_box_conffile=$(uci get "sing-box.main.conffile")
|
||
log "sing-box config path: $sing_box_config_path" "debug"
|
||
log "sing-box service conffile: $sing_box_conffile" "debug"
|
||
if [ "$sing_box_conffile" != "$sing_box_config_path" ]; then
|
||
uci set "sing-box.main.conffile=$sing_box_config_path"
|
||
uci commit "sing-box"
|
||
log "Configuration file path has been set to $sing_box_config_path"
|
||
fi
|
||
|
||
[ -f /etc/rc.d/S99sing-box ] && log "Disable sing-box" && /etc/init.d/sing-box disable
|
||
}
|
||
|
||
sing_box_init_config() {
|
||
local config='{"log":{},"dns":{},"ntp":{},"certificate":{},"endpoints":[],"inbounds":[],"outbounds":[],"route":{},"services":[],"experimental":{}}'
|
||
|
||
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
|
||
sing_box_save_config
|
||
}
|
||
|
||
sing_box_configure_log() {
|
||
log "Configure the log section of a sing-box JSON configuration"
|
||
|
||
config=$(sing_box_cm_configure_log "$config" false "$SB_DEFAULT_LOG_LEVEL" false)
|
||
}
|
||
|
||
sing_box_configure_inbounds() {
|
||
log "Configure the inbounds section of a sing-box JSON configuration"
|
||
|
||
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_configure_outbounds() {
|
||
log "Configure the outbounds section of a sing-box JSON configuration"
|
||
|
||
config=$(sing_box_cm_add_direct_outbound "$config" "$SB_DIRECT_OUTBOUND_TAG")
|
||
|
||
config_foreach configure_outbound_handler "section"
|
||
}
|
||
|
||
configure_outbound_handler() {
|
||
local section="$1"
|
||
|
||
local connection_type
|
||
config_get connection_type "$section" "connection_type"
|
||
case "$connection_type" in
|
||
proxy)
|
||
log "Configuring outbound in proxy connection type for the $section section"
|
||
local proxy_config_type
|
||
config_get proxy_config_type "$section" "proxy_config_type"
|
||
|
||
case "$proxy_config_type" in
|
||
url)
|
||
log "Detected proxy configuration type: url" "debug"
|
||
local proxy_string udp_over_tcp
|
||
config_get proxy_string "$section" "proxy_string"
|
||
config_get udp_over_tcp "$section" "enable_shadowsocks_udp_over_tcp"
|
||
# 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 "Proxy string is not set. Aborted." "fatal"
|
||
exit 1
|
||
fi
|
||
config=$(sing_box_cf_add_proxy_outbound "$config" "$section" "$active_proxy_string" "$udp_over_tcp")
|
||
;;
|
||
outbound)
|
||
log "Detected proxy configuration type: outbound" "debug"
|
||
local json_outbound
|
||
config_get json_outbound "$section" "outbound_json"
|
||
config=$(sing_box_cf_add_json_outbound "$config" "$section" "$json_outbound")
|
||
;;
|
||
urltest)
|
||
log "Detected proxy configuration type: urltest" "debug"
|
||
local urltest_proxy_links udp_over_tcp i urltest_tag selector_tag outbound_tag outbound_tags \
|
||
urltest_outbounds selector_outbounds
|
||
config_get urltest_proxy_links "$section" "urltest_proxy_links"
|
||
config_get udp_over_tcp "$section" "enable_shadowsocks_udp_over_tcp"
|
||
|
||
if [ -z "$urltest_proxy_links" ]; then
|
||
log "URLTest proxy links is not set. Aborted." "fatal"
|
||
exit 1
|
||
fi
|
||
|
||
i=1
|
||
for link in $urltest_proxy_links; do
|
||
config="$(sing_box_cf_add_proxy_outbound "$config" "$section-$i" "$link" "$udp_over_tcp")"
|
||
outbound_tag="$(get_outbound_tag_by_section "$section-$i")"
|
||
if [ -z "$outbound_tags" ]; then
|
||
outbound_tags="$outbound_tag"
|
||
else
|
||
outbound_tags="$outbound_tags,$outbound_tag"
|
||
fi
|
||
i=$((i + 1))
|
||
done
|
||
|
||
urltest_tag="$(get_outbound_tag_by_section "$section-urltest")"
|
||
selector_tag="$(get_outbound_tag_by_section "$section")"
|
||
urltest_outbounds="$(comma_string_to_json_array "$outbound_tags")"
|
||
selector_outbounds="$(comma_string_to_json_array "$outbound_tags,$urltest_tag")"
|
||
config="$(sing_box_cm_add_urltest_outbound "$config" "$urltest_tag" "$urltest_outbounds")"
|
||
config="$(sing_box_cm_add_selector_outbound "$config" "$selector_tag" "$selector_outbounds" "$urltest_tag")"
|
||
;;
|
||
*)
|
||
log "Unknown proxy configuration type: '$proxy_config_type'. Aborted." "fatal"
|
||
exit 1
|
||
;;
|
||
esac
|
||
;;
|
||
vpn)
|
||
log "Configuring outbound in VPN connection type for the $section section"
|
||
local interface_name domain_resolver_enabled domain_resolver_dns_type domain_resolver_dns_server \
|
||
domain_resolver_dns_server_address outbound_tag domain_resolver_tag dns_domain_resolver
|
||
|
||
config_get interface_name "$section" "interface"
|
||
config_get domain_resolver_enabled "$section" "domain_resolver_enabled"
|
||
config_get domain_resolver_dns_type "$section" "domain_resolver_dns_type"
|
||
config_get domain_resolver_dns_server "$section" "domain_resolver_dns_server"
|
||
|
||
if [ -z "$interface_name" ]; then
|
||
log "VPN interface is not set. Aborted." "fatal"
|
||
exit 1
|
||
fi
|
||
|
||
local outbound_tag
|
||
outbound_tag="$(get_outbound_tag_by_section "$section")"
|
||
|
||
if [ "$domain_resolver_enabled" -eq 1 ]; then
|
||
domain_resolver_dns_server_address="$(url_get_host "$dns_server")"
|
||
if ! is_ipv4 "$domain_resolver_dns_server_address"; then
|
||
dns_domain_resolver=$SB_BOOTSTRAP_SERVER_TAG
|
||
fi
|
||
domain_resolver_tag="$(get_domain_resolver_tag "$section")"
|
||
config=$(sing_box_cf_add_dns_server "$config" "$domain_resolver_dns_type" "$domain_resolver_tag" \
|
||
"$domain_resolver_dns_server" "$dns_domain_resolver" "$outbound_tag")
|
||
fi
|
||
|
||
config=$(sing_box_cm_add_interface_outbound "$config" "$outbound_tag" "$interface_name" "$domain_resolver_tag")
|
||
;;
|
||
block)
|
||
log "Connection type 'block' detected for the $section section – no outbound will be created (handled via reject route rules)"
|
||
;;
|
||
*)
|
||
log "Unknown connection type '$connection_type' for the $section section. Aborted." "fatal"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
sing_box_configure_dns() {
|
||
log "Configure the DNS section of a sing-box JSON configuration"
|
||
config=$(sing_box_cm_configure_dns "$config" "$SB_DNS_SERVER_TAG" "ipv4_only" true)
|
||
|
||
log "Adding DNS Servers" "debug"
|
||
local dns_type dns_server bootstrap_dns_server dns_domain_resolver dns_server_address
|
||
config_get dns_type "settings" "dns_type" "doh"
|
||
config_get dns_server "settings" "dns_server" "1.1.1.1"
|
||
config_get bootstrap_dns_server "settings" "bootstrap_dns_server" "77.88.8.8"
|
||
|
||
dns_server_address="$(url_get_host "$dns_server")"
|
||
if ! is_ipv4 "$dns_server_address"; then
|
||
dns_domain_resolver=$SB_BOOTSTRAP_SERVER_TAG
|
||
fi
|
||
|
||
config=$(sing_box_cm_add_udp_dns_server "$config" "$SB_BOOTSTRAP_SERVER_TAG" "$bootstrap_dns_server" 53)
|
||
config=$(sing_box_cf_add_dns_server "$config" "$dns_type" "$SB_DNS_SERVER_TAG" "$dns_server" "$dns_domain_resolver")
|
||
config=$(sing_box_cm_add_fakeip_dns_server "$config" "$SB_FAKEIP_DNS_SERVER_TAG" "$SB_FAKEIP_INET4_RANGE")
|
||
|
||
log "Adding DNS Rules"
|
||
local rewrite_ttl service_domains
|
||
config_get rewrite_ttl "settings" "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 "$FAKEIP_TEST_DOMAIN,$CHECK_PROXY_IP_DOMAIN")
|
||
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "domain" "$service_domains")
|
||
}
|
||
|
||
sing_box_configure_route() {
|
||
log "Configure the route section of a sing-box JSON configuration"
|
||
|
||
config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG")
|
||
|
||
local sniff_inbounds
|
||
sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG")
|
||
config=$(sing_box_cm_sniff_route_rule "$config" "inbound" "$sniff_inbounds")
|
||
|
||
config=$(sing_box_cm_add_hijack_dns_route_rule "$config" "protocol" "dns")
|
||
|
||
local disable_quic
|
||
config_get_bool disable_quic "settings" "disable_quic" 0
|
||
if [ "$disable_quic" -eq 1 ]; then
|
||
config=$(sing_box_cf_add_single_key_reject_rule "$config" "$SB_TPROXY_INBOUND_TAG" "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" "$FAKEIP_TEST_DOMAIN" 8443)
|
||
|
||
config_foreach include_source_ips_in_routing_handler "section"
|
||
|
||
configure_common_reject_route_rule
|
||
|
||
local routing_excluded_ips
|
||
config_get_bool routing_excluded_ips "settings" "routing_excluded_ips"
|
||
if [ -n "$routing_excluded_ips" ]; 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 "settings" "routing_excluded_ips" exclude_source_ip_from_routing_handler "$rule_tag"
|
||
fi
|
||
|
||
config_foreach configure_routing_for_section_lists "section"
|
||
}
|
||
|
||
include_source_ips_in_routing_handler() {
|
||
local section="$1"
|
||
|
||
local fully_routed_ips rule_tag
|
||
config_get fully_routed_ips "$section" "fully_routed_ips"
|
||
if [ -n "$fully_routed_ips" ]; 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" "fully_routed_ips" include_source_ip_in_routing_handler "$rule_tag"
|
||
fi
|
||
}
|
||
|
||
configure_common_reject_route_rule() {
|
||
local block_sections block_section_lists_enabled
|
||
block_sections="$(get_block_sections)"
|
||
block_section_lists_enabled=0
|
||
|
||
if [ -n "$block_sections" ]; then
|
||
for block_section in $block_sections; do
|
||
if section_has_enabled_lists "$block_section"; then
|
||
block_section_lists_enabled=1
|
||
break
|
||
fi
|
||
done
|
||
if [ "$block_section_lists_enabled" -eq 1 ]; then
|
||
config=$(sing_box_cm_add_reject_route_rule "$config" "$SB_REJECT_RULE_TAG" "$SB_TPROXY_INBOUND_TAG")
|
||
else
|
||
log "Block sections does not have any enabled list, reject rule is not required" "warn"
|
||
fi
|
||
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"
|
||
|
||
log "Configuring routing for '$section' section"
|
||
if ! section_has_enabled_lists "$section"; then
|
||
log "Section '$section' does not have any enabled list, skipping..." "warn"
|
||
return 0
|
||
fi
|
||
|
||
local community_lists user_domain_list_type user_subnet_list_type local_domain_lists local_subnet_lists \
|
||
remote_domain_lists remote_subnet_lists section_connection_type route_rule_tag
|
||
config_get_bool community_lists "$section" "community_lists"
|
||
config_get user_domain_list_type "$section" "user_domain_list_type" "disabled"
|
||
config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled"
|
||
config_get_bool local_domain_lists "$section" "local_domain_lists"
|
||
config_get_bool local_subnet_lists "$section" "local_subnet_lists"
|
||
config_get_bool remote_domain_lists "$section" "remote_domain_lists"
|
||
config_get_bool remote_subnet_lists "$section" "remote_subnet_lists"
|
||
config_get section_connection_type "$section" "connection_type"
|
||
|
||
if [ "$section_connection_type" = "block" ]; then
|
||
route_rule_tag="$SB_REJECT_RULE_TAG"
|
||
else
|
||
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")
|
||
fi
|
||
|
||
if [ -n "$community_lists" ]; then
|
||
log "Processing community list routing rules for '$section' section"
|
||
config_list_foreach "$section" "community_lists" configure_community_list_handler "$section" "$route_rule_tag"
|
||
fi
|
||
|
||
if [ "$user_domain_list_type" != "disabled" ]; then
|
||
log "Processing user domains routing rules for '$section' section"
|
||
prepare_common_ruleset "$section" "domains" "$route_rule_tag"
|
||
configure_user_domain_or_subnets_list "$section" "domains" "$route_rule_tag"
|
||
fi
|
||
|
||
if [ "$user_subnet_list_type" != "disabled" ]; then
|
||
log "Processing user subnets routing rules for '$section' section"
|
||
prepare_common_ruleset "$section" "subnets" "$route_rule_tag"
|
||
configure_user_domain_or_subnets_list "$section" "subnets" "$route_rule_tag"
|
||
fi
|
||
|
||
if [ -n "$local_domain_lists" ]; then
|
||
log "Processing local domains routing rules for '$section' section"
|
||
configure_local_domain_or_subnet_lists "$section" "domains" "$route_rule_tag"
|
||
fi
|
||
|
||
if [ -n "$local_subnet_lists" ]; then
|
||
log "Processing local subnets routing rules for '$section' section"
|
||
configure_local_domain_or_subnet_lists "$section" "subnets" "$route_rule_tag"
|
||
fi
|
||
|
||
if [ -n "$remote_domain_lists" ]; then
|
||
log "Processing remote domains routing rules for '$section' section"
|
||
prepare_common_ruleset "$section" "domains" "$route_rule_tag"
|
||
config_list_foreach "$section" "remote_domain_lists" configure_remote_domain_or_subnet_list_handler \
|
||
"domains" "$section" "$route_rule_tag"
|
||
fi
|
||
|
||
if [ -n "$remote_subnet_lists" ]; then
|
||
log "Processing remote subnets routing rules for '$section' section"
|
||
prepare_common_ruleset "$section" "subnets" "$route_rule_tag"
|
||
config_list_foreach "$section" "remote_subnet_lists" configure_remote_domain_or_subnet_list_handler \
|
||
"subnets" "$section" "$route_rule_tag"
|
||
fi
|
||
}
|
||
|
||
prepare_common_ruleset() {
|
||
local section="$1"
|
||
local type="$2"
|
||
local route_rule_tag="$3"
|
||
|
||
log "Preparing a common $type ruleset for '$section' section" "debug"
|
||
ruleset_tag=$(get_ruleset_tag "$section" "common" "$type")
|
||
ruleset_filename="$ruleset_tag.json"
|
||
ruleset_filepath="$TMP_RULESET_FOLDER/$ruleset_filename"
|
||
if file_exists "$ruleset_filepath"; then
|
||
log "Ruleset $ruleset_filepath already exists. Skipping." "debug"
|
||
else
|
||
sing_box_cm_create_local_source_ruleset "$ruleset_filepath"
|
||
config=$(sing_box_cm_add_local_ruleset "$config" "$ruleset_tag" "source" "$ruleset_filepath")
|
||
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag")
|
||
case "$type" in
|
||
domains)
|
||
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_tag")
|
||
;;
|
||
subnets) ;;
|
||
*) log "Unsupported remote rule set type: $type" "warn" ;;
|
||
esac
|
||
fi
|
||
}
|
||
|
||
configure_community_list_handler() {
|
||
local tag="$1"
|
||
local section="$2"
|
||
local route_rule_tag="$3"
|
||
|
||
local ruleset_tag format url update_interval detour
|
||
ruleset_tag="$(get_ruleset_tag "$section" "$tag" "community")"
|
||
format="binary"
|
||
url="$SRS_MAIN_URL/$tag.srs"
|
||
detour="$(get_download_detour_tag)"
|
||
config_get update_interval "settings" "update_interval" "1d"
|
||
|
||
config=$(sing_box_cm_add_remote_ruleset "$config" "$ruleset_tag" "$format" "$url" "$detour" "$update_interval")
|
||
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag")
|
||
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_tag")
|
||
}
|
||
|
||
configure_user_domain_or_subnets_list() {
|
||
local section="$1"
|
||
local type="$2"
|
||
|
||
local items ruleset_tag ruleset_filename ruleset_filepath json_array
|
||
case "$type" in
|
||
domains)
|
||
local user_domain_list_type
|
||
config_get user_domain_list_type "$section" "user_domain_list_type"
|
||
case "$user_domain_list_type" in
|
||
dynamic) config_get items "$section" "user_domains" ;;
|
||
text) config_get items "$section" "user_domains_text" ;;
|
||
esac
|
||
;;
|
||
subnets)
|
||
local user_subnet_list_type
|
||
config_get user_subnet_list_type "$section" "user_subnet_list_type"
|
||
case "$user_subnet_list_type" in
|
||
dynamic) config_get items "$section" "user_subnets" ;;
|
||
text) config_get items "$section" "user_subnets_text" ;;
|
||
esac
|
||
;;
|
||
esac
|
||
|
||
ruleset_tag=$(get_ruleset_tag "$section" "common" "$type")
|
||
ruleset_filename="$ruleset_tag.json"
|
||
ruleset_filepath="$TMP_RULESET_FOLDER/$ruleset_filename"
|
||
items="$(parse_domain_or_subnet_string_to_commas_string "$items" "$type")"
|
||
json_array="$(comma_string_to_json_array "$items")"
|
||
case "$type" in
|
||
domains) sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$json_array" ;;
|
||
subnets)
|
||
sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "ip_cidr" "$json_array"
|
||
nft_add_set_elements "$NFT_TABLE_NAME" "$NFT_COMMON_SET_NAME" "$items"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
configure_local_domain_or_subnet_lists() {
|
||
local section="$1"
|
||
local type="$2"
|
||
local route_rule_tag="$3"
|
||
|
||
local ruleset_tag ruleset_filename ruleset_filepath
|
||
ruleset_tag="$(get_ruleset_tag "$section" "local" "$type")"
|
||
ruleset_filename="$ruleset_tag.json"
|
||
ruleset_filepath="$TMP_RULESET_FOLDER/$ruleset_filename"
|
||
|
||
sing_box_cm_create_local_source_ruleset "$ruleset_filepath"
|
||
config=$(sing_box_cm_add_local_ruleset "$config" "$ruleset_tag" "source" "$ruleset_filepath")
|
||
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag")
|
||
|
||
case "$type" in
|
||
domains)
|
||
config_list_foreach "$section" "local_domain_lists" import_local_domain_or_subnet_list "$type" \
|
||
"$section" "$ruleset_filepath"
|
||
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_tag")
|
||
;;
|
||
subnets)
|
||
config_list_foreach "$section" "local_subnet_lists" import_local_domain_or_subnet_list "$type" \
|
||
"$section" "$ruleset_filepath"
|
||
;;
|
||
*) log "Unsupported local rule set type: $type" "warn" ;;
|
||
esac
|
||
}
|
||
|
||
import_local_domain_or_subnet_list() {
|
||
local filepath="$1"
|
||
local type="$2"
|
||
local section="$3"
|
||
local ruleset_filepath="$4"
|
||
|
||
if ! file_exists "$filepath"; then
|
||
log "File $filepath not found" "warn"
|
||
return 1
|
||
fi
|
||
|
||
local items json_array
|
||
items="$(parse_domain_or_subnet_file_to_comma_string "$filepath" "$type")"
|
||
|
||
if [ -z "$items" ]; then
|
||
log "No valid $type found in $filepath"
|
||
return 0
|
||
fi
|
||
|
||
json_array="$(comma_string_to_json_array "$items")"
|
||
case "$type" in
|
||
domains) sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$json_array" ;;
|
||
subnets)
|
||
sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "ip_cidr" "$json_array"
|
||
nft_add_set_elements "$NFT_TABLE_NAME" "$NFT_COMMON_SET_NAME" "$items"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
configure_remote_domain_or_subnet_list_handler() {
|
||
local url="$1"
|
||
local type="$2"
|
||
local section="$3"
|
||
local route_rule_tag="$4"
|
||
|
||
local file_extension
|
||
file_extension=$(url_get_file_extension "$url")
|
||
case "$file_extension" in
|
||
json | srs)
|
||
log "Detected file extension: '$file_extension' → proceeding with processing" "debug"
|
||
local basename ruleset_tag format detour update_interval
|
||
basename=$(url_get_basename "$url")
|
||
ruleset_tag=$(get_ruleset_tag "$section" "$basename" "remote-$type")
|
||
format="$(get_ruleset_format_by_file_extension "$file_extension")"
|
||
detour="$(get_download_detour_tag)"
|
||
config_get update_interval "settings" "update_interval" "1d"
|
||
|
||
config=$(sing_box_cm_add_remote_ruleset "$config" "$ruleset_tag" "$format" "$url" "$detour" "$update_interval")
|
||
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag")
|
||
case "$type" in
|
||
domains)
|
||
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_tag")
|
||
;;
|
||
subnets) ;;
|
||
*) log "Unsupported remote rule set type: $type" "warn" ;;
|
||
esac
|
||
;;
|
||
*)
|
||
log "Detected file extension: '$file_extension' → no processing needed, managed on list_update" "debug"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
sing_box_configure_experimental() {
|
||
log "Configure the experimental section of a sing-box JSON configuration"
|
||
|
||
log "Configuring cache database"
|
||
local cache_file
|
||
config_get cache_file "settings" "cache_path" "/tmp/sing-box/cache.db"
|
||
config=$(sing_box_cm_configure_cache_file "$config" true "$cache_file" true)
|
||
|
||
local enable_yacd external_controller_ui
|
||
config_get_bool enable_yacd "settings" "enable_yacd" 0
|
||
log "Configuring Clash API"
|
||
if [ "$enable_yacd" -eq 1 ]; then
|
||
log "YACD is enabled, enabling Clash API with downloadable YACD" "debug"
|
||
local external_controller_ui="ui"
|
||
config=$(sing_box_cm_configure_clash_api "$config" "$SB_CLASH_API_CONTROLLER" "$external_controller_ui")
|
||
else
|
||
log "YACD is disabled, enabling Clash API in online mode" "debug"
|
||
config=$(sing_box_cm_configure_clash_api "$config" "$SB_CLASH_API_CONTROLLER")
|
||
fi
|
||
}
|
||
|
||
sing_box_additional_inbounds() {
|
||
log "Configure the additional inbounds of a sing-box JSON configuration"
|
||
|
||
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"
|
||
)
|
||
|
||
config_foreach configure_section_mixed_proxy "section"
|
||
}
|
||
|
||
configure_section_mixed_proxy() {
|
||
local section="$1"
|
||
|
||
local mixed_inbound_enabled mixed_proxy_port mixed_inbound_tag mixed_outbound_tag
|
||
config_get_bool mixed_inbound_enabled "$section" "mixed_proxy_enabled" 0
|
||
config_get mixed_proxy_port "$section" "mixed_proxy_port"
|
||
if [ "$mixed_inbound_enabled" -eq 1 ]; then
|
||
mixed_inbound_tag="$(get_inbound_tag_by_section "$section-mixed")"
|
||
mixed_outbound_tag="$(get_outbound_tag_by_section "$section")"
|
||
config=$(
|
||
sing_box_cf_add_mixed_inbound_and_route_rule \
|
||
"$config" \
|
||
"$mixed_inbound_tag" \
|
||
"$SB_MIXED_INBOUND_ADDRESS" \
|
||
"$mixed_proxy_port" \
|
||
"$mixed_outbound_tag"
|
||
)
|
||
fi
|
||
}
|
||
|
||
sing_box_save_config() {
|
||
local sing_box_config_path temp_file_path current_config_hash temp_config_hash
|
||
config_get sing_box_config_path "settings" "config_path"
|
||
temp_file_path="$(mktemp)"
|
||
|
||
log "Save sing-box temporary config to $temp_file_path" "debug"
|
||
sing_box_cm_save_config_to_file "$config" "$temp_file_path"
|
||
|
||
sing_box_config_check "$temp_file_path"
|
||
|
||
current_config_hash=$(md5sum "$sing_box_config_path" 2> /dev/null | awk '{print $1}')
|
||
temp_config_hash=$(md5sum "$temp_file_path" | awk '{print $1}')
|
||
log "Current sing-box config hash: $current_config_hash" "debug"
|
||
log "Temporary sing-box config hash: $temp_config_hash" "debug"
|
||
if [ "$current_config_hash" != "$temp_config_hash" ]; then
|
||
log "sing-box configuration has changed and will be updated"
|
||
mv "$temp_file_path" "$sing_box_config_path"
|
||
else
|
||
log "sing-box configuration is unchanged"
|
||
rm "$temp_file_path"
|
||
fi
|
||
}
|
||
|
||
sing_box_config_check() {
|
||
local config_path="$1"
|
||
|
||
if ! sing-box -c "$config_path" check > /dev/null 2>&1; then
|
||
log "Sing-box configuration $config_path is invalid" "fatal"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
import_community_subnet_lists() {
|
||
local section="$1"
|
||
local community_lists
|
||
config_get community_lists "$section" "community_lists"
|
||
if [ -n "$community_lists" ]; then
|
||
log "Importing community subnet lists for '$section' section"
|
||
config_list_foreach "$section" "community_lists" import_community_service_subnet_list_handler
|
||
fi
|
||
}
|
||
|
||
import_community_service_subnet_list_handler() {
|
||
local service="$1"
|
||
|
||
case "$service" in
|
||
"twitter")
|
||
URL=$SUBNETS_TWITTER
|
||
;;
|
||
"meta")
|
||
URL=$SUBNETS_META
|
||
;;
|
||
"telegram")
|
||
URL=$SUBNETS_TELERAM
|
||
;;
|
||
"cloudflare")
|
||
URL=$SUBNETS_CLOUDFLARE
|
||
;;
|
||
"hetzner")
|
||
URL=$SUBNETS_HETZNER
|
||
;;
|
||
"ovh")
|
||
URL=$SUBNETS_OVH
|
||
;;
|
||
"digitalocean")
|
||
URL=$SUBNETS_DIGITALOCEAN
|
||
;;
|
||
"cloudfront")
|
||
URL=$SUBNETS_CLOUDFRONT
|
||
;;
|
||
"discord")
|
||
URL=$SUBNETS_DISCORD
|
||
nft_create_ipv4_set "$NFT_TABLE_NAME" "$NFT_DISCORD_SET_NAME"
|
||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr \
|
||
"@$NFT_DISCORD_SET_NAME" udp dport '{ 50000-65535 }' meta mark set 0x105 counter
|
||
;;
|
||
*) return 0 ;;
|
||
esac
|
||
|
||
local tmpfile http_proxy_address subnets
|
||
tmpfile=$(mktemp)
|
||
http_proxy_address="$(get_service_proxy_address)"
|
||
|
||
download_to_file "$URL" "$tmpfile" "$http_proxy_address"
|
||
|
||
if [ $? -ne 0 ] || [ ! -s "$tmpfile" ]; then
|
||
log "Download $service list failed" "error"
|
||
return 1
|
||
fi
|
||
|
||
subnets="$(parse_domain_or_subnet_file_to_comma_string "$tmpfile" "subnets")"
|
||
rm -f "$tmpfile"
|
||
|
||
if [ "$service" = "discord" ]; then
|
||
nft_add_set_elements "$NFT_TABLE_NAME" "$NFT_DISCORD_SET_NAME" "$subnets"
|
||
else
|
||
nft_add_set_elements "$NFT_TABLE_NAME" "$NFT_COMMON_SET_NAME" "$subnets"
|
||
fi
|
||
}
|
||
|
||
import_domains_from_remote_domain_lists() {
|
||
local section="$1"
|
||
local remote_domain_lists
|
||
config_get remote_domain_lists "$section" "remote_domain_lists"
|
||
if [ -n "$remote_domain_lists" ]; then
|
||
log "Importing domains from remote domain lists for '$section' section"
|
||
config_list_foreach "$section" "remote_domain_lists" import_domains_from_remote_domain_list_handler "$section"
|
||
fi
|
||
}
|
||
|
||
import_domains_from_remote_domain_list_handler() {
|
||
local url="$1"
|
||
local section="$2"
|
||
|
||
log "Importing domains from URL: $url"
|
||
|
||
local file_extension
|
||
file_extension=$(url_get_file_extension "$url")
|
||
case "$file_extension" in
|
||
json | srs)
|
||
log "Detected file extension: '$file_extension' → no update needed, sing-box manages updates" "debug"
|
||
;;
|
||
*)
|
||
log "Detected file extension: '$file_extension' → proceeding with processing" "debug"
|
||
import_domains_or_subnets_from_remote_file "$url" "$section" "domains"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
import_subnets_from_remote_subnet_lists() {
|
||
local section="$1"
|
||
local remote_subnet_lists
|
||
config_get remote_subnet_lists "$section" "remote_subnet_lists"
|
||
if [ -n "$remote_subnet_lists" ]; then
|
||
log "Importing subnets from remote subnet lists for '$section' section"
|
||
config_list_foreach "$section" "remote_subnet_lists" import_subnets_from_remote_subnet_list_handler "$section"
|
||
fi
|
||
}
|
||
|
||
import_subnets_from_remote_subnet_list_handler() {
|
||
local url="$1"
|
||
local section="$2"
|
||
|
||
log "Importing subnets from URL: $url"
|
||
|
||
local file_extension
|
||
file_extension="$(url_get_file_extension "$url")"
|
||
case "$file_extension" in
|
||
json)
|
||
log "Detected file extension: '$file_extension' → proceeding with processing" "debug"
|
||
import_subnets_from_remote_json_file "$url"
|
||
;;
|
||
srs)
|
||
log "Detected file extension: '$file_extension' → proceeding with processing" "debug"
|
||
import_subnets_from_remote_srs_file "$url"
|
||
;;
|
||
*)
|
||
log "Detected file extension: '$file_extension' → proceeding with processing" "debug"
|
||
import_domains_or_subnets_from_remote_file "$url" "$section" "subnets"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
import_domains_or_subnets_from_remote_file() {
|
||
local url="$1"
|
||
local section="$2"
|
||
local type="$3"
|
||
|
||
local tmpfile http_proxy_address items json_array
|
||
tmpfile=$(mktemp)
|
||
http_proxy_address="$(get_service_proxy_address)"
|
||
|
||
download_to_file "$url" "$tmpfile" "$http_proxy_address"
|
||
|
||
if [ $? -ne 0 ] || [ ! -s "$tmpfile" ]; then
|
||
log "Download $url list failed" "error"
|
||
return 1
|
||
fi
|
||
|
||
items="$(parse_domain_or_subnet_file_to_comma_string "$tmpfile" "$type")"
|
||
rm -f "$tmpfile"
|
||
|
||
if [ -z "$items" ]; then
|
||
log "No valid $type found in $url"
|
||
return 0
|
||
fi
|
||
|
||
ruleset_tag=$(get_ruleset_tag "$section" "common" "$type")
|
||
ruleset_filename="$ruleset_tag.json"
|
||
ruleset_filepath="$TMP_RULESET_FOLDER/$ruleset_filename"
|
||
json_array="$(comma_string_to_json_array "$items")"
|
||
case "$type" in
|
||
domains) sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$json_array" ;;
|
||
subnets)
|
||
sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "ip_cidr" "$json_array"
|
||
nft_add_set_elements "$NFT_TABLE_NAME" "$NFT_COMMON_SET_NAME" "$items"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
import_subnets_from_remote_json_file() {
|
||
local url="$1"
|
||
local tmpfile subnets http_proxy_address
|
||
tmpfile="$(mktemp)"
|
||
http_proxy_address="$(get_service_proxy_address)"
|
||
|
||
download_to_stream "$url" "$http_proxy_address" | jq -r '.rules[].ip_cidr[]?' > "$tmpfile"
|
||
|
||
if [ $? -ne 0 ] || [ ! -s "$tmpfile" ]; then
|
||
log "Download $url list failed" "error"
|
||
return 1
|
||
fi
|
||
|
||
subnets="$(parse_domain_or_subnet_file_to_comma_string "$tmpfile" "subnets")"
|
||
rm -f "$tmpfile"
|
||
nft_add_set_elements "$NFT_TABLE_NAME" "$NFT_COMMON_SET_NAME" "$subnets"
|
||
}
|
||
|
||
import_subnets_from_remote_srs_file() {
|
||
local url="$1"
|
||
|
||
local binary_tmpfile json_tmpfile subnets_tmpfile subnets http_proxy_address
|
||
binary_tmpfile="$(mktemp)"
|
||
json_tmpfile="$(mktemp)"
|
||
subnets_tmpfile="$(mktemp)"
|
||
http_proxy_address="$(get_service_proxy_address)"
|
||
|
||
download_to_file "$url" "$binary_tmpfile" "$http_proxy_address"
|
||
|
||
if [ $? -ne 0 ] || [ ! -s "$binary_tmpfile" ]; then
|
||
log "Download $url list failed" "error"
|
||
return 1
|
||
fi
|
||
|
||
if ! decompile_srs_file "$binary_tmpfile" "$json_tmpfile"; then
|
||
log "Failed to decompile SRS file" "error"
|
||
return 1
|
||
fi
|
||
|
||
jq -r '.rules[].ip_cidr[]' "$json_tmpfile" > "$subnets_tmpfile"
|
||
subnets="$(parse_domain_or_subnet_file_to_comma_string "$subnets_tmpfile" "subnets")"
|
||
rm -f "$binary_tmpfile" "$json_tmpfile" "$subnets_tmpfile"
|
||
nft_add_set_elements "$NFT_TABLE_NAME" "$NFT_COMMON_SET_NAME" "$subnets"
|
||
}
|
||
|
||
## Support functions
|
||
get_service_proxy_address() {
|
||
local download_lists_via_proxy
|
||
config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" 0
|
||
if [ "$download_lists_via_proxy" -eq 1 ]; then
|
||
echo "$SB_SERVICE_MIXED_INBOUND_ADDRESS:$SB_SERVICE_MIXED_INBOUND_PORT"
|
||
else
|
||
echo ""
|
||
fi
|
||
}
|
||
|
||
get_download_detour_tag() {
|
||
config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" 0
|
||
if [ "$download_lists_via_proxy" -eq 1 ]; then
|
||
echo "$SB_MAIN_OUTBOUND_TAG"
|
||
else
|
||
echo ""
|
||
fi
|
||
}
|
||
|
||
get_block_sections() {
|
||
uci show podkop | grep "\.connection_type='block'" | cut -d'.' -f2
|
||
}
|
||
|
||
block_section_exists() {
|
||
if uci show podkop | grep -q "\.connection_type='block'"; then
|
||
return 0
|
||
else
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
section_has_enabled_lists() {
|
||
local section="$1"
|
||
local community_lists user_domain_list_type user_subnet_list_type local_domain_lists local_subnet_lists \
|
||
remote_domain_lists remote_subnet_lists
|
||
|
||
config_get_bool community_lists "$section" "community_lists"
|
||
config_get user_domain_list_type "$section" "user_domain_list_type" "disabled"
|
||
config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled"
|
||
config_get_bool local_domain_lists "$section" "local_domain_lists"
|
||
config_get_bool local_subnet_lists "$section" "local_subnet_lists"
|
||
config_get_bool remote_domain_lists "$section" "remote_domain_lists"
|
||
config_get_bool remote_subnet_lists "$section" "remote_subnet_lists"
|
||
|
||
if [ -n "$community_lists" ] ||
|
||
[ "$user_domain_list_type" != "disabled" ] ||
|
||
[ "$user_subnet_list_type" != "disabled" ] ||
|
||
[ -n "$local_domain_lists" ] ||
|
||
[ -n "$local_subnet_lists" ] ||
|
||
[ -n "$remote_domain_lists" ] ||
|
||
[ -n "$remote_subnet_lists" ]; then
|
||
return 0
|
||
else
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
## nftables
|
||
nft_list_all_traffic_from_ip() {
|
||
local ip="$1"
|
||
|
||
if ! nft list chain inet "$NFT_TABLE_NAME" mangle | grep -q "ip saddr $ip"; then
|
||
nft insert rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip saddr "$ip" meta l4proto tcp meta mark set 0x105 counter
|
||
nft insert rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip saddr "$ip" meta l4proto udp meta mark set 0x105 counter
|
||
nft insert rule inet "$NFT_TABLE_NAME" mangle ip saddr "$ip" ip daddr @localv4 return
|
||
fi
|
||
}
|
||
|
||
# Diagnotics
|
||
check_proxy() {
|
||
local sing_box_config_path
|
||
config_get sing_box_config_path "settings" "config_path"
|
||
|
||
if ! command -v sing-box > /dev/null 2>&1; then
|
||
nolog "sing-box is not installed"
|
||
return 1
|
||
fi
|
||
|
||
if [ ! -f "$sing_box_config_path" ]; then
|
||
nolog "Configuration file not found"
|
||
return 1
|
||
fi
|
||
|
||
nolog "Checking sing-box configuration..."
|
||
|
||
if ! sing-box -c "$sing_box_config_path" check > /dev/null; then
|
||
nolog "Invalid configuration"
|
||
return 1
|
||
fi
|
||
|
||
jq '
|
||
walk(
|
||
if type == "object" then
|
||
with_entries(
|
||
if .key == "uuid" then
|
||
.value = "MASKED"
|
||
elif .key == "server" then
|
||
.value = "MASKED"
|
||
elif .key == "server_name" then
|
||
.value = "MASKED"
|
||
elif .key == "password" then
|
||
.value = "MASKED"
|
||
elif .key == "public_key" then
|
||
.value = "MASKED"
|
||
elif .key == "short_id" then
|
||
.value = "MASKED"
|
||
elif .key == "fingerprint" then
|
||
.value = "MASKED"
|
||
elif .key == "server_port" then
|
||
.value = "MASKED"
|
||
else . end
|
||
)
|
||
else . end
|
||
)' "$sing_box_config_path"
|
||
|
||
nolog "Checking proxy connection..."
|
||
|
||
for attempt in $(seq 1 5); do
|
||
response=$(sing-box tools fetch ifconfig.me -D /etc/sing-box 2> /dev/null)
|
||
if echo "$response" | grep -q "^<html\|403 Forbidden"; then
|
||
continue
|
||
fi
|
||
if [[ $response =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||
ip=$(echo "$response" | sed -n 's/^[0-9]\+\.[0-9]\+\.[0-9]\+\.\([0-9]\+\)$/X.X.X.\1/p')
|
||
nolog "$ip - should match proxy IP"
|
||
return 0
|
||
elif echo "$response" | grep -q "^[0-9a-fA-F:]*::[0-9a-fA-F:]*$\|^[0-9a-fA-F:]\+$"; then
|
||
ip=$(echo "$response" | sed 's/\([0-9a-fA-F]\+:[0-9a-fA-F]\+:[0-9a-fA-F]\+\):.*/\1:XXXX:XXXX:XXXX/')
|
||
nolog "$ip - should match proxy IP"
|
||
return 0
|
||
fi
|
||
if [ $attempt -eq 5 ]; then
|
||
nolog "Failed to get valid IP address after 5 attempts"
|
||
if [ -z "$response" ]; then
|
||
nolog "Error: Empty response"
|
||
else
|
||
nolog "Error response: $response"
|
||
fi
|
||
return 1
|
||
fi
|
||
done
|
||
}
|
||
|
||
check_nft() {
|
||
if ! command -v nft > /dev/null 2>&1; then
|
||
nolog "nft is not installed"
|
||
return 1
|
||
fi
|
||
|
||
nolog "Checking $NFT_TABLE_NAME rules..."
|
||
|
||
# Check if table exists
|
||
if ! nft list table inet "$NFT_TABLE_NAME" > /dev/null 2>&1; then
|
||
nolog "❌ $NFT_TABLE_NAME not found"
|
||
return 1
|
||
fi
|
||
|
||
local found_hetzner=0
|
||
local found_ovh=0
|
||
|
||
check_domain_list_contains() {
|
||
local section="$1"
|
||
|
||
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
|
||
if [ "$domain_list_enabled" -eq 1 ]; then
|
||
config_list_foreach "$section" "domain_list" check_domain_value
|
||
fi
|
||
}
|
||
|
||
check_domain_value() {
|
||
local domain_value="$1"
|
||
|
||
if [ "$domain_value" = "hetzner" ]; then
|
||
found_hetzner=1
|
||
elif [ "$domain_value" = "ovh" ]; then
|
||
found_ovh=1
|
||
fi
|
||
}
|
||
|
||
config_foreach check_domain_list_contains
|
||
|
||
if [ "$found_hetzner" -eq 1 ] || [ "$found_ovh" -eq 1 ]; then
|
||
|
||
local sets="podkop_subnets podkop_domains interfaces podkop_discord_subnets localv4"
|
||
|
||
nolog "Sets statistics:"
|
||
for set_name in $sets; do
|
||
if nft list set inet "$NFT_TABLE_NAME" $set_name > /dev/null 2>&1; then
|
||
# Count elements using grep to count commas and add 1 (last element has no comma)
|
||
local count=$(nft list set inet "$NFT_TABLE_NAME" $set_name 2> /dev/null | grep -o ',\|{' | wc -l)
|
||
echo "- $set_name: $count elements"
|
||
fi
|
||
done
|
||
|
||
nolog "Chain configurations:"
|
||
|
||
# Create a temporary file for processing
|
||
local tmp_file=$(mktemp)
|
||
nft list table inet "$NFT_TABLE_NAME" > "$tmp_file"
|
||
|
||
# Extract chain configurations without element listings
|
||
sed -n '/chain mangle {/,/}/p' "$tmp_file" | grep -v "elements" | grep -v "^[[:space:]]*[0-9]"
|
||
sed -n '/chain proxy {/,/}/p' "$tmp_file" | grep -v "elements" | grep -v "^[[:space:]]*[0-9]"
|
||
|
||
# Clean up
|
||
rm -f "$tmp_file"
|
||
else
|
||
# Simple view as originally implemented
|
||
nolog "Sets configuration:"
|
||
nft list table inet "$NFT_TABLE_NAME"
|
||
fi
|
||
|
||
nolog "NFT check completed"
|
||
}
|
||
|
||
check_github() {
|
||
nolog "Checking GitHub connectivity..."
|
||
|
||
if ! curl -m 3 github.com; then
|
||
nolog "Error: Cannot connect to GitHub"
|
||
return 1
|
||
fi
|
||
nolog "GitHub is accessible"
|
||
|
||
nolog "Checking lists availability:"
|
||
for url in "$DOMAINS_RU_INSIDE" "$DOMAINS_RU_OUTSIDE" "$DOMAINS_UA" "$DOMAINS_YOUTUBE" \
|
||
"$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do
|
||
local list_name=$(basename "$url")
|
||
|
||
config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" "0"
|
||
if [ "$download_lists_via_proxy" -eq 1 ]; then
|
||
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -q -O /dev/null "$url"
|
||
else
|
||
wget -q -O /dev/null "$url"
|
||
fi
|
||
|
||
if [ $? -eq 0 ]; then
|
||
nolog "- $list_name: available"
|
||
else
|
||
nolog "- $list_name: not available"
|
||
fi
|
||
done
|
||
}
|
||
|
||
check_dnsmasq() {
|
||
nolog "Checking dnsmasq configuration..."
|
||
|
||
local config=$(uci show dhcp.@dnsmasq[0])
|
||
if [ -z "$config" ]; then
|
||
nolog "No dnsmasq configuration found"
|
||
return 1
|
||
fi
|
||
|
||
echo "$config" | while IFS='=' read -r key value; do
|
||
nolog "$key = $value"
|
||
done
|
||
}
|
||
|
||
check_sing_box_connections() {
|
||
nolog "Checking sing-box connections..."
|
||
|
||
if ! command -v netstat > /dev/null 2>&1; then
|
||
nolog "netstat is not installed"
|
||
return 1
|
||
fi
|
||
|
||
local connections=$(netstat -tuanp | grep sing-box)
|
||
if [ -z "$connections" ]; then
|
||
nolog "No active sing-box connections found"
|
||
return 1
|
||
fi
|
||
|
||
echo "$connections" | while read -r line; do
|
||
nolog "$line"
|
||
done
|
||
}
|
||
|
||
check_sing_box_logs() {
|
||
nolog "Showing sing-box logs from system journal..."
|
||
|
||
local logs=$(logread -e sing-box | tail -n 50)
|
||
if [ -z "$logs" ]; then
|
||
nolog "No sing-box logs found"
|
||
return 1
|
||
fi
|
||
|
||
echo "$logs"
|
||
}
|
||
|
||
check_logs() {
|
||
nolog "Showing podkop logs from system journal..."
|
||
|
||
if ! command -v logread > /dev/null 2>&1; then
|
||
nolog "Error: logread command not found"
|
||
return 1
|
||
fi
|
||
|
||
# Get all logs first
|
||
local all_logs=$(logread)
|
||
|
||
# Find the last occurrence of "Starting podkop"
|
||
local start_line=$(echo "$all_logs" | grep -n "podkop.*Starting podkop" | tail -n 1 | cut -d: -f1)
|
||
|
||
if [ -z "$start_line" ]; then
|
||
nolog "No 'Starting podkop' message found in logs"
|
||
return 1
|
||
fi
|
||
|
||
# Output all logs from the last start
|
||
echo "$all_logs" | tail -n +"$start_line"
|
||
}
|
||
|
||
show_sing_box_config() {
|
||
local sing_box_config_path
|
||
config_get sing_box_config_path "settings" "config_path"
|
||
nolog "Current sing-box configuration:"
|
||
|
||
if [ ! -f "$sing_box_config_path" ]; then
|
||
nolog "Configuration file not found"
|
||
return 1
|
||
fi
|
||
|
||
jq '
|
||
walk(
|
||
if type == "object" then
|
||
with_entries(
|
||
if .key == "uuid" then
|
||
.value = "MASKED"
|
||
elif .key == "server" then
|
||
.value = "MASKED"
|
||
elif .key == "server_name" then
|
||
.value = "MASKED"
|
||
elif .key == "password" then
|
||
.value = "MASKED"
|
||
elif .key == "public_key" then
|
||
.value = "MASKED"
|
||
elif .key == "short_id" then
|
||
.value = "MASKED"
|
||
elif .key == "fingerprint" then
|
||
.value = "MASKED"
|
||
elif .key == "server_port" then
|
||
.value = "MASKED"
|
||
else . end
|
||
)
|
||
else . end
|
||
)' "$sing_box_config_path"
|
||
}
|
||
|
||
show_config() {
|
||
if [ ! -f "$PODKOP_CONFIG" ]; then
|
||
nolog "Configuration file not found"
|
||
return 1
|
||
fi
|
||
|
||
tmp_config=$(mktemp)
|
||
|
||
sed -e 's/\(option proxy_string\).*/\1 '\''MASKED'\''/g' \
|
||
-e 's/\(option outbound_json\).*/\1 '\''MASKED'\''/g' \
|
||
-e 's/\(option second_proxy_string\).*/\1 '\''MASKED'\''/g' \
|
||
-e 's/\(option second_outbound_json\).*/\1 '\''MASKED'\''/g' \
|
||
-e 's/\(vless:\/\/[^@]*@\)/vless:\/\/MASKED@/g' \
|
||
-e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \
|
||
-e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \
|
||
-e 's/\(sid=[^&]*\)/sid=MASKED/g' \
|
||
-e 's/\(option dns_server '\''[^'\'']*\.dns\.nextdns\.io'\''\)/option dns_server '\''MASKED.dns.nextdns.io'\''/g' \
|
||
-e "s|\(option dns_server 'dns\.nextdns\.io\)/[^']*|\1/MASKED|" \
|
||
-e 's/\(list urltest_proxy_links\).*/\1 '\''MASKED'\''/g' \
|
||
"$PODKOP_CONFIG" > "$tmp_config"
|
||
|
||
cat "$tmp_config"
|
||
rm -f "$tmp_config"
|
||
}
|
||
|
||
show_version() {
|
||
echo "$PODKOP_VERSION"
|
||
}
|
||
|
||
show_sing_box_version() {
|
||
local version
|
||
version=$(sing-box version | head -n 1 | awk '{print $3}')
|
||
echo "$version"
|
||
}
|
||
|
||
show_system_info() {
|
||
echo "=== OpenWrt Version ==="
|
||
grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2
|
||
echo
|
||
echo "=== Device Model ==="
|
||
cat /tmp/sysinfo/model
|
||
}
|
||
|
||
get_sing_box_status() {
|
||
local running=0
|
||
local enabled=0
|
||
local status=""
|
||
local version=""
|
||
local dns_configured=0
|
||
|
||
# Check if service is enabled
|
||
if [ -x /etc/rc.d/S99sing-box ]; then
|
||
enabled=1
|
||
fi
|
||
|
||
# Check if service is running
|
||
if pgrep -f "sing-box" > /dev/null; then
|
||
running=1
|
||
version=$(sing-box version | head -n 1 | awk '{print $3}')
|
||
fi
|
||
|
||
# Check DNS configuration
|
||
local dns_server
|
||
dns_server=$(uci get dhcp.@dnsmasq[0].server 2> /dev/null)
|
||
if [ "$dns_server" = "127.0.0.42" ]; then
|
||
dns_configured=1
|
||
fi
|
||
|
||
# Format status message
|
||
if [ $running -eq 1 ]; then
|
||
if [ $enabled -eq 1 ]; then
|
||
status="running & enabled"
|
||
else
|
||
status="running but disabled"
|
||
fi
|
||
else
|
||
if [ $enabled -eq 1 ]; then
|
||
status="stopped but enabled"
|
||
else
|
||
status="stopped & disabled"
|
||
fi
|
||
fi
|
||
|
||
echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\",\"dns_configured\":$dns_configured}"
|
||
}
|
||
|
||
get_status() {
|
||
local enabled=0
|
||
local status=""
|
||
|
||
# Check if service is enabled
|
||
if [ -x /etc/rc.d/S99podkop ]; then
|
||
enabled=1
|
||
status="enabled"
|
||
else
|
||
status="disabled"
|
||
fi
|
||
|
||
echo "{\"enabled\":$enabled,\"status\":\"$status\"}"
|
||
}
|
||
|
||
check_dns_available() {
|
||
local dns_type dns_server bootstrap_dns_server
|
||
config_get dns_type "settings" "dns_type"
|
||
config_get dns_server "settings" "dns_server"
|
||
config_get bootstrap_dns_server "settings" "bootstrap_dns_server"
|
||
|
||
local dns_status=0
|
||
local local_dns_status=0
|
||
local bootstrap_dns_status=0
|
||
local dhcp_has_dns_server=0
|
||
local domain="google.com"
|
||
|
||
# Mask NextDNS ID if present
|
||
local display_dns_server="$dns_server"
|
||
if echo "$dns_server" | grep -q "\.dns\.nextdns\.io$"; then
|
||
local nextdns_id
|
||
nextdns_id=$(echo "$dns_server" | cut -d'.' -f1)
|
||
display_dns_server="$(echo "$nextdns_id" | sed 's/./*/g').dns.nextdns.io"
|
||
elif echo "$dns_server" | grep -q "^dns\.nextdns\.io/"; then
|
||
local masked_path
|
||
masked_path=$(echo "$dns_server" | cut -d'/' -f2- | sed 's/./*/g')
|
||
display_dns_server="dns.nextdns.io/$masked_path"
|
||
fi
|
||
|
||
if [ "$dns_type" = "doh" ]; then
|
||
# Check if dns_server already contains a path
|
||
local doh_path="/dns-query"
|
||
if echo "$dns_server" | grep -q "/"; then
|
||
# Path is already present, extract it
|
||
doh_path="/$(echo "$dns_server" | cut -d'/' -f2-)"
|
||
dns_server="$(echo "$dns_server" | cut -d'/' -f1)"
|
||
fi
|
||
|
||
if dig @"$dns_server" "$domain" +https="$doh_path" +timeout=2 +tries=1 > /dev/null 2>&1; then
|
||
dns_status=1
|
||
fi
|
||
elif [ "$dns_type" = "dot" ]; then
|
||
if dig @"$dns_server" "$domain" +tls +timeout=2 +tries=1 > /dev/null 2>&1; then
|
||
dns_status=1
|
||
fi
|
||
elif [ "$dns_type" = "udp" ]; then
|
||
if dig @"$dns_server" "$domain" +timeout=2 +tries=1 > /dev/null 2>&1; then
|
||
dns_status=1
|
||
fi
|
||
fi
|
||
|
||
# Check if local DNS resolver is working
|
||
if dig @127.0.0.1 "$domain" +timeout=2 +tries=1 > /dev/null 2>&1; then
|
||
local_dns_status=1
|
||
fi
|
||
|
||
# Check bootstrap DNS server
|
||
if [ -n "$bootstrap_dns_server" ]; then
|
||
if dig @"$bootstrap_dns_server" "$domain" +timeout=2 +tries=1 > /dev/null 2>&1; then
|
||
bootstrap_dns_status=1
|
||
fi
|
||
fi
|
||
|
||
# Check if /etc/config/dhcp has server 127.0.0.42
|
||
config_load dhcp
|
||
config_foreach check_dhcp_has_podkop_dns dnsmasq
|
||
config_load "$PODKOP_CONFIG"
|
||
|
||
echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"dns_status\":$dns_status,\"local_dns_status\":$local_dns_status,\"bootstrap_dns_server\":\"$bootstrap_dns_server\",\"bootstrap_dns_status\":$bootstrap_dns_status,\"dhcp_has_dns_server\":$dhcp_has_dns_server}" | jq .
|
||
}
|
||
|
||
check_dhcp_has_podkop_dns() {
|
||
local server_list
|
||
config_get server_list "$1" "server"
|
||
|
||
if [ -n "$server_list" ]; then
|
||
for server in $server_list; do
|
||
if [ "$server" = "127.0.0.42" ]; then
|
||
dhcp_has_dns_server=1
|
||
return 0
|
||
fi
|
||
done
|
||
fi
|
||
}
|
||
|
||
check_nft_rules() {
|
||
local table_exist=0
|
||
local rules_mangle_exist=0
|
||
local rules_mangle_counters=0
|
||
local rules_mangle_output_exist=0
|
||
local rules_mangle_output_counters=0
|
||
local rules_proxy_exist=0
|
||
local rules_proxy_counters=0
|
||
local rules_other_mark_exist=0
|
||
|
||
# Generate traffic through PodkopTable
|
||
curl -m 3 -s "http://ip.podkop.fyi/check" > /dev/null 2>&1 &
|
||
local pid1=$!
|
||
curl -m 3 -s "http://fakeip.podkop.fyi/check" > /dev/null 2>&1 &
|
||
local pid2=$!
|
||
|
||
wait $pid1 2>/dev/null
|
||
wait $pid2 2>/dev/null
|
||
sleep 1
|
||
|
||
# Check if PodkopTable exists
|
||
if nft list table inet "$NFT_TABLE_NAME" > /dev/null 2>&1; then
|
||
table_exist=1
|
||
|
||
# Check mangle chain rules
|
||
if nft list chain inet "$NFT_TABLE_NAME" mangle > /dev/null 2>&1; then
|
||
local mangle_output
|
||
mangle_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle)
|
||
if echo "$mangle_output" | grep -q "counter"; then
|
||
rules_mangle_exist=1
|
||
|
||
if echo "$mangle_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then
|
||
rules_mangle_counters=1
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Check mangle_output chain rules
|
||
if nft list chain inet "$NFT_TABLE_NAME" mangle_output > /dev/null 2>&1; then
|
||
local mangle_output_output
|
||
mangle_output_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle_output)
|
||
if echo "$mangle_output_output" | grep -q "counter"; then
|
||
rules_mangle_output_exist=1
|
||
|
||
if echo "$mangle_output_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then
|
||
rules_mangle_output_counters=1
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Check proxy chain rules
|
||
if nft list chain inet "$NFT_TABLE_NAME" proxy > /dev/null 2>&1; then
|
||
local proxy_output
|
||
proxy_output=$(nft list chain inet "$NFT_TABLE_NAME" proxy)
|
||
if echo "$proxy_output" | grep -q "counter"; then
|
||
rules_proxy_exist=1
|
||
|
||
if echo "$proxy_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then
|
||
rules_proxy_counters=1
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Check for other mark rules outside PodkopTable
|
||
nft list tables 2>/dev/null | while read -r _ family table_name; do
|
||
[ -z "$table_name" ] && continue
|
||
|
||
[ "$table_name" = "$NFT_TABLE_NAME" ] && continue
|
||
|
||
if nft list table "$family" "$table_name" 2>/dev/null | grep -q "meta mark set"; then
|
||
touch /tmp/podkop_mark_check.$$
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [ -f /tmp/podkop_mark_check.$$ ]; then
|
||
rules_other_mark_exist=1
|
||
rm -f /tmp/podkop_mark_check.$$
|
||
fi
|
||
|
||
echo "{\"table_exist\":$table_exist,\"rules_mangle_exist\":$rules_mangle_exist,\"rules_mangle_counters\":$rules_mangle_counters,\"rules_mangle_output_exist\":$rules_mangle_output_exist,\"rules_mangle_output_counters\":$rules_mangle_output_counters,\"rules_proxy_exist\":$rules_proxy_exist,\"rules_proxy_counters\":$rules_proxy_counters,\"rules_other_mark_exist\":$rules_other_mark_exist}" | jq .
|
||
}
|
||
|
||
check_sing_box() {
|
||
local sing_box_installed=0
|
||
local sing_box_version_ok=0
|
||
local sing_box_service_exist=0
|
||
local sing_box_autostart_disabled=0
|
||
local sing_box_process_running=0
|
||
local sing_box_ports_listening=0
|
||
|
||
# Check if sing-box is installed
|
||
if command -v sing-box > /dev/null 2>&1; then
|
||
sing_box_installed=1
|
||
|
||
# Check version (must be >= 1.12.4)
|
||
local version
|
||
version=$(sing-box version 2>/dev/null | head -n 1 | awk '{print $3}')
|
||
if [ -n "$version" ]; then
|
||
version=$(echo "$version" | sed 's/^v//')
|
||
local major
|
||
local minor
|
||
local patch
|
||
major=$(echo "$version" | cut -d. -f1)
|
||
minor=$(echo "$version" | cut -d. -f2)
|
||
patch=$(echo "$version" | cut -d. -f3)
|
||
|
||
# Compare version: must be >= 1.12.4
|
||
if [ "$major" -gt 1 ] || \
|
||
[ "$major" -eq 1 ] && [ "$minor" -gt 12 ] || \
|
||
[ "$major" -eq 1 ] && [ "$minor" -eq 12 ] && [ "$patch" -ge 4 ]; then
|
||
sing_box_version_ok=1
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Check if service exists
|
||
if [ -f /etc/init.d/sing-box ]; then
|
||
sing_box_service_exist=1
|
||
|
||
if ! /etc/init.d/sing-box enabled 2>/dev/null; then
|
||
sing_box_autostart_disabled=1
|
||
fi
|
||
fi
|
||
|
||
# Check if process is running
|
||
if pgrep "sing-box" > /dev/null 2>&1; then
|
||
sing_box_process_running=1
|
||
fi
|
||
|
||
# Check if sing-box is listening on required ports
|
||
local port_53_ok=0
|
||
local port_1602_ok=0
|
||
|
||
if netstat -ln 2>/dev/null | grep -q "127.0.0.42:53"; then
|
||
port_53_ok=1
|
||
fi
|
||
|
||
if netstat -ln 2>/dev/null | grep -q "127.0.0.1:1602"; then
|
||
port_1602_ok=1
|
||
fi
|
||
|
||
# Both ports must be listening
|
||
if [ "$port_53_ok" = "1" ] && [ "$port_1602_ok" = "1" ]; then
|
||
sing_box_ports_listening=1
|
||
fi
|
||
|
||
echo "{\"sing_box_installed\":$sing_box_installed,\"sing_box_version_ok\":$sing_box_version_ok,\"sing_box_service_exist\":$sing_box_service_exist,\"sing_box_autostart_disabled\":$sing_box_autostart_disabled,\"sing_box_process_running\":$sing_box_process_running,\"sing_box_ports_listening\":$sing_box_ports_listening}" | jq .
|
||
}
|
||
|
||
#######################################
|
||
# Clash API interface for managing proxies and groups
|
||
# Arguments:
|
||
# $1 - Action: get_proxies, get_proxy_latency, get_group_latency, set_group_proxy
|
||
# $2 - Proxy/Group tag (required for latency and set operations)
|
||
# $3 - Timeout in ms (optional, defaults: 2000 for proxy, 5000 for group) or target proxy tag for set_group_proxy
|
||
# Outputs:
|
||
# JSON formatted response
|
||
# Usage:
|
||
# clash_api get_proxies
|
||
# clash_api get_proxy_latency <proxy_tag> [timeout]
|
||
# clash_api get_group_latency <group_tag> [timeout]
|
||
# clash_api set_group_proxy <group_tag> <proxy_tag>
|
||
#######################################
|
||
|
||
clash_api() {
|
||
local CLASH_URL="127.0.0.1:9090"
|
||
local TEST_URL="https://www.gstatic.com/generate_204"
|
||
local action="$1"
|
||
|
||
case "$action" in
|
||
get_proxies)
|
||
curl -s "$CLASH_URL/proxies" | jq .
|
||
;;
|
||
|
||
get_proxy_latency)
|
||
local proxy_tag="$2"
|
||
local timeout="${3:-2000}"
|
||
|
||
if [ -z "$proxy_tag" ]; then
|
||
echo '{"error":"proxy_tag required"}' | jq .
|
||
return 1
|
||
fi
|
||
|
||
curl -G -s "$CLASH_URL/proxies/$proxy_tag/delay" \
|
||
--data-urlencode "url=$TEST_URL" \
|
||
--data-urlencode "timeout=$timeout" | jq .
|
||
;;
|
||
|
||
get_group_latency)
|
||
local group_tag="$2"
|
||
local timeout="${3:-5000}"
|
||
|
||
if [ -z "$group_tag" ]; then
|
||
echo '{"error":"group_tag required"}' | jq .
|
||
return 1
|
||
fi
|
||
|
||
curl -G -s "$CLASH_URL/group/$group_tag/delay" \
|
||
--data-urlencode "url=$TEST_URL" \
|
||
--data-urlencode "timeout=$timeout" | jq .
|
||
;;
|
||
|
||
set_group_proxy)
|
||
local group_tag="$2"
|
||
local proxy_tag="$3"
|
||
|
||
if [ -z "$group_tag" ] || [ -z "$proxy_tag" ]; then
|
||
echo '{"error":"group_tag and proxy_tag required"}' | jq .
|
||
return 1
|
||
fi
|
||
|
||
local response
|
||
response=$(curl -X PUT -s -w "\n%{http_code}" "$CLASH_URL/proxies/$group_tag" \
|
||
--data-raw "{\"name\":\"$proxy_tag\"}")
|
||
|
||
local http_code
|
||
local body
|
||
http_code=$(echo "$response" | tail -n 1)
|
||
body=$(echo "$response" | sed '$d')
|
||
|
||
case "$http_code" in
|
||
204)
|
||
echo "{\"success\":true,\"group\":\"$group_tag\",\"proxy\":\"$proxy_tag\"}" | jq .
|
||
;;
|
||
404)
|
||
echo "{\"success\":false,\"error\":\"group_not_found\",\"message\":\"$group_tag does not exist\"}" | jq .
|
||
return 1
|
||
;;
|
||
400)
|
||
if echo "$body" | grep -q "not found"; then
|
||
echo "{\"success\":false,\"error\":\"proxy_not_found\",\"message\":\"$proxy_tag not found in group $group_tag\"}" | jq .
|
||
else
|
||
echo '{"success":false,"error":"bad_request","message":"Invalid request"}' | jq .
|
||
fi
|
||
return 1
|
||
;;
|
||
*)
|
||
if [ -n "$body" ]; then
|
||
local body_json
|
||
body_json=$(echo "$body" | jq -c .)
|
||
echo "{\"success\":false,\"http_code\":$http_code,\"body\":$body_json}" | jq .
|
||
else
|
||
echo "{\"success\":false,\"http_code\":$http_code}" | jq .
|
||
fi
|
||
return 1
|
||
;;
|
||
esac
|
||
;;
|
||
|
||
*)
|
||
echo '{"error":"unknown action","available":["get_proxies","get_proxy_latency","get_group_latency","set_group_proxy"]}' | jq .
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
|
||
print_global() {
|
||
local message="$1"
|
||
echo "$message"
|
||
}
|
||
|
||
global_check() {
|
||
local PODKOP_LUCI_VERSION="Unknown"
|
||
[ -n "$1" ] && PODKOP_LUCI_VERSION="$1"
|
||
|
||
print_global "📡 Global check run!"
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "🛠️ System info"
|
||
print_global "🕳️ Podkop: ${PODKOP_VERSION}"
|
||
print_global "🕳️ LuCI App: ${PODKOP_LUCI_VERSION}"
|
||
print_global "📦 Sing-box: $(sing-box version | head -n 1 | awk '{print $3}')"
|
||
print_global "🛜 OpenWrt: $(grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2)"
|
||
print_global "🛜 Device: $(cat /tmp/sysinfo/model)"
|
||
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "📄 Podkop config"
|
||
show_config
|
||
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "🔧 System check"
|
||
|
||
if grep -E "^nameserver\s+([0-9]{1,3}\.){3}[0-9]{1,3}" "$RESOLV_CONF" | grep -vqE "127\.0\.0\.1|0\.0\.0\.0"; then
|
||
print_global "❌ /etc/resolv.conf contains external nameserver:"
|
||
cat /etc/resolv.conf
|
||
echo ""
|
||
else
|
||
print_global "✅ /etc/resolv.conf"
|
||
fi
|
||
|
||
cachesize="$(uci get dhcp.@dnsmasq[0].cachesize 2> /dev/null)"
|
||
noresolv="$(uci get dhcp.@dnsmasq[0].noresolv 2> /dev/null)"
|
||
server="$(uci get dhcp.@dnsmasq[0].server 2> /dev/null)"
|
||
|
||
if [ "$cachesize" != "0" ] || [ "$noresolv" != "1" ] || [ "$server" != "127.0.0.42" ]; then
|
||
print_global "❌ DHCP configuration differs from template. 📄 DHCP config:"
|
||
awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp
|
||
elif [ "$(uci get podkop.main.dont_touch_dhcp 2> /dev/null)" = "1" ]; then
|
||
print_global "⚠️ dont_touch_dhcp is enabled. 📄 DHCP config:"
|
||
awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp
|
||
else
|
||
print_global "✅ /etc/config/dhcp"
|
||
fi
|
||
|
||
if ! pgrep -f "sing-box" > /dev/null; then
|
||
print_global "❌ sing-box is not running"
|
||
else
|
||
print_global "✅ sing-box is running"
|
||
fi
|
||
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "🧱 NFT table"
|
||
check_nft
|
||
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "📄 WAN config"
|
||
if uci show network.wan > /dev/null 2>&1; then
|
||
awk '
|
||
/^config / {
|
||
p = ($2 == "interface" && $3 == "'\''wan'\''")
|
||
proto = ""
|
||
}
|
||
p {
|
||
if ($1 == "option" && $2 == "proto") {
|
||
proto = $3
|
||
print
|
||
} else if (proto == "'\''static'\''" && $1 == "option" && ($2 == "ipaddr" || $2 == "netmask" || $2 == "gateway")) {
|
||
print " option", $2, "'\''******'\''"
|
||
} else if (proto == "'\''pppoe'\''" && $1 == "option" && ($2 == "username" || $2 == "password")) {
|
||
print " option", $2, "'\''******'\''"
|
||
} else {
|
||
print
|
||
}
|
||
}
|
||
' /etc/config/network
|
||
else
|
||
print_global "❌ WAN configuration not found"
|
||
fi
|
||
|
||
if uci show network | grep -q endpoint_host; then
|
||
uci show network | grep endpoint_host | cut -d'=' -f2 | tr -d "'\" " | while read -r host; do
|
||
if [ "$host" = "engage.cloudflareclient.com" ]; then
|
||
print_global "⚠️ WARP detected: $host"
|
||
continue
|
||
fi
|
||
|
||
ip_prefix=$(echo "$host" | cut -d'.' -f1,2)
|
||
if echo "$CLOUDFLARE_OCTETS" | grep -wq "$ip_prefix"; then
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "⚠️ WARP detected: $host"
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if uci show network | grep -q route_allowed_ips; then
|
||
uci show network | grep route_allowed_ips | cut -d"'" -f2 | while read -r value; do
|
||
if [ "$value" = "1" ]; then
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "⚠️ WG Route allowed IP enabled"
|
||
continue
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if [ -f "/etc/init.d/zapret" ]; then
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "⚠️ Zapret detected"
|
||
fi
|
||
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "➡️ DNS status"
|
||
dns_info=$(check_dns_available)
|
||
dns_type=$(echo "$dns_info" | jq -r '.dns_type')
|
||
dns_server=$(echo "$dns_info" | jq -r '.dns_server')
|
||
status=$(echo "$dns_info" | jq -r '.status')
|
||
print_global "$dns_type ($dns_server) is $status"
|
||
|
||
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
print_global "🔁 FakeIP"
|
||
|
||
print_global "➡️ DNS resolution: system DNS server"
|
||
nslookup -timeout=2 $FAKEIP_TEST_DOMAIN
|
||
|
||
print_global "➡️ DNS resolution: sing-box DNS server (127.0.0.42)"
|
||
local result
|
||
result=$(nslookup -timeout=2 $FAKEIP_TEST_DOMAIN 127.0.0.42 2>&1)
|
||
echo "$result"
|
||
|
||
if echo "$result" | grep -q "198.18"; then
|
||
print_global "✅ FakeIP is working correctly on router (198.18.x.x)"
|
||
else
|
||
print_global "❌ FakeIP test failed: Domain did not resolve to FakeIP range"
|
||
if ! pgrep -f "sing-box" > /dev/null; then
|
||
print_global " ❌ sing-box is not running"
|
||
else
|
||
print_global " 🤔 sing-box is running"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
show_help() {
|
||
cat << EOF
|
||
Usage: $0 COMMAND
|
||
|
||
Available commands:
|
||
start Start podkop service
|
||
stop Stop podkop service
|
||
reload Reload podkop configuration
|
||
restart Restart podkop service
|
||
enable Enable podkop autostart
|
||
disable Disable podkop autostart
|
||
main Run main podkop process
|
||
list_update Update domain lists
|
||
check_proxy Check proxy connectivity
|
||
check_nft Check NFT rules
|
||
check_nft_rules Check NFT rules status
|
||
check_sing_box Check sing-box installation and status
|
||
check_github Check GitHub connectivity
|
||
check_logs Show podkop logs from system journal
|
||
check_sing_box_connections Show active sing-box connections
|
||
check_sing_box_logs Show sing-box logs
|
||
check_dnsmasq Check DNSMasq configuration
|
||
clash_api Clash API interface for managing proxies and groups
|
||
show_config Display current podkop configuration
|
||
show_version Show podkop version
|
||
show_sing_box_config Show sing-box configuration
|
||
show_sing_box_version Show sing-box version
|
||
show_system_info Show system information
|
||
get_status Get podkop service status
|
||
get_sing_box_status Get sing-box service status
|
||
check_dns_available Check DNS server availability
|
||
global_check Run global system check
|
||
EOF
|
||
}
|
||
|
||
case "$1" in
|
||
start)
|
||
start
|
||
;;
|
||
stop)
|
||
stop
|
||
;;
|
||
reload)
|
||
reload
|
||
;;
|
||
restart)
|
||
restart
|
||
;;
|
||
main)
|
||
main
|
||
;;
|
||
list_update)
|
||
list_update
|
||
;;
|
||
check_proxy)
|
||
check_proxy
|
||
;;
|
||
check_nft)
|
||
check_nft
|
||
;;
|
||
check_nft_rules)
|
||
check_nft_rules
|
||
;;
|
||
check_sing_box)
|
||
check_sing_box
|
||
;;
|
||
check_github)
|
||
check_github
|
||
;;
|
||
check_logs)
|
||
check_logs
|
||
;;
|
||
check_sing_box_connections)
|
||
check_sing_box_connections
|
||
;;
|
||
check_sing_box_logs)
|
||
check_sing_box_logs
|
||
;;
|
||
check_dnsmasq)
|
||
check_dnsmasq
|
||
;;
|
||
clash_api)
|
||
clash_api "$2" "$3" "$4"
|
||
;;
|
||
show_config)
|
||
show_config
|
||
;;
|
||
show_version)
|
||
show_version
|
||
;;
|
||
show_sing_box_config)
|
||
show_sing_box_config
|
||
;;
|
||
show_sing_box_version)
|
||
show_sing_box_version
|
||
;;
|
||
show_system_info)
|
||
show_system_info
|
||
;;
|
||
get_status)
|
||
get_status
|
||
;;
|
||
get_sing_box_status)
|
||
get_sing_box_status
|
||
;;
|
||
check_dns_available)
|
||
check_dns_available
|
||
;;
|
||
global_check)
|
||
global_check "${2:-}"
|
||
;;
|
||
*)
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac |