diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 8b54c62..84ad889 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -32,6 +32,7 @@ TEST_DOMAIN="fakeip.podkop.fyi" INTERFACES_LIST="" SRC_INTERFACE="" RESOLV_CONF="/etc/resolv.conf" +TMP_FOLDER="/tmp/podkop" # Endpoints https://github.com/ampetelin/warp-endpoint-checker CLOUDFLARE_OCTETS="8.47 162.159 188.114" @@ -62,9 +63,9 @@ nolog() { echolog() { local message="$1" - local module="$2" + local level="$2" - log "$message" "$module" + log "$message" "$level" nolog "$message" } @@ -826,7 +827,6 @@ sing_box_configure_route() { config_foreach include_source_ips_in_routing_handler # TODO(ampetelin): Add block rules - config_foreach local exclude_from_ip_enabled config_get_bool exclude_from_ip_enabled "main" "exclude_from_ip_enabled" 0 @@ -906,12 +906,11 @@ configure_routing_for_section_lists() { if [ "$local_domains_list_enabled" -eq 1 ]; then log "Processing local domains routing rules for $section section" - # TODO(ampetelin): it is necessary to implement - # configure_local_domains_list_handler "$section" "$route_rule_tag" + configure_local_domains_lists "$section" "$route_rule_tag" fi if [ "$remote_domains_list_enabled" -eq 1 ]; then - log "Processing local domains routing rules for $section section" + log "Processing remote domains routing rules for $section section" config_list_foreach "$section" "remote_domains_list" configure_remote_domains_or_subnets_list_handler \ "domains" "$section" "$route_rule_tag" fi @@ -934,16 +933,16 @@ configure_community_list_handler() { 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")" + 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 "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") + config=$(sing_box_cm_add_remote_ruleset "$config" "$ruleset_tag" "$format" "$url" "$detour" "$update_interval") + _add_ruleset_to_dns_rules "$ruleset_tag" + config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag") } configure_user_domains_list_handler() { @@ -951,9 +950,58 @@ configure_user_domains_list_handler() { # TODO(ampetelin): it is necessary to implement } -configure_local_domains_list_handler() { +configure_local_domains_lists() { local section="$1" - # TODO(ampetelin): it is necessary to implement + local route_rule_tag="$2" + + local ruleset_tag ruleset_filename ruleset_filepath + ruleset_tag="$(get_ruleset_tag "$section" "local" "domains")" + ruleset_filename="$ruleset_tag.json" + ruleset_filepath="$TMP_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") + _add_ruleset_to_dns_rules "$ruleset_tag" "$route_rule_tag" + + config_list_foreach "$section" "local_domains_list" import_local_domains_ruleset "$section" "$ruleset_filepath" +} + +import_local_domains_ruleset() { + local filepath="$1" + local section="$2" + local ruleset_filepath="$3" + + if ! file_exists "$filepath"; then + log "File $filepath not found" "warn" + return 1 + fi + + local domains="" + while IFS= read -r domain; do + if [ -z "$domain" ]; then + continue + fi + + if ! is_domain "$domain"; then + log "$domain is not domain" "debug" + continue + fi + + if [ -z "$domains" ]; then + domains="$domain" + else + domains="$domains,$domain" + fi + done < "$filepath" + + if [ -z "$domains" ]; then + log "No valid domains found in $filepath" + return 0 + fi + + domains="$(comma_string_to_json_array "$domains")" + sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$domains" } configure_remote_domains_or_subnets_list_handler() { @@ -968,24 +1016,24 @@ configure_remote_domains_or_subnets_list_handler() { json|srs) log "Detected file extension: .$file_extension → proceeding with processing" "debug" - local basename rule_set_tag format detour update_interval + local basename ruleset_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")" + 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 "main" "update_interval" "1d" - config=$(sing_box_cm_add_remote_ruleset "$config" "$rule_set_tag" "$format" "$url" "$detour" "$update_interval") - config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$rule_set_tag") + 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) _add_rule_set_to_dns_rules "$rule_set_tag" "$route_rule_tag" ;; + domains) _add_ruleset_to_dns_rules "$ruleset_tag" "$route_rule_tag" ;; subnets) ;; *) log "Unsupported remote rule set type: $type" "warn" ;; esac ;; *) - log "Detected file extension: .$file_extension → no processing needed, managed on list_update" + log "Detected file extension: .$file_extension → no processing needed, managed on list_update" "debug" ;; esac } @@ -1004,14 +1052,14 @@ _get_download_detour_tag() { fi } -_add_rule_set_to_dns_rules() { - local rule_set_tag="$1" +_add_ruleset_to_dns_rules() { + local ruleset_tag="$1" - config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$rule_set_tag") + config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_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") + config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_INVERT_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_tag") fi } @@ -1290,6 +1338,7 @@ import_subnets_from_remote_srs_file() { rm -f "$binary_filepath" "$json_filepath" } +## Support functions # Decompiles a sing-box SRS binary file into a JSON ruleset file decompile_srs_file() { local binary_filepath="$1" diff --git a/podkop/files/usr/lib/constants.sh b/podkop/files/usr/lib/constants.sh index ee60ebf..713e748 100644 --- a/podkop/files/usr/lib/constants.sh +++ b/podkop/files/usr/lib/constants.sh @@ -1,6 +1,6 @@ SB_CONFIG="/etc/sing-box/config.json" # Log -SB_DEFAULT_LOG_LEVEL="warn" +SB_DEFAULT_LOG_LEVEL="info" # DNS SB_DNS_SERVER_TAG="dns-server" SB_SPLIT_DNS_SERVER_TAG="split-dns-server" diff --git a/podkop/files/usr/lib/helpers.sh b/podkop/files/usr/lib/helpers.sh index 1d21ca4..b759ce4 100644 --- a/podkop/files/usr/lib/helpers.sh +++ b/podkop/files/usr/lib/helpers.sh @@ -2,21 +2,28 @@ is_ipv4() { local ip="$1" local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$" - [[ $ip =~ $regex ]] + [[ "$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 ]] + [[ "$ip" =~ $regex ]] +} + +is_domain() { + local str="$1" + #local regex="^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$" + echo "$str" | grep -Eq '^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9]))+$' + #[[ $str =~ $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 + if echo "$str" | base64 -d >/dev/null 2>&1; then return 0 fi return 1 @@ -58,10 +65,10 @@ get_outbound_tag_by_section() { } # Constructs and returns a ruleset tag using section, name, optional type, and a fixed postfix -get_rule_set_tag() { +get_ruleset_tag() { local section="$1" local name="$2" - local type="${3:-}" + local type="$3" local postfix="ruleset" if [ -n "$type" ]; then @@ -72,7 +79,7 @@ get_rule_set_tag() { } # Determines the ruleset format based on the file extension (json → source, srs → binary) -get_rule_set_format_by_file_extension() { +get_ruleset_format_by_file_extension() { local file_extension="$1" local format @@ -154,7 +161,7 @@ base64_decode() { local str="$1" local decoded_url - decoded_url="$(echo "$str" | base64 -d 2> /dev/null)" + decoded_url="$(echo "$str" | base64 -d 2>/dev/null)" echo "$decoded_url" } @@ -180,7 +187,7 @@ migrate_config_key() { # Download URL content directly download_to_stream() { local url="$1" - local http_proxy_url="${2}" + local http_proxy_url="$2" if [ -n "$http_proxy_url" ]; then http_proxy="$http_proxy_url" https_proxy="$http_proxy_url" wget -qO- "$url" | sed 's/\r$//' @@ -193,7 +200,7 @@ download_to_stream() { download_to_tempfile() { local url="$1" local filepath="$2" - local http_proxy_url="${3}" + local http_proxy_url="$3" if [ -n "$http_proxy_url" ]; then http_proxy="$http_proxy_url" https_proxy="$http_proxy_url" wget -O "$filepath" "$url" @@ -205,4 +212,4 @@ download_to_tempfile() { log "$filename has Windows line endings (CRLF). Converting to Unix (LF)" sed -i 's/\r$//' "$filepath" fi -} \ No newline at end of file +} diff --git a/podkop/files/usr/lib/sing_box_config_manager.sh b/podkop/files/usr/lib/sing_box_config_manager.sh index 0cb3e0d..a959034 100644 --- a/podkop/files/usr/lib/sing_box_config_manager.sh +++ b/podkop/files/usr/lib/sing_box_config_manager.sh @@ -1227,6 +1227,28 @@ sing_box_cm_configure_clash_api() { }' } +sing_box_cm_create_local_source_ruleset() { + local filepath="$1" + + jq -n '{version: 3, rules: []}' > "$filepath" +} + +sing_box_cm_patch_local_source_ruleset_rules() { + local filepath="$1" + local key="$2" + local value="$3" + + value=$(_normalize_arg "$value") + + local content + content="$(cat "$filepath")" + + echo "$content" | jq \ + --arg key "$key" \ + --argjson value "$value" \ + '.rules += [{($key): $value}]' > "$filepath" +} + ####################################### # Save a sing-box JSON configuration to a file, removing service-specific tags. # Arguments: @@ -1239,12 +1261,12 @@ sing_box_cm_configure_clash_api() { ####################################### sing_box_cm_save_config_to_file() { local config="$1" - local file_path="$2" + local filepath="$2" echo "$config" | jq \ --arg tag "$SERVICE_TAG" \ 'walk(if type == "object" then del(.[$tag]) else . end)' \ - > "$file_path" + > "$filepath" } _normalize_arg() {