diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index b23481e..8abf252 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -147,6 +147,213 @@ has_outbound_section() { return $section_exists } +get_subscription_json_path() { + local section="$1" + + echo "$TMP_SUBSCRIPTION_FOLDER/${section}.json" +} + +get_subscription_url_cache_path() { + local section="$1" + + echo "$TMP_SUBSCRIPTION_FOLDER/${section}.url" +} + +subscription_cache_is_usable() { + local subscription_json_path="$1" + + [ -s "$subscription_json_path" ] || return 1 + + validate_subscription_file "$subscription_json_path" +} + +wait_for_subscription_connectivity() { + local section="$1" + local subscription_url="$2" + local service_proxy_address="$3" + local attempts="${4:-12}" + local wait="${5:-5}" + local timeout="${6:-5}" + local attempt + + for attempt in $(seq 1 "$attempts"); do + if check_subscription_connectivity "$subscription_url" "$service_proxy_address" 1 0 "$timeout"; then + log "Subscription connectivity check passed for section '$section'" "info" + return 0 + fi + + log "Subscription source is unavailable for section '$section' [$attempt/$attempts]" "warn" + [ "$attempt" -lt "$attempts" ] && sleep "$wait" + done + + log "Subscription connectivity check failed for section '$section' after $attempts attempts" "error" + return 1 +} + +download_subscription_into_cache() { + local section="$1" + local subscription_url="$2" + local subscription_json_path="$3" + local subscription_url_cache_path="$4" + local service_proxy_address="$5" + local tmpfile + + mkdir -p "$TMP_SUBSCRIPTION_FOLDER" + tmpfile="$(mktemp "$TMP_SUBSCRIPTION_FOLDER/${section}.download.XXXXXX")" || return 1 + + if ! download_subscription "$subscription_url" "$tmpfile" "$service_proxy_address" 3 2 10; then + rm -f "$tmpfile" + return 1 + fi + + if ! validate_subscription_file "$tmpfile"; then + log "Downloaded subscription for section '$section' is invalid" "error" + rm -f "$tmpfile" + return 1 + fi + + if [ -f "$subscription_json_path" ] && cmp -s "$tmpfile" "$subscription_json_path"; then + rm -f "$tmpfile" + printf '%s' "$subscription_url" > "$subscription_url_cache_path" + log "Subscription for section '$section' is unchanged" "info" + return 2 + fi + + mv "$tmpfile" "$subscription_json_path" || { + rm -f "$tmpfile" + return 1 + } + + printf '%s' "$subscription_url" > "$subscription_url_cache_path" + return 0 +} + +prepare_subscription_cache_for_startup() { + local section="$1" + local connection_type proxy_config_type subscription_url subscription_json_path subscription_url_cache_path + local cached_subscription_url service_proxy_address had_usable_cache cache_needs_refresh + + config_get connection_type "$section" "connection_type" + [ "$connection_type" = "proxy" ] || return 0 + + config_get proxy_config_type "$section" "proxy_config_type" + [ "$proxy_config_type" = "subscription" ] || return 0 + + config_get subscription_url "$section" "subscription_url" + if [ -z "$subscription_url" ]; then + log "Subscription URL is not set for section '$section'. Aborted." "fatal" + exit 1 + fi + + subscription_json_path="$(get_subscription_json_path "$section")" + subscription_url_cache_path="$(get_subscription_url_cache_path "$section")" + cached_subscription_url="" + had_usable_cache=0 + cache_needs_refresh=0 + + if subscription_cache_is_usable "$subscription_json_path"; then + had_usable_cache=1 + else + rm -f "$subscription_json_path" + fi + + if [ -f "$subscription_url_cache_path" ]; then + cached_subscription_url="$(cat "$subscription_url_cache_path" 2> /dev/null)" + fi + + if [ "$had_usable_cache" -eq 0 ] || [ "$cached_subscription_url" != "$subscription_url" ]; then + cache_needs_refresh=1 + fi + + if [ "$cache_needs_refresh" -eq 0 ]; then + return 0 + fi + + service_proxy_address="$(get_service_proxy_address 2>/dev/null || echo '')" + + if wait_for_subscription_connectivity "$section" "$subscription_url" "$service_proxy_address"; then + if download_subscription_into_cache \ + "$section" "$subscription_url" "$subscription_json_path" "$subscription_url_cache_path" "$service_proxy_address"; then + return 0 + fi + fi + + if [ "$had_usable_cache" -eq 1 ]; then + log "Keeping cached subscription for section '$section' until a fresh download succeeds" "warn" + return 0 + fi + + log "No usable subscription cache for section '$section'; podkop startup will wait for internet connectivity" "warn" + subscription_startup_blocked=1 + return 1 +} + +prepare_subscription_caches_for_startup() { + subscription_startup_blocked=0 + config_foreach prepare_subscription_cache_for_startup "section" + + [ "$subscription_startup_blocked" -eq 0 ] +} + +stop_subscription_startup_retry_worker() { + local pidfile="/var/run/podkop_subscription_retry.pid" + + if [ -f "$pidfile" ]; then + pid="$(cat "$pidfile" 2> /dev/null)" + if [ -n "$pid" ] && kill -0 "$pid" 2> /dev/null; then + kill "$pid" 2> /dev/null + log "Stopped deferred startup recovery worker" + fi + rm -f "$pidfile" + fi +} + +start_subscription_startup_retry_worker() { + local pidfile="/var/run/podkop_subscription_retry.pid" + + if [ -f "$pidfile" ]; then + pid="$(cat "$pidfile" 2> /dev/null)" + if [ -n "$pid" ] && kill -0 "$pid" 2> /dev/null; then + log "Deferred startup recovery worker is already running" "debug" + return 0 + fi + rm -f "$pidfile" + fi + + ( + trap 'rm -f "'"$pidfile"'"' EXIT INT TERM + + while true; do + config_load "$PODKOP_CONFIG" + + if prepare_subscription_caches_for_startup; then + log "Subscription cache is ready, resuming deferred podkop startup" "info" + rm -f "$pidfile" + start_main + start_rc=$? + + if [ "$start_rc" -eq 0 ]; then + config_get_bool dont_touch_dhcp "settings" "dont_touch_dhcp" 0 + if [ "$dont_touch_dhcp" -eq 0 ]; then + dnsmasq_configure + fi + + uci_set "podkop" "settings" "shutdown_correctly" 0 + uci commit "podkop" && config_load "$PODKOP_CONFIG" + fi + + exit "$start_rc" + fi + + log "Deferred podkop startup is still waiting for subscription connectivity" "warn" + sleep 10 + done + ) & + + echo $! > "$pidfile" + log "Started deferred startup recovery worker with PID $!" "warn" +} + start_main() { log "Starting podkop" @@ -167,6 +374,14 @@ start_main() { mkdir -p "$TMP_RULESET_FOLDER" mkdir -p "$TMP_SUBSCRIPTION_FOLDER" + if ! prepare_subscription_caches_for_startup; then + log "Podkop startup is deferred until the subscription source becomes reachable" "warn" + start_subscription_startup_retry_worker + return 2 + fi + + stop_subscription_startup_retry_worker + # base route_table_rule_mark create_nft_rules @@ -186,6 +401,8 @@ start_main() { stop_main() { log "Stopping the podkop" + stop_subscription_startup_retry_worker + 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 @@ -220,6 +437,15 @@ stop_main() { start() { start_main + start_rc=$? + + if [ "$start_rc" -eq 2 ]; then + return 0 + fi + + if [ "$start_rc" -ne 0 ]; then + return "$start_rc" + fi config_get_bool dont_touch_dhcp "settings" "dont_touch_dhcp" 0 if [ "$dont_touch_dhcp" -eq 0 ]; then @@ -245,8 +471,8 @@ stop() { reload() { log "Podkop reload" - stop_main - start_main + stop + start } restart() { @@ -587,7 +813,7 @@ ensure_nft_ready_for_list_update() { list_update() { - echolog "🔄 Starting lists update..." + echolog "Ņ€ŅŸâ€â€ž Starting lists update..." local nslookup_timeout=3 local nslookup_attempts=10 @@ -600,7 +826,7 @@ list_update() { # DNS Check for i in $(seq 1 $nslookup_attempts); do if nslookup -timeout=$nslookup_timeout openwrt.org > /dev/null 2>&1; then - echolog "✅ DNS check passed" + echolog "Đ˛Ņšâ€Ļ DNS check passed" break fi echolog "DNS is unavailable [$i/$nslookup_attempts]" @@ -608,7 +834,7 @@ list_update() { done if [ "$i" -eq $nslookup_attempts ]; then - echolog "❌ DNS check failed after $nslookup_attempts attempts" + echolog "Đ˛ŅœĐŠ DNS check failed after $nslookup_attempts attempts" return 1 fi @@ -619,12 +845,12 @@ list_update() { if [ -n "$service_proxy_address" ]; then if curl -s -x "http://$service_proxy_address" -m $curl_timeout https://github.com > /dev/null; then - echolog "✅ GitHub connection check passed (via proxy)" + echolog "Đ˛Ņšâ€Ļ GitHub connection check passed (via proxy)" break fi else if curl -s -m $curl_timeout https://github.com > /dev/null; then - echolog "✅ GitHub connection check passed" + echolog "Đ˛Ņšâ€Ļ GitHub connection check passed" break fi fi @@ -637,16 +863,16 @@ list_update() { done if [ "$i" -eq $curl_attempts ]; then - echolog "❌ GitHub connection check failed after $curl_attempts attempts" + echolog "Đ˛ŅœĐŠ GitHub connection check failed after $curl_attempts attempts" return 1 fi if ! ensure_nft_ready_for_list_update; then - echolog "❌ NFT table is unavailable, cannot update lists" + echolog "Đ˛ŅœĐŠ NFT table is unavailable, cannot update lists" return 1 fi - echolog "đŸ“Ĩ Downloading and processing lists..." + echolog "Ņ€ŅŸâ€œŌ Downloading and processing lists..." local update_failed=0 config_foreach import_community_subnet_lists "section" || update_failed=1 @@ -654,17 +880,19 @@ list_update() { config_foreach import_subnets_from_remote_subnet_lists "section" || update_failed=1 if [ "$update_failed" -eq 0 ]; then - echolog "✅ Lists update completed successfully" + echolog "Đ˛Ņšâ€Ļ Lists update completed successfully" else - echolog "❌ Lists update failed" + echolog "Đ˛ŅœĐŠ Lists update failed" return 1 fi } subscription_update() { - echolog "🔄 Starting subscription update..." + echolog "Ņ€ŅŸâ€â€ž Starting subscription update..." local has_subscription=0 + local updated_sections=0 + local failed_sections=0 _check_subscription_section() { local section="$1" @@ -683,13 +911,14 @@ subscription_update() { config_foreach _check_subscription_section "section" if [ "$has_subscription" -eq 0 ]; then - echolog "â„šī¸ No subscription sections found, nothing to update" + echolog "в„№ĐŋŅ‘Đ No subscription sections found, nothing to update" return 0 fi _update_subscription_for_section() { local section="$1" local connection_type proxy_config_type subscription_url subscription_json_path + local subscription_url_cache_path service_proxy_address update_result outbounds_count config_get connection_type "$section" "connection_type" if [ "$connection_type" != "proxy" ]; then @@ -704,31 +933,44 @@ subscription_update() { config_get subscription_url "$section" "subscription_url" if [ -z "$subscription_url" ]; then - echolog "❌ Subscription URL not set for section '$section'" + echolog "Đ˛ŅœĐŠ Subscription URL not set for section '$section'" + failed_sections=$((failed_sections + 1)) return fi mkdir -p "$TMP_SUBSCRIPTION_FOLDER" - subscription_json_path="$TMP_SUBSCRIPTION_FOLDER/${section}.json" - local subscription_url_cache_path - subscription_url_cache_path="$TMP_SUBSCRIPTION_FOLDER/${section}.url" + subscription_json_path="$(get_subscription_json_path "$section")" + subscription_url_cache_path="$(get_subscription_url_cache_path "$section")" - echolog "đŸ“Ĩ Updating subscription for section '$section'..." + echolog "Ņ€ŅŸâ€œŌ Updating subscription for section '$section'..." - local service_proxy_address service_proxy_address="$(get_service_proxy_address 2>/dev/null || echo '')" - # Remove old cached file to force re-download - rm -f "$subscription_json_path" - download_subscription "$subscription_url" "$subscription_json_path" "$service_proxy_address" - - if [ ! -f "$subscription_json_path" ] || [ ! -s "$subscription_json_path" ]; then - echolog "❌ Failed to download subscription for section '$section'" + if ! wait_for_subscription_connectivity "$section" "$subscription_url" "$service_proxy_address" 6 5 5; then + echolog "Đ˛ŅœĐŠ Failed to download subscription for section '$section'" + failed_sections=$((failed_sections + 1)) return fi - printf '%s' "$subscription_url" > "$subscription_url_cache_path" - local outbounds_count + download_subscription_into_cache \ + "$section" "$subscription_url" "$subscription_json_path" "$subscription_url_cache_path" "$service_proxy_address" + update_result=$? + + case "$update_result" in + 0) + updated_sections=$((updated_sections + 1)) + ;; + 2) + echolog "в„№ĐŋŅ‘Đ Subscription for section '$section' is unchanged" + return + ;; + *) + echolog "Đ˛ŅœĐŠ Failed to download subscription for section '$section'" + failed_sections=$((failed_sections + 1)) + return + ;; + esac + outbounds_count=$(jq -r '[.outbounds[] | select( .type != "selector" and .type != "urltest" and @@ -737,13 +979,33 @@ subscription_update() { .type != "block" )] | length' "$subscription_json_path" 2>/dev/null) - echolog "✅ Subscription updated for section '$section': $outbounds_count outbounds" + echolog "Đ˛Ņšâ€Ļ Subscription updated for section '$section': $outbounds_count outbounds" } config_foreach _update_subscription_for_section "section" - echolog "🔄 Restarting podkop to apply updated subscriptions..." + if [ "$updated_sections" -eq 0 ]; then + if [ "$failed_sections" -gt 0 ]; then + echolog "Đ˛ŅœĐŠ Subscription update finished with errors; keeping the last working cache" + return 1 + fi + + echolog "в„№ĐŋŅ‘Đ Subscription update completed: no changes detected" + return 0 + fi + + echolog "Ņ€ŅŸâ€â€ž Restarting podkop to apply updated subscriptions..." restart - echolog "✅ Subscription update completed" + restart_rc=$? + if [ "$restart_rc" -ne 0 ]; then + echolog "Đ˛ŅœĐŠ Subscription was downloaded, but podkop restart failed" + return "$restart_rc" + fi + + if [ "$failed_sections" -gt 0 ]; then + echolog "Đ˛Ņšâ€Ļ Subscription update applied for changed sections; failed sections kept their previous cache" + else + echolog "Đ˛Ņšâ€Ļ Subscription update completed" + fi } # sing-box funcs @@ -1002,12 +1264,16 @@ configure_outbound_handler() { fi mkdir -p "$TMP_SUBSCRIPTION_FOLDER" - subscription_json_path="$TMP_SUBSCRIPTION_FOLDER/${section}.json" - local subscription_url_cache_path cached_subscription_url should_download - subscription_url_cache_path="$TMP_SUBSCRIPTION_FOLDER/${section}.url" + subscription_json_path="$(get_subscription_json_path "$section")" + local subscription_url_cache_path cached_subscription_url should_download had_usable_cache + subscription_url_cache_path="$(get_subscription_url_cache_path "$section")" should_download=0 + had_usable_cache=0 - if [ ! -f "$subscription_json_path" ] || [ ! -s "$subscription_json_path" ]; then + if subscription_cache_is_usable "$subscription_json_path"; then + had_usable_cache=1 + else + rm -f "$subscription_json_path" should_download=1 fi @@ -1019,24 +1285,30 @@ configure_outbound_handler() { if [ "$cached_subscription_url" != "$subscription_url" ]; then if [ -n "$cached_subscription_url" ]; then - log "Subscription URL changed for section '$section', refreshing cache" "debug" + log "Subscription URL changed for section '$section'" "warn" + fi + if [ "$had_usable_cache" -eq 0 ]; then + should_download=1 + else + log "Using cached subscription for section '$section' until a fresh download succeeds" "warn" fi - should_download=1 - rm -f "$subscription_json_path" fi if [ "$should_download" -eq 1 ]; then log "Downloading subscription for section '$section'" local service_proxy_address service_proxy_address="$(get_service_proxy_address 2>/dev/null || echo '')" - download_subscription "$subscription_url" "$subscription_json_path" "$service_proxy_address" - if [ ! -f "$subscription_json_path" ] || [ ! -s "$subscription_json_path" ]; then - log "Failed to download subscription for section '$section'. Aborted." "fatal" - exit 1 + if ! wait_for_subscription_connectivity "$section" "$subscription_url" "$service_proxy_address" 6 5 5 || + ! download_subscription_into_cache \ + "$section" "$subscription_url" "$subscription_json_path" "$subscription_url_cache_path" "$service_proxy_address"; then + if [ "$had_usable_cache" -eq 1 ]; then + log "Failed to refresh subscription for section '$section', continuing with cached data" "warn" + else + log "Failed to download subscription for section '$section'. Aborted." "fatal" + exit 1 + fi fi - - printf '%s' "$subscription_url" > "$subscription_url_cache_path" fi # Parse subscription outbounds @@ -1169,10 +1441,10 @@ configure_outbound_handler() { 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 "Connection type 'block' detected for the $section section – no outbound will be created (handled via reject route rules)" ;; exclusion) - log "Connection type 'exclusion' detected for the $section section – no outbound will be created (handled via route rules)" + log "Connection type 'exclusion' detected for the $section section – no outbound will be created (handled via route rules)" ;; *) log "Unknown connection type '$connection_type' for the $section section. Aborted." "fatal" @@ -2133,7 +2405,7 @@ check_nft() { # Check if table exists if ! nft list table inet "$NFT_TABLE_NAME" > /dev/null 2>&1; then - nolog "❌ $NFT_TABLE_NAME not found" + nolog "Đ˛ŅœĐŠ $NFT_TABLE_NAME not found" return 1 fi @@ -2782,9 +3054,9 @@ 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 "Ņ€ŅŸâ€œĐŽ Global check run!" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Ņ€ŅŸâ€ē ĐŋŅ‘Đ System info" local system_info_json system_info_json=$(get_system_info) @@ -2799,17 +3071,17 @@ global_check() { openwrt_version=$(echo "$system_info_json" | jq -r '.openwrt_version // "unknown"') device_model=$(echo "$system_info_json" | jq -r '.device_model // "unknown"') - print_global "đŸ•ŗī¸ Podkop: $podkop_version (latest: $podkop_latest_version)" - print_global "đŸ•ŗī¸ LuCI App: $luci_app_version" - print_global "đŸ“Ļ Sing-box: $sing_box_version" - print_global "🛜 OpenWrt: $openwrt_version" - print_global "🛜 Device: $device_model" + print_global "Ņ€ŅŸâ€ĸŅ–ĐŋŅ‘Đ Podkop: $podkop_version (latest: $podkop_latest_version)" + print_global "Ņ€ŅŸâ€ĸŅ–ĐŋŅ‘Đ LuCI App: $luci_app_version" + print_global "Ņ€ŅŸâ€œÂĻ Sing-box: $sing_box_version" + print_global "Ņ€ŅŸâ€ēҚ OpenWrt: $openwrt_version" + print_global "Ņ€ŅŸâ€ēҚ Device: $device_model" else - print_global "❌ Failed to get system info" + print_global "Đ˛ŅœĐŠ Failed to get system info" fi - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "âžĄī¸ DNS status" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Đ˛Ņ›ĐŽĐŋŅ‘Đ DNS status" local dns_check_json dns_check_json=$(check_dns_available) @@ -2828,24 +3100,24 @@ global_check() { # Bootstrap DNS if [ -n "$bootstrap_dns_server" ]; then if [ "$bootstrap_dns_status" -eq 1 ]; then - print_global "✅ Bootstrap DNS: $bootstrap_dns_server" + print_global "Đ˛Ņšâ€Ļ Bootstrap DNS: $bootstrap_dns_server" else - print_global "❌ Bootstrap DNS: $bootstrap_dns_server" + print_global "Đ˛ŅœĐŠ Bootstrap DNS: $bootstrap_dns_server" fi fi # DNS server status if [ "$dns_status" -eq 1 ]; then - print_global "✅ Main DNS: $dns_server [$dns_type]" + print_global "Đ˛Ņšâ€Ļ Main DNS: $dns_server [$dns_type]" else - print_global "❌ Main DNS: $dns_server [$dns_type]" + print_global "Đ˛ŅœĐŠ Main DNS: $dns_server [$dns_type]" fi # DNS on router if [ "$dns_on_router" -eq 1 ]; then - print_global "✅ DNS on router" + print_global "Đ˛Ņšâ€Ļ DNS on router" else - print_global "❌ DNS on router" + print_global "Đ˛ŅœĐŠ DNS on router" fi # DHCP configuration check @@ -2853,20 +3125,20 @@ global_check() { config_get dont_touch_dhcp "settings" "dont_touch_dhcp" if [ "$dont_touch_dhcp" = "1" ]; then - print_global "âš ī¸ dont_touch_dhcp is enabled. 📄 DHCP config:" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ dont_touch_dhcp is enabled. Ņ€ŅŸâ€œâ€ž DHCP config:" awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp elif [ "$dhcp_config_status" -eq 0 ]; then - print_global "❌ DHCP configuration differs from template. 📄 DHCP config:" + print_global "Đ˛ŅœĐŠ DHCP configuration differs from template. Ņ€ŅŸâ€œâ€ž DHCP config:" awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp else - print_global "✅ /etc/config/dhcp" + print_global "Đ˛Ņšâ€Ļ /etc/config/dhcp" fi else - print_global "❌ Failed to get DNS info" + print_global "Đ˛ŅœĐŠ Failed to get DNS info" fi - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "đŸ“Ļ Sing-box status" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Ņ€ŅŸâ€œÂĻ Sing-box status" local singbox_check_json singbox_check_json=$(check_sing_box) @@ -2882,46 +3154,46 @@ global_check() { sing_box_ports_listening=$(echo "$singbox_check_json" | jq -r '.sing_box_ports_listening // 0') if [ "$sing_box_installed" -eq 1 ]; then - print_global "✅ Sing-box installed" + print_global "Đ˛Ņšâ€Ļ Sing-box installed" else - print_global "❌ Sing-box installed" + print_global "Đ˛ŅœĐŠ Sing-box installed" fi if [ "$sing_box_version_ok" -eq 1 ]; then - print_global "✅ Sing-box version is compatible (newer than 1.12.4)" + print_global "Đ˛Ņšâ€Ļ Sing-box version is compatible (newer than 1.12.4)" else - print_global "❌ Sing-box version is not compatible (older than 1.12.4)" + print_global "Đ˛ŅœĐŠ Sing-box version is not compatible (older than 1.12.4)" fi if [ "$sing_box_service_exist" -eq 1 ]; then - print_global "✅ Sing-box service exist" + print_global "Đ˛Ņšâ€Ļ Sing-box service exist" else - print_global "❌ Sing-box service exist" + print_global "Đ˛ŅœĐŠ Sing-box service exist" fi if [ "$sing_box_autostart_disabled" -eq 1 ]; then - print_global "✅ Sing-box autostart disabled" + print_global "Đ˛Ņšâ€Ļ Sing-box autostart disabled" else - print_global "❌ Sing-box autostart disabled" + print_global "Đ˛ŅœĐŠ Sing-box autostart disabled" fi if [ "$sing_box_process_running" -eq 1 ]; then - print_global "✅ Sing-box process running" + print_global "Đ˛Ņšâ€Ļ Sing-box process running" else - print_global "❌ Sing-box process running" + print_global "Đ˛ŅœĐŠ Sing-box process running" fi if [ "$sing_box_ports_listening" -eq 1 ]; then - print_global "✅ Sing-box listening ports" + print_global "Đ˛Ņšâ€Ļ Sing-box listening ports" else - print_global "❌ Sing-box listening ports" + print_global "Đ˛ŅœĐŠ Sing-box listening ports" fi else - print_global "❌ Failed to get sing-box info" + print_global "Đ˛ŅœĐŠ Failed to get sing-box info" fi - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "🧱 NFT rules status" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Ņ€ŅŸÂ§Âą NFT rules status" local nft_check_json nft_check_json=$(check_nft_rules) @@ -2939,78 +3211,78 @@ global_check() { rules_other_mark_exist=$(echo "$nft_check_json" | jq -r '.rules_other_mark_exist // 0') if [ "$table_exist" -eq 1 ]; then - print_global "✅ Table exist" + print_global "Đ˛Ņšâ€Ļ Table exist" else - print_global "❌ Table exist" + print_global "Đ˛ŅœĐŠ Table exist" fi if [ "$rules_mangle_exist" -eq 1 ]; then - print_global "✅ Rules mangle exist" + print_global "Đ˛Ņšâ€Ļ Rules mangle exist" else - print_global "❌ Rules mangle exist" + print_global "Đ˛ŅœĐŠ Rules mangle exist" fi if [ "$rules_mangle_counters" -eq 1 ]; then - print_global "✅ Rules mangle counters" + print_global "Đ˛Ņšâ€Ļ Rules mangle counters" else - print_global "âš ī¸ Rules mangle counters" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ Rules mangle counters" fi if [ "$rules_mangle_output_exist" -eq 1 ]; then - print_global "✅ Rules mangle output exist" + print_global "Đ˛Ņšâ€Ļ Rules mangle output exist" else - print_global "❌ Rules mangle output exist" + print_global "Đ˛ŅœĐŠ Rules mangle output exist" fi if [ "$rules_mangle_output_counters" -eq 1 ]; then - print_global "✅ Rules mangle output counters" + print_global "Đ˛Ņšâ€Ļ Rules mangle output counters" else - print_global "âš ī¸ Rules mangle output counters" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ Rules mangle output counters" fi if [ "$rules_proxy_exist" -eq 1 ]; then - print_global "✅ Rules proxy exist" + print_global "Đ˛Ņšâ€Ļ Rules proxy exist" else - print_global "❌ Rules proxy exist" + print_global "Đ˛ŅœĐŠ Rules proxy exist" fi if [ "$rules_proxy_counters" -eq 1 ]; then - print_global "✅ Rules proxy counters" + print_global "Đ˛Ņšâ€Ļ Rules proxy counters" else - print_global "âš ī¸ Rules proxy counters" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ Rules proxy counters" fi if [ "$rules_other_mark_exist" -eq 1 ]; then - print_global "âš ī¸ Additional marking rules found:" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ Additional marking rules found:" nft list ruleset | awk '/table inet '"$NFT_TABLE_NAME"'/{flag=1; next} /^table/{flag=0} !flag' | grep -E "mark set|meta mark" else - print_global "✅ Additional marking rules found" + print_global "Đ˛Ņšâ€Ļ Additional marking rules found" fi else - print_global "❌ Failed to get NFT rules info" + print_global "Đ˛ŅœĐŠ Failed to get NFT rules info" fi - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "📄 Podkop config" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Ņ€ŅŸâ€œâ€ž Podkop config" show_config - # print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - # print_global "🔧 System check" + # 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:" + # print_global "Đ˛ŅœĐŠ /etc/resolv.conf contains external nameserver:" # cat /etc/resolv.conf # echo "" # else - # print_global "✅ /etc/resolv.conf" + # print_global "Đ˛Ņšâ€Ļ /etc/resolv.conf" # fi - # print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - # print_global "🧱 NFT table" + # print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + # print_global "Ņ€ŅŸÂ§Âą NFT table" # check_nft - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "📄 WAN config" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Ņ€ŅŸâ€œâ€ž WAN config" if uci show network.wan > /dev/null 2>&1; then awk ' /^config / { @@ -3031,20 +3303,20 @@ global_check() { } ' /etc/config/network else - print_global "❌ WAN configuration not found" + 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" + 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" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ WARP detected: $host" fi done fi @@ -3055,19 +3327,19 @@ global_check() { allowed_ips=$(uci get "${peer_section}.allowed_ips" 2> /dev/null) if [ "$allowed_ips" = "0.0.0.0/0" ]; then - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "âš ī¸ WG Route allowed IP enabled with 0.0.0.0/0" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ WG Route allowed IP enabled with 0.0.0.0/0" fi done fi if [ -f "/etc/init.d/zapret" ]; then - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "âš ī¸ Zapret detected" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ Zapret detected" fi - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "đŸĨ¸ FakeIP status" + print_global "в”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓв”Ѓ" + print_global "Ņ€ŅŸŌŅ‘ FakeIP status" local fakeip_check_json fakeip_check_json=$(check_fakeip) @@ -3078,21 +3350,21 @@ global_check() { fakeip_status=$(echo "$fakeip_check_json" | jq -r '.fakeip // false') if [ "$fakeip_status" = "true" ]; then - print_global "✅ Router DNS is routed through sing-box" + print_global "Đ˛Ņšâ€Ļ Router DNS is routed through sing-box" else - print_global "âš ī¸ Router DNS is NOT routed through sing-box" + print_global "Đ˛Ņ™Â ĐŋŅ‘Đ Router DNS is NOT routed through sing-box" fi else - print_global "❌ Failed to get FakeIP info" + print_global "Đ˛ŅœĐŠ Failed to get FakeIP info" fi local fakeip_address fakeip_address=$(dig +short @127.0.0.42 $FAKEIP_TEST_DOMAIN) if echo "$fakeip_address" | grep -q "^198\.18\."; then - print_global "✅ Sing-box works with FakeIP: $fakeip_address" + print_global "Đ˛Ņšâ€Ļ Sing-box works with FakeIP: $fakeip_address" else - print_global "❌ Sing-box does NOT work with FakeIP: $fakeip_address" + print_global "Đ˛ŅœĐŠ Sing-box does NOT work with FakeIP: $fakeip_address" fi } diff --git a/podkop/files/usr/lib/helpers.sh b/podkop/files/usr/lib/helpers.sh index 24f656c..dda8ea8 100644 --- a/podkop/files/usr/lib/helpers.sh +++ b/podkop/files/usr/lib/helpers.sh @@ -417,6 +417,7 @@ download_subscription() { local http_proxy_address="$3" local retries="${4:-3}" local wait="${5:-2}" + local timeout="${6:-10}" local sb_version device_model kernel_version hwid sb_version="$(get_sing_box_version)" @@ -424,24 +425,100 @@ download_subscription() { kernel_version="$(get_kernel_version)" hwid="$(generate_hwid)" - local header_args="" - header_args="--header='User-Agent: singbox/$sb_version'" - header_args="$header_args --header='X-HWID: $hwid'" - header_args="$header_args --header='X-Device-OS: OpenWrt Linux'" - header_args="$header_args --header='X-Device-Model: $device_model'" - header_args="$header_args --header='X-Ver-OS: $kernel_version'" - header_args="$header_args --header='Accept-Language: ru-RU,en,*'" - header_args="$header_args --header='X-Device-Locale: EN'" + local tmpfile + tmpfile="${filepath}.part.$$" + rm -f "$tmpfile" for attempt in $(seq 1 "$retries"); do if [ -n "$http_proxy_address" ]; then http_proxy="http://$http_proxy_address" https_proxy="http://$http_proxy_address" \ - eval wget -O "$filepath" $header_args "$url" && break + wget -T "$timeout" -t 1 -O "$tmpfile" \ + --header "User-Agent: singbox/$sb_version" \ + --header "X-HWID: $hwid" \ + --header "X-Device-OS: OpenWrt Linux" \ + --header "X-Device-Model: $device_model" \ + --header "X-Ver-OS: $kernel_version" \ + --header "Accept-Language: ru-RU,en,*" \ + --header "X-Device-Locale: EN" \ + "$url" else - eval wget -O "$filepath" $header_args "$url" && break + wget -T "$timeout" -t 1 -O "$tmpfile" \ + --header "User-Agent: singbox/$sb_version" \ + --header "X-HWID: $hwid" \ + --header "X-Device-OS: OpenWrt Linux" \ + --header "X-Device-Model: $device_model" \ + --header "X-Ver-OS: $kernel_version" \ + --header "Accept-Language: ru-RU,en,*" \ + --header "X-Device-Locale: EN" \ + "$url" fi + if [ $? -eq 0 ] && [ -s "$tmpfile" ]; then + mv "$tmpfile" "$filepath" + return 0 + fi + + rm -f "$tmpfile" log "Attempt $attempt/$retries to download subscription from $url failed" "warn" sleep "$wait" done -} \ No newline at end of file + + rm -f "$tmpfile" + return 1 +} + +check_subscription_connectivity() { + local url="$1" + local http_proxy_address="$2" + local retries="${3:-3}" + local wait="${4:-2}" + local timeout="${5:-5}" + + local sb_version device_model kernel_version hwid + sb_version="$(get_sing_box_version)" + device_model="$(get_device_model)" + kernel_version="$(get_kernel_version)" + hwid="$(generate_hwid)" + + local attempt + for attempt in $(seq 1 "$retries"); do + if [ -n "$http_proxy_address" ]; then + http_proxy="http://$http_proxy_address" https_proxy="http://$http_proxy_address" \ + wget -q -T "$timeout" -t 1 -O /dev/null \ + --header "User-Agent: singbox/$sb_version" \ + --header "X-HWID: $hwid" \ + --header "X-Device-OS: OpenWrt Linux" \ + --header "X-Device-Model: $device_model" \ + --header "X-Ver-OS: $kernel_version" \ + --header "Accept-Language: ru-RU,en,*" \ + --header "X-Device-Locale: EN" \ + "$url" && return 0 + else + wget -q -T "$timeout" -t 1 -O /dev/null \ + --header "User-Agent: singbox/$sb_version" \ + --header "X-HWID: $hwid" \ + --header "X-Device-OS: OpenWrt Linux" \ + --header "X-Device-Model: $device_model" \ + --header "X-Ver-OS: $kernel_version" \ + --header "Accept-Language: ru-RU,en,*" \ + --header "X-Device-Locale: EN" \ + "$url" && return 0 + fi + + [ "$attempt" -lt "$retries" ] && sleep "$wait" + done + + return 1 +} + +validate_subscription_file() { + local filepath="$1" + + [ -s "$filepath" ] || return 1 + + jq -e ' + type == "object" and + (.outbounds | type == "array") and + ((.outbounds | length) > 0) + ' "$filepath" > /dev/null 2>&1 +}