Optimize Podkop runtime process batching

This commit is contained in:
ushan0v
2026-06-24 04:23:47 +03:00
parent fbe812c7ce
commit 86f1be3273
14 changed files with 939 additions and 173 deletions
+13 -2
View File
@@ -16,6 +16,7 @@ PODKOP_CONFIG_FILE="/etc/config/$CONFIG_NAME"
PODKOP_RELOAD_LOCK_DIR="/var/run/podkop-plus.reload.lock"
PODKOP_RUNTIME_STATE_DIR="/var/run/podkop-plus"
PODKOP_PENDING_RELOAD_FILE="$PODKOP_RUNTIME_STATE_DIR/reload.pending"
PODKOP_SERVICE_TRIGGER_SYNC_FILE="$PODKOP_RUNTIME_STATE_DIR/service-triggers.sync"
PODKOP_INTERNAL_CONFIG_TRIGGER_GUARD="/var/run/podkop-plus.internal-config-change"
PODKOP_CONFIG_CHANGE_REASON="on_config_change"
PODKOP_DNSMASQ_FAILSAFE="/usr/lib/podkop-plus/dnsmasq_failsafe_restore.sh"
@@ -136,6 +137,15 @@ sync_service_triggers() {
_procd_call sync_service_triggers_payload
}
service_trigger_sync_requested() {
local value
[ -f "$PODKOP_SERVICE_TRIGGER_SYNC_FILE" ] || return 0
value="$(sed -n '1p' "$PODKOP_SERVICE_TRIGGER_SYNC_FILE" 2>/dev/null)"
rm -f "$PODKOP_SERVICE_TRIGGER_SYNC_FILE" 2>/dev/null || true
[ "$value" = "1" ]
}
sync_service_triggers_payload() {
# rc.common registers triggers when the service is started. Config reloads can
# change Bad WAN interface triggers, so refresh the trigger set after a
@@ -347,10 +357,11 @@ reload_service() {
return 0
fi
rm -f "$PODKOP_SERVICE_TRIGGER_SYNC_FILE" 2>/dev/null || true
trap 'release_reload_lock' EXIT INT TERM
"$PODKOP_BIN" reload "$reason" > /dev/null 2>&1
PODKOP_UI_ACTION_TRACKED=1 "$PODKOP_BIN" reload "$reason" > /dev/null 2>&1
local status="$?"
if [ "$status" -eq 0 ] && is_config_change_reload "$reason"; then
if [ "$status" -eq 0 ] && is_config_change_reload "$reason" && service_trigger_sync_requested; then
sync_service_triggers
fi
trap - EXIT INT TERM
+25 -22
View File
@@ -338,6 +338,7 @@ PODKOP_RUNTIME_STATE_DIR="/var/run/podkop-plus"
PODKOP_SYSTEM_INFO_CACHE_FILE="$PODKOP_RUNTIME_STATE_DIR/system-info.json"
PODKOP_RELOAD_STATE_FILE="$PODKOP_RUNTIME_STATE_DIR/reload-state"
PODKOP_PENDING_RELOAD_FILE="$PODKOP_RUNTIME_STATE_DIR/reload.pending"
PODKOP_SERVICE_TRIGGER_SYNC_FILE="$PODKOP_RUNTIME_STATE_DIR/service-triggers.sync"
PODKOP_LIST_UPDATE_STATE_FILE="$PODKOP_RUNTIME_STATE_DIR/list-update.timestamp"
PODKOP_SUBSCRIPTION_UPDATE_STATE_DIR="$PODKOP_RUNTIME_STATE_DIR/subscription-update"
PODKOP_SUBSCRIPTION_UPDATE_JOB_DIR="$PODKOP_RUNTIME_STATE_DIR/subscription-update-jobs"
@@ -424,8 +425,6 @@ start_main() {
# Sync time only when the system clock is clearly invalid.
sync_time_if_needed
sleep 1
ensure_runtime_dirs
acquire_start_subscription_update_lock
if ! prepare_subscription_caches_for_startup; then
@@ -639,6 +638,13 @@ restart_runtime_for_reload() {
write_reload_state
}
write_service_trigger_sync_state() {
local changed="$1"
mkdir -p "$PODKOP_RUNTIME_STATE_DIR"
printf '%s\n' "$changed" > "$PODKOP_SERVICE_TRIGGER_SYNC_FILE" 2>/dev/null || true
}
reload() {
local reload_reason="${1:-}"
local status
@@ -652,6 +658,7 @@ reload() {
log "Podkop Plus reload"
[ "$reload_reason" = "on_config_change" ] || force_runtime_reload=1
PODKOP_RULE_CONDITION_CACHE_ENABLED="$force_runtime_reload"
migration
config_load "$PODKOP_CONFIG_NAME"
@@ -668,33 +675,28 @@ reload() {
return "$status"
fi
if ! reload_state_is_compatible; then
if ! load_previous_reload_state; then
log "Reload state is missing or incompatible, falling back to in-process restart"
restart_runtime_for_reload
status="$?"
return "$status"
fi
previous_service_trigger_signature="$(read_reload_state_value "service_trigger_signature")"
previous_service_trigger_signature="$PREVIOUS_RELOAD_STATE_SERVICE_TRIGGER_SIGNATURE"
[ -n "$previous_service_trigger_signature" ] ||
previous_service_trigger_signature="$(read_reload_state_value "restart_signature")"
previous_dnsmasq_signature="$(read_reload_state_value "dnsmasq_signature")"
previous_sing_box_signature="$(read_reload_state_value "sing_box_signature")"
previous_nft_signature="$(read_reload_state_value "nft_signature")"
previous_zapret_queue_signature="$(read_reload_state_value "zapret_queue_signature")"
previous_zapret_runtime_signature="$(read_reload_state_value "zapret_runtime_signature")"
previous_zapret2_queue_signature="$(read_reload_state_value "zapret2_queue_signature")"
previous_zapret2_runtime_signature="$(read_reload_state_value "zapret2_runtime_signature")"
previous_byedpi_runtime_signature="$(read_reload_state_value "byedpi_runtime_signature")"
previous_list_signature="$(read_reload_state_value "list_signature")"
previous_cron_signature="$(read_reload_state_value "cron_signature")"
previous_urltest_enabled_sections_known=0
if reload_state_has_key "urltest_enabled_sections"; then
previous_urltest_enabled_sections_known=1
previous_urltest_enabled_sections="$(read_reload_state_value "urltest_enabled_sections")"
else
previous_urltest_enabled_sections=""
fi
previous_service_trigger_signature="$PREVIOUS_RELOAD_STATE_RESTART_SIGNATURE"
previous_dnsmasq_signature="$PREVIOUS_RELOAD_STATE_DNSMASQ_SIGNATURE"
previous_sing_box_signature="$PREVIOUS_RELOAD_STATE_SING_BOX_SIGNATURE"
previous_nft_signature="$PREVIOUS_RELOAD_STATE_NFT_SIGNATURE"
previous_zapret_queue_signature="$PREVIOUS_RELOAD_STATE_ZAPRET_QUEUE_SIGNATURE"
previous_zapret_runtime_signature="$PREVIOUS_RELOAD_STATE_ZAPRET_RUNTIME_SIGNATURE"
previous_zapret2_queue_signature="$PREVIOUS_RELOAD_STATE_ZAPRET2_QUEUE_SIGNATURE"
previous_zapret2_runtime_signature="$PREVIOUS_RELOAD_STATE_ZAPRET2_RUNTIME_SIGNATURE"
previous_byedpi_runtime_signature="$PREVIOUS_RELOAD_STATE_BYEDPI_RUNTIME_SIGNATURE"
previous_list_signature="$PREVIOUS_RELOAD_STATE_LIST_SIGNATURE"
previous_cron_signature="$PREVIOUS_RELOAD_STATE_CRON_SIGNATURE"
previous_urltest_enabled_sections_known="$PREVIOUS_RELOAD_STATE_URLTEST_ENABLED_SECTIONS_KNOWN"
previous_urltest_enabled_sections="$PREVIOUS_RELOAD_STATE_URLTEST_ENABLED_SECTIONS"
capture_reload_state
if [ "$previous_urltest_enabled_sections_known" -eq 1 ]; then
PODKOP_URLTEST_NEW_ENABLED_SECTIONS="$(
@@ -726,6 +728,7 @@ reload() {
changed_list=0
[ "$RELOAD_STATE_SERVICE_TRIGGER_SIGNATURE" != "$previous_service_trigger_signature" ] && changed_service_triggers=1
write_service_trigger_sync_state "$changed_service_triggers"
if [ "$RELOAD_STATE_SING_BOX_SIGNATURE" != "$previous_sing_box_signature" ]; then
changed_sing_box=1
+2 -2
View File
@@ -947,7 +947,7 @@ migration() {
validate_service() {
local service="$1"
helpers_ucode whitespace-list-contains "$COMMUNITY_SERVICES" "$service" >/dev/null 2>&1 && return 0
list_has_item "$COMMUNITY_SERVICES" "$service" && return 0
log "Invalid service in community lists: $service. Check config and LuCI cache. Aborted." "fatal"
exit 1
@@ -960,7 +960,7 @@ validate_ruleset_reference() {
return 0
fi
if helpers_ucode whitespace-list-contains "$COMMUNITY_SERVICES" "$reference" >/dev/null 2>&1; then
if list_has_item "$COMMUNITY_SERVICES" "$reference"; then
return 0
fi
+33 -5
View File
@@ -119,7 +119,15 @@ service_exists() {
list_has_item() {
local list="$1"
local needle="$2"
helpers_ucode whitespace-list-contains "$list" "$needle" >/dev/null 2>&1
local item
[ -n "$needle" ] || return 1
for item in $list; do
[ "$item" = "$needle" ] && return 0
done
return 1
}
allocate_runtime_tag() {
@@ -298,25 +306,45 @@ get_kernel_version() {
get_sing_box_version() {
local version=""
if [ "${PODKOP_SING_BOX_VERSION_CACHED:-0}" = "1" ]; then
printf '%s\n' "$PODKOP_SING_BOX_VERSION"
return 0
fi
if command -v sing-box >/dev/null 2>&1; then
if is_sing_box_compressed_marker_set; then
read_sing_box_version_state 2>/dev/null || true
version="$(read_sing_box_version_state 2>/dev/null || true)"
PODKOP_SING_BOX_VERSION="$version"
PODKOP_SING_BOX_VERSION_CACHED=1
printf '%s\n' "$version"
return 0
fi
version="$(sing_box_version_output | sing_box_version_from_output)"
fi
echo "$version"
PODKOP_SING_BOX_VERSION="$version"
PODKOP_SING_BOX_VERSION_CACHED=1
printf '%s\n' "$version"
}
sing_box_version_from_output() {
helpers_ucode stdin-first-line-last-field
local line field last=""
IFS= read -r line || return 0
for field in $line; do
last="$field"
done
printf '%s\n' "$last"
}
sing_box_version_output() {
command -v sing-box >/dev/null 2>&1 || return 1
sing-box version 2>/dev/null
if [ "${PODKOP_SING_BOX_VERSION_OUTPUT_CACHED:-0}" != "1" ]; then
PODKOP_SING_BOX_VERSION_OUTPUT="$(sing-box version 2>/dev/null || true)"
PODKOP_SING_BOX_VERSION_OUTPUT_CACHED=1
fi
printf '%s\n' "$PODKOP_SING_BOX_VERSION_OUTPUT"
}
sing_box_output_has_build_tag() {
+171 -9
View File
@@ -4,6 +4,85 @@ rules_nft_runtime_ucode() {
ucode "${PODKOP_LIB:-/usr/lib/podkop-plus}/rules_nft_runtime.uc" "$@"
}
rules_nft_cache_enabled() {
case "${PODKOP_RULE_CONDITION_CACHE_ENABLED:-0}" in
1 | true | yes)
return 0
;;
esac
return 1
}
rules_nft_cache_init() {
if [ -z "${PODKOP_RULE_CONDITION_CACHE_DIR:-}" ]; then
if [ -z "${PODKOP_RULE_CONDITION_CACHE_CLEANED:-}" ]; then
rm -rf /tmp/podkop-plus-rule-cache.* 2>/dev/null || true
PODKOP_RULE_CONDITION_CACHE_CLEANED=1
fi
PODKOP_RULE_CONDITION_CACHE_DIR="/tmp/podkop-plus-rule-cache.$$"
mkdir -p "$PODKOP_RULE_CONDITION_CACHE_DIR" 2>/dev/null || return 1
fi
}
rules_nft_cache_dir() {
rules_nft_cache_init || return 1
printf '%s\n' "$PODKOP_RULE_CONDITION_CACHE_DIR"
}
rule_condition_cache_clear() {
[ -z "${PODKOP_RULE_CONDITION_CACHE_DIR:-}" ] || rm -rf "$PODKOP_RULE_CONDITION_CACHE_DIR" 2>/dev/null || true
rm -rf /tmp/podkop-plus-rule-cache.* 2>/dev/null || true
PODKOP_RULE_CONDITION_CACHE_DIR=""
PODKOP_RULE_CONDITION_CACHE_CLEANED=""
}
rules_nft_cache_key_is_safe() {
case "$1" in
"" | *[!A-Za-z0-9_]*)
return 1
;;
esac
return 0
}
rules_nft_cache_path() {
local namespace="$1"
local section="$2"
local key="$3"
local kind="${4:-value}"
local cache_dir
rules_nft_cache_enabled || return 1
rules_nft_cache_key_is_safe "$namespace" || return 1
rules_nft_cache_key_is_safe "$section" || return 1
rules_nft_cache_key_is_safe "$key" || return 1
rules_nft_cache_key_is_safe "$kind" || return 1
rules_nft_cache_init || return 1
cache_dir="$PODKOP_RULE_CONDITION_CACHE_DIR"
printf '%s/%s_%s_%s_%s\n' "$cache_dir" "$namespace" "$section" "$key" "$kind"
}
rules_nft_cache_get() {
local path="$1"
local value
[ -f "$path" ] || return 1
value=""
IFS= read -r value < "$path" || true
printf '%s\n' "$value"
}
rules_nft_cache_put() {
local path="$1"
local value="$2"
printf '%s\n' "$value" > "$path" 2>/dev/null || return 1
}
normalize_port_condition_for_nft() {
rules_nft_runtime_ucode normalize-port-condition-for-nft "$1"
}
@@ -24,6 +103,7 @@ br_netfilter_disable() {
parse_generic_string_to_commas_string() {
local string="$1"
[ -n "$string" ] || return 0
rules_nft_runtime_ucode text-list-to-csv "$string" comma
}
@@ -31,6 +111,7 @@ parse_domain_or_subnet_string_to_commas_string() {
local string="$1"
local type="$2"
[ -n "$string" ] || return 0
rules_nft_runtime_ucode domain-subnet-text-csv "$string" "$type"
}
@@ -45,6 +126,7 @@ parse_combined_domain_string_to_commas_string() {
local string="$1"
local type="$2"
[ -n "$string" ] || return 0
rules_nft_runtime_ucode combined-domain-text-csv "$string" "$type"
}
@@ -52,6 +134,7 @@ parse_combined_domain_list_to_commas_string() {
local list_value="$1"
local type="$2"
[ -n "$list_value" ] || return 0
rules_nft_runtime_ucode combined-domain-csv "$(rule_list_value_to_commas_string "$list_value")" "$type"
}
@@ -139,7 +222,7 @@ get_combined_domain_condition_commas_string() {
fi
}
get_rule_condition_commas_string() {
get_rule_condition_commas_string_uncached() {
local section="$1"
local key="$2"
local kind="$3"
@@ -200,40 +283,119 @@ get_rule_condition_commas_string() {
rule_list_value_to_commas_string "$list_value"
}
get_rule_condition_commas_string() {
local section="$1"
local key="$2"
local kind="$3"
local cache_path value
cache_path="$(rules_nft_cache_path condition "$section" "$key" "$kind" 2>/dev/null || true)"
if [ -n "$cache_path" ]; then
if value="$(rules_nft_cache_get "$cache_path")"; then
printf '%s\n' "$value"
return 0
fi
fi
value="$(get_rule_condition_commas_string_uncached "$section" "$key" "$kind")"
[ -z "$cache_path" ] || rules_nft_cache_put "$cache_path" "$value" || true
printf '%s\n' "$value"
}
get_rule_condition_json_array() {
local section="$1"
local key="$2"
local kind="$3"
local values
local values cache_path value
cache_path="$(rules_nft_cache_path condition_json "$section" "$key" "$kind" 2>/dev/null || true)"
if [ -n "$cache_path" ]; then
if value="$(rules_nft_cache_get "$cache_path")"; then
printf '%s\n' "$value"
return 0
fi
fi
values="$(get_rule_condition_commas_string "$section" "$key" "$kind")"
rules_nft_runtime_ucode csv-to-json-array "$values"
if [ -n "$values" ]; then
value="$(rules_nft_runtime_ucode csv-to-json-array "$values")"
else
value="[]"
fi
[ -z "$cache_path" ] || rules_nft_cache_put "$cache_path" "$value" || true
printf '%s\n' "$value"
}
get_rule_ports_commas_string() {
get_rule_ports_commas_string_uncached() {
local section="$1"
local values text_value
config_get values "$section" "ports"
config_get text_value "$section" "ports_text"
[ -n "$values$text_value" ] || return 0
rules_nft_runtime_ucode rule-ports-csv "$values" "$text_value"
}
get_rule_ports_commas_string() {
local section="$1"
local cache_path value
cache_path="$(rules_nft_cache_path ports "$section" ports value 2>/dev/null || true)"
if [ -n "$cache_path" ]; then
if value="$(rules_nft_cache_get "$cache_path")"; then
printf '%s\n' "$value"
return 0
fi
fi
value="$(get_rule_ports_commas_string_uncached "$section")"
[ -z "$cache_path" ] || rules_nft_cache_put "$cache_path" "$value" || true
printf '%s\n' "$value"
}
get_rule_port_values_json_array() {
local section="$1"
local ports
local ports cache_path value
cache_path="$(rules_nft_cache_path ports_json "$section" values value 2>/dev/null || true)"
if [ -n "$cache_path" ]; then
if value="$(rules_nft_cache_get "$cache_path")"; then
printf '%s\n' "$value"
return 0
fi
fi
ports="$(get_rule_ports_commas_string "$section")"
rules_nft_runtime_ucode rule-port-values-json "$ports"
if [ -n "$ports" ]; then
value="$(rules_nft_runtime_ucode rule-port-values-json "$ports")"
else
value="[]"
fi
[ -z "$cache_path" ] || rules_nft_cache_put "$cache_path" "$value" || true
printf '%s\n' "$value"
}
get_rule_port_ranges_json_array() {
local section="$1"
local ports
local ports cache_path value
cache_path="$(rules_nft_cache_path ports_json "$section" ranges value 2>/dev/null || true)"
if [ -n "$cache_path" ]; then
if value="$(rules_nft_cache_get "$cache_path")"; then
printf '%s\n' "$value"
return 0
fi
fi
ports="$(get_rule_ports_commas_string "$section")"
rules_nft_runtime_ucode rule-port-ranges-json "$ports"
if [ -n "$ports" ]; then
value="$(rules_nft_runtime_ucode rule-port-ranges-json "$ports")"
else
value="[]"
fi
[ -z "$cache_path" ] || rules_nft_cache_put "$cache_path" "$value" || true
printf '%s\n' "$value"
}
section_has_destination_matchers() {
@@ -397,7 +559,7 @@ resolve_ruleset_reference() {
ENSURED_RULESET_TAG=""
ENSURED_RULESET_KIND="unknown"
if helpers_ucode whitespace-list-contains "$COMMUNITY_SERVICES" "$reference" >/dev/null 2>&1; then
if list_has_item "$COMMUNITY_SERVICES" "$reference"; then
ensure_builtin_ruleset "$reference"
return 0
fi
+62 -3
View File
@@ -218,6 +218,55 @@ reload_state_is_compatible() {
[ "$(read_reload_state_value "format")" = "$PODKOP_RELOAD_STATE_FORMAT" ]
}
load_previous_reload_state() {
local line key value
PREVIOUS_RELOAD_STATE_FORMAT=""
PREVIOUS_RELOAD_STATE_SERVICE_TRIGGER_SIGNATURE=""
PREVIOUS_RELOAD_STATE_RESTART_SIGNATURE=""
PREVIOUS_RELOAD_STATE_DNSMASQ_SIGNATURE=""
PREVIOUS_RELOAD_STATE_SING_BOX_SIGNATURE=""
PREVIOUS_RELOAD_STATE_NFT_SIGNATURE=""
PREVIOUS_RELOAD_STATE_ZAPRET_QUEUE_SIGNATURE=""
PREVIOUS_RELOAD_STATE_ZAPRET_RUNTIME_SIGNATURE=""
PREVIOUS_RELOAD_STATE_ZAPRET2_QUEUE_SIGNATURE=""
PREVIOUS_RELOAD_STATE_ZAPRET2_RUNTIME_SIGNATURE=""
PREVIOUS_RELOAD_STATE_BYEDPI_RUNTIME_SIGNATURE=""
PREVIOUS_RELOAD_STATE_LIST_SIGNATURE=""
PREVIOUS_RELOAD_STATE_CRON_SIGNATURE=""
PREVIOUS_RELOAD_STATE_URLTEST_ENABLED_SECTIONS=""
PREVIOUS_RELOAD_STATE_URLTEST_ENABLED_SECTIONS_KNOWN=0
[ -f "$PODKOP_RELOAD_STATE_FILE" ] || return 1
while IFS= read -r line || [ -n "$line" ]; do
key="${line%%=*}"
[ "$line" != "$key" ] && value="${line#*=}" || value=""
case "$key" in
format) PREVIOUS_RELOAD_STATE_FORMAT="$value" ;;
service_trigger_signature) PREVIOUS_RELOAD_STATE_SERVICE_TRIGGER_SIGNATURE="$value" ;;
restart_signature) PREVIOUS_RELOAD_STATE_RESTART_SIGNATURE="$value" ;;
dnsmasq_signature) PREVIOUS_RELOAD_STATE_DNSMASQ_SIGNATURE="$value" ;;
sing_box_signature) PREVIOUS_RELOAD_STATE_SING_BOX_SIGNATURE="$value" ;;
nft_signature) PREVIOUS_RELOAD_STATE_NFT_SIGNATURE="$value" ;;
zapret_queue_signature) PREVIOUS_RELOAD_STATE_ZAPRET_QUEUE_SIGNATURE="$value" ;;
zapret_runtime_signature) PREVIOUS_RELOAD_STATE_ZAPRET_RUNTIME_SIGNATURE="$value" ;;
zapret2_queue_signature) PREVIOUS_RELOAD_STATE_ZAPRET2_QUEUE_SIGNATURE="$value" ;;
zapret2_runtime_signature) PREVIOUS_RELOAD_STATE_ZAPRET2_RUNTIME_SIGNATURE="$value" ;;
byedpi_runtime_signature) PREVIOUS_RELOAD_STATE_BYEDPI_RUNTIME_SIGNATURE="$value" ;;
list_signature) PREVIOUS_RELOAD_STATE_LIST_SIGNATURE="$value" ;;
cron_signature) PREVIOUS_RELOAD_STATE_CRON_SIGNATURE="$value" ;;
urltest_enabled_sections)
PREVIOUS_RELOAD_STATE_URLTEST_ENABLED_SECTIONS="$value"
PREVIOUS_RELOAD_STATE_URLTEST_ENABLED_SECTIONS_KNOWN=1
;;
esac
done < "$PODKOP_RELOAD_STATE_FILE"
[ "$PREVIOUS_RELOAD_STATE_FORMAT" = "$PODKOP_RELOAD_STATE_FORMAT" ]
}
signature_begin() {
RELOAD_SIGNATURE_TMPFILE="$(mktemp)"
}
@@ -230,11 +279,12 @@ signature_add() {
}
signature_finish() {
local hash
local hash_line
hash="$(md5sum "$RELOAD_SIGNATURE_TMPFILE" | runtime_state_ucode stdin-first-field)"
hash_line="$(md5sum "$RELOAD_SIGNATURE_TMPFILE")"
rm -f "$RELOAD_SIGNATURE_TMPFILE"
echo "$hash"
set -- $hash_line
echo "$1"
}
build_service_trigger_signature() {
@@ -1122,6 +1172,8 @@ capture_reload_state() {
}
write_reload_state() {
local status
mkdir -p "$PODKOP_RUNTIME_STATE_DIR"
runtime_state_ucode write-reload-state \
"$PODKOP_RELOAD_STATE_FILE" \
@@ -1139,6 +1191,13 @@ write_reload_state() {
"$RELOAD_STATE_CRON_SIGNATURE" \
"$RELOAD_STATE_URLTEST_ENABLED_SECTIONS" \
"$RELOAD_STATE_DONT_TOUCH_DHCP"
status="$?"
if [ -n "${PODKOP_RULE_CONDITION_CACHE_DIR:-}" ]; then
rm -rf "$PODKOP_RULE_CONDITION_CACHE_DIR" 2>/dev/null || true
PODKOP_RULE_CONDITION_CACHE_DIR=""
fi
rm -rf /tmp/podkop-plus-rule-cache.* 2>/dev/null || true
return "$status"
}
enabled_rule_has_list_sources() {
+220 -30
View File
@@ -488,6 +488,34 @@ sing_box_cf_log_subscription_skips() {
fi
}
sing_box_cf_prepared_file_skip_count() {
local prepared_path="$1"
local skipped_count
skipped_count="$(sing_box_cf_ucode skip-count-file "$prepared_path" 2>/dev/null)"
case "$skipped_count" in
'' | *[!0-9]*) printf '0\n' ;;
*) printf '%s\n' "$skipped_count" ;;
esac
}
sing_box_cf_log_subscription_skips_file() {
local prepared_path="$1"
local context="$2"
local skipped_count reason_summary
skipped_count="$(sing_box_cf_prepared_file_skip_count "$prepared_path")"
[ "$skipped_count" -gt 0 ] || return 0
reason_summary="$(sing_box_cf_ucode skip-summary-file "$prepared_path" 2>/dev/null)"
if [ -n "$reason_summary" ]; then
log "Skipped $skipped_count subscription outbounds $context: $reason_summary" "warn"
else
log "Skipped $skipped_count subscription outbounds $context" "warn"
fi
}
sing_box_cf_subscription_plugin_supports() {
local outbounds_json="$1"
local records_tmp plugin_name plugin_supports_json supported
@@ -509,6 +537,27 @@ sing_box_cf_subscription_plugin_supports() {
printf '%s\n' "$plugin_supports_json"
}
sing_box_cf_subscription_plugin_supports_file() {
local subscription_json_path="$1"
local records_tmp plugin_name plugin_supports_json supported
records_tmp="$(mktemp)" || return 1
: > "$records_tmp"
sing_box_cf_ucode subscription-plugin-names "$subscription_json_path" 2>/dev/null |
while IFS= read -r plugin_name || [ -n "$plugin_name" ]; do
[ -n "$plugin_name" ] || continue
command -v "$plugin_name" >/dev/null 2>&1 && supported=true || supported=false
printf '%s\t%s\n' "$plugin_name" "$supported" >> "$records_tmp"
done
plugin_supports_json="$(sing_box_cf_ucode plugin-supports-from-records "$records_tmp" 2>/dev/null)"
rm -f "$records_tmp"
[ -n "$plugin_supports_json" ] || plugin_supports_json="{}"
printf '%s\n' "$plugin_supports_json"
}
sing_box_cf_prepare_subscription_batch() {
local config="$1"
local outbounds_json="$2"
@@ -559,6 +608,45 @@ sing_box_cf_prepare_subscription_batch() {
return 1
}
sing_box_cf_prepare_subscription_batch_file() {
local config="$1"
local subscription_json_path="$2"
local prepared_path="$3"
local outbounds_count_path="$4"
local supports_xhttp plugin_supports_json config_tmp plugin_supports_tmp
supports_xhttp=false
if is_sing_box_extended; then
supports_xhttp=true
fi
plugin_supports_json="$(sing_box_cf_subscription_plugin_supports_file "$subscription_json_path")"
[ -n "$plugin_supports_json" ] || plugin_supports_json="{}"
command -v ucode >/dev/null 2>&1 || return 1
config_tmp="$(mktemp)" || return 1
plugin_supports_tmp="$(mktemp)" || {
rm -f "$config_tmp"
return 1
}
if printf '%s' "$config" > "$config_tmp" &&
printf '%s' "$plugin_supports_json" > "$plugin_supports_tmp" &&
sing_box_cf_ucode prepare-subscription-file \
"$config_tmp" \
"$subscription_json_path" \
"$prepared_path" \
"$outbounds_count_path" \
"$supports_xhttp" \
"$plugin_supports_tmp"; then
rm -f "$config_tmp" "$plugin_supports_tmp"
return 0
fi
rm -f "$config_tmp" "$plugin_supports_tmp"
return 1
}
sing_box_cf_validation_error_summary() {
local output="$1"
local summary
@@ -706,6 +794,26 @@ sing_box_cf_load_prepared_state() {
return "$status"
}
sing_box_cf_read_prepared_state_files() {
local tags_tmp="$1"
local tags_csv_tmp="$2"
local names_lines_tmp="$3"
local link_refs_tmp="$4"
local names_tmp="$5"
local servers_tmp="$6"
IFS= read -r SUBSCRIPTION_OUTBOUND_TAGS_JSON < "$tags_tmp" || SUBSCRIPTION_OUTBOUND_TAGS_JSON=""
[ -n "$SUBSCRIPTION_OUTBOUND_TAGS_JSON" ] || SUBSCRIPTION_OUTBOUND_TAGS_JSON="[]"
IFS= read -r SUBSCRIPTION_OUTBOUND_TAGS < "$tags_csv_tmp" || SUBSCRIPTION_OUTBOUND_TAGS=""
SUBSCRIPTION_OUTBOUND_NAMES="$(cat "$names_lines_tmp" 2>/dev/null)"
IFS= read -r SUBSCRIPTION_OUTBOUND_LINK_REFS_JSON < "$link_refs_tmp" || SUBSCRIPTION_OUTBOUND_LINK_REFS_JSON=""
[ -n "$SUBSCRIPTION_OUTBOUND_LINK_REFS_JSON" ] || SUBSCRIPTION_OUTBOUND_LINK_REFS_JSON="{}"
IFS= read -r SUBSCRIPTION_OUTBOUND_NAMES_JSON < "$names_tmp" || SUBSCRIPTION_OUTBOUND_NAMES_JSON=""
[ -n "$SUBSCRIPTION_OUTBOUND_NAMES_JSON" ] || SUBSCRIPTION_OUTBOUND_NAMES_JSON="{}"
IFS= read -r SUBSCRIPTION_OUTBOUND_SERVERS_JSON < "$servers_tmp" || SUBSCRIPTION_OUTBOUND_SERVERS_JSON=""
[ -n "$SUBSCRIPTION_OUTBOUND_SERVERS_JSON" ] || SUBSCRIPTION_OUTBOUND_SERVERS_JSON="{}"
}
sing_box_cf_append_subscription_prepared_metadata() {
local prepared_json="$1"
local tags_tmp names_lines_tmp link_refs_tmp names_tmp servers_tmp status
@@ -761,42 +869,105 @@ sing_box_cf_append_subscription_prepared_metadata() {
sing_box_cf_apply_subscription_batch() {
local config="$1"
local prepared_json="$2"
local new_outbounds_tmp new_outbounds_count_tmp new_outbounds_count
local tmpdir new_outbounds_tmp new_outbounds_count_tmp new_outbounds_count \
tags_tmp tags_csv_tmp names_lines_tmp link_refs_tmp names_tmp servers_tmp
new_outbounds_tmp="$(mktemp)" || return 1
new_outbounds_count_tmp="$(mktemp)" || {
rm -f "$new_outbounds_tmp"
return 1
}
tmpdir="$(mktemp -d)" || return 1
new_outbounds_tmp="$tmpdir/outbounds.json"
new_outbounds_count_tmp="$tmpdir/outbounds.count"
tags_tmp="$tmpdir/tags.json"
tags_csv_tmp="$tmpdir/tags.csv"
names_lines_tmp="$tmpdir/names.lines"
link_refs_tmp="$tmpdir/link-refs.json"
names_tmp="$tmpdir/names.json"
servers_tmp="$tmpdir/servers.json"
if ! sing_box_cf_prepared_field_to_file "$prepared_json" outbounds "$new_outbounds_tmp" "$new_outbounds_count_tmp"; then
rm -f "$new_outbounds_tmp" "$new_outbounds_count_tmp"
if ! printf '%s' "$prepared_json" |
ucode "$PODKOP_LIB/sing_box_config_facade.uc" prepared-outbounds-state-to-files "$SING_BOX_CF_SOURCE_SECTION" \
"$new_outbounds_tmp" "$new_outbounds_count_tmp" \
"$tags_tmp" "$tags_csv_tmp" "$names_lines_tmp" "$link_refs_tmp" "$names_tmp" "$servers_tmp"; then
rm -rf "$tmpdir"
return 1
fi
new_outbounds_count="$(cat "$new_outbounds_count_tmp" 2>/dev/null)"
rm -f "$new_outbounds_count_tmp"
case "$new_outbounds_count" in
'' | *[!0-9]*)
rm -f "$new_outbounds_tmp"
rm -rf "$tmpdir"
return 1
;;
esac
if [ "$new_outbounds_count" -eq 0 ]; then
rm -f "$new_outbounds_tmp"
rm -rf "$tmpdir"
return 1
fi
if ! sing_box_cf_try_subscription_outbounds_batch_file "$config" "$new_outbounds_tmp"; then
rm -f "$new_outbounds_tmp"
rm -rf "$tmpdir"
return 1
fi
rm -f "$new_outbounds_tmp"
sing_box_cf_load_prepared_state "$prepared_json" || return 1
sing_box_cf_read_prepared_state_files "$tags_tmp" "$tags_csv_tmp" "$names_lines_tmp" "$link_refs_tmp" "$names_tmp" "$servers_tmp"
SUBSCRIPTION_OUTBOUND_COUNT="$(sing_box_cf_tags_json_count "$SUBSCRIPTION_OUTBOUND_TAGS_JSON")"
[ "${SUBSCRIPTION_OUTBOUND_COUNT:-0}" -gt 0 ] || return 1
if [ "${SUBSCRIPTION_OUTBOUND_COUNT:-0}" -le 0 ]; then
rm -rf "$tmpdir"
return 1
fi
SING_BOX_CF_LAST_CONFIG="$SING_BOX_CF_VALIDATED_CONFIG"
rm -rf "$tmpdir"
return 0
}
sing_box_cf_apply_subscription_batch_file() {
local config="$1"
local prepared_path="$2"
local tmpdir new_outbounds_tmp new_outbounds_count_tmp new_outbounds_count \
tags_tmp tags_csv_tmp names_lines_tmp link_refs_tmp names_tmp servers_tmp
tmpdir="$(mktemp -d)" || return 1
new_outbounds_tmp="$tmpdir/outbounds.json"
new_outbounds_count_tmp="$tmpdir/outbounds.count"
tags_tmp="$tmpdir/tags.json"
tags_csv_tmp="$tmpdir/tags.csv"
names_lines_tmp="$tmpdir/names.lines"
link_refs_tmp="$tmpdir/link-refs.json"
names_tmp="$tmpdir/names.json"
servers_tmp="$tmpdir/servers.json"
if ! ucode "$PODKOP_LIB/sing_box_config_facade.uc" prepared-outbounds-state-file-to-files \
"$prepared_path" "$SING_BOX_CF_SOURCE_SECTION" \
"$new_outbounds_tmp" "$new_outbounds_count_tmp" \
"$tags_tmp" "$tags_csv_tmp" "$names_lines_tmp" "$link_refs_tmp" "$names_tmp" "$servers_tmp"; then
rm -rf "$tmpdir"
return 1
fi
new_outbounds_count="$(cat "$new_outbounds_count_tmp" 2>/dev/null)"
case "$new_outbounds_count" in
'' | *[!0-9]*)
rm -rf "$tmpdir"
return 1
;;
esac
if [ "$new_outbounds_count" -eq 0 ]; then
rm -rf "$tmpdir"
return 1
fi
if ! sing_box_cf_try_subscription_outbounds_batch_file "$config" "$new_outbounds_tmp"; then
rm -rf "$tmpdir"
return 1
fi
sing_box_cf_read_prepared_state_files "$tags_tmp" "$tags_csv_tmp" "$names_lines_tmp" "$link_refs_tmp" "$names_tmp" "$servers_tmp"
SUBSCRIPTION_OUTBOUND_COUNT="$(sing_box_cf_tags_json_count "$SUBSCRIPTION_OUTBOUND_TAGS_JSON")"
if [ "${SUBSCRIPTION_OUTBOUND_COUNT:-0}" -le 0 ]; then
rm -rf "$tmpdir"
return 1
fi
SING_BOX_CF_LAST_CONFIG="$SING_BOX_CF_VALIDATED_CONFIG"
rm -rf "$tmpdir"
return 0
}
@@ -907,7 +1078,7 @@ sing_box_cf_add_subscription_outbounds() {
local section="$2"
local subscription_json_path="$3"
local source_section="${4:-}"
local outbounds_json outbounds_count prepared_json skipped_count
local tmpdir prepared_tmp outbounds_count_tmp outbounds_count prepared_json skipped_count
SUBSCRIPTION_OUTBOUND_TAGS=""
SUBSCRIPTION_OUTBOUND_TAGS_JSON="[]"
@@ -925,32 +1096,51 @@ sing_box_cf_add_subscription_outbounds() {
return 1
fi
outbounds_json="$(sing_box_cf_subscription_candidate_outbounds "$subscription_json_path")"
outbounds_count="$(sing_box_cf_outbounds_json_count "$outbounds_json")"
tmpdir="$(mktemp -d)" || {
echo "$config"
return 1
}
prepared_tmp="$tmpdir/prepared.json"
outbounds_count_tmp="$tmpdir/outbounds.count"
if [ -z "$outbounds_count" ] || [ "$outbounds_count" -eq 0 ]; then
if ! sing_box_cf_prepare_subscription_batch_file "$config" "$subscription_json_path" "$prepared_tmp" "$outbounds_count_tmp"; then
rm -rf "$tmpdir"
echo "$config"
return 1
fi
outbounds_count="$(cat "$outbounds_count_tmp" 2>/dev/null)"
case "$outbounds_count" in
'' | *[!0-9]*)
rm -rf "$tmpdir"
echo "$config"
return 1
;;
esac
if [ "$outbounds_count" -eq 0 ]; then
log "No proxy outbounds found in subscription JSON" "error"
rm -rf "$tmpdir"
echo "$config"
return 1
fi
log "Found $outbounds_count proxy outbounds in subscription" "info"
prepared_json="$(sing_box_cf_prepare_subscription_batch "$config" "$outbounds_json")"
if [ -n "$prepared_json" ]; then
skipped_count="$(sing_box_cf_ucode_input skip-count "$prepared_json" 2>/dev/null)"
if [ "${skipped_count:-0}" -gt 0 ]; then
sing_box_cf_log_subscription_skips "$prepared_json" "before validation"
fi
skipped_count="$(sing_box_cf_prepared_file_skip_count "$prepared_tmp")"
if [ "${skipped_count:-0}" -gt 0 ]; then
sing_box_cf_log_subscription_skips_file "$prepared_tmp" "before validation"
fi
if sing_box_cf_apply_subscription_batch "$config" "$prepared_json"; then
log "Added $(sing_box_cf_subscription_outbound_count) subscription outbounds for rule '$section'" "info"
echo "$SING_BOX_CF_LAST_CONFIG"
return 0
fi
if sing_box_cf_apply_subscription_batch_file "$config" "$prepared_tmp"; then
log "Added $(sing_box_cf_subscription_outbound_count) subscription outbounds for rule '$section'" "info"
rm -rf "$tmpdir"
echo "$SING_BOX_CF_LAST_CONFIG"
return 0
fi
log "Batch subscription validation failed for rule '$section', trying chunked fallback validation" "warn"
prepared_json="$(cat "$prepared_tmp" 2>/dev/null)" || prepared_json=""
rm -rf "$tmpdir"
if [ -n "$prepared_json" ] && sing_box_cf_apply_subscription_outbounds_chunked "$config" "$prepared_json"; then
log "Added $(sing_box_cf_subscription_outbound_count) subscription outbounds for rule '$section'" "info"
echo "$SING_BOX_CF_LAST_CONFIG"
+119 -16
View File
@@ -587,8 +587,7 @@ function rewrite_prepared_outbound_references(outbounds, tag_map) {
}
}
function candidate_outbounds(path) {
let subscription = object_or_empty(read_json_file(path));
function candidate_outbound_list(subscription) {
let result = [];
let skipped_types = {
selector: true,
@@ -607,16 +606,19 @@ function candidate_outbounds(path) {
push(result, outbound);
}
return result;
}
function candidate_outbounds(path) {
let subscription = object_or_empty(read_json_file(path));
let result = candidate_outbound_list(subscription);
write_json(result);
}
function prepare_subscription(config_path, outbounds_path, output_path, supports_xhttp_arg, plugin_supports_path) {
let config = read_json_file(config_path);
let outbounds = read_json_file(outbounds_path);
let plugin_supports = object_or_empty(read_json_file(plugin_supports_path));
function prepare_subscription_object(config, outbounds, supports_xhttp_arg, plugin_supports) {
if (type(config) != "object" || type(outbounds) != "array")
exit(1);
return null;
let supports_xhttp = supports_xhttp_arg == "true" || supports_xhttp_arg == "1";
let taken = {};
@@ -667,7 +669,7 @@ function prepare_subscription(config_path, outbounds_path, output_path, supports
rewrite_prepared_outbound_references(prepared, tag_map);
if (!write_file_json(output_path, {
return {
outbounds: prepared,
links,
source_indices,
@@ -677,17 +679,52 @@ function prepare_subscription(config_path, outbounds_path, output_path, supports
skipped_reason_counts: length(keys(skipped_reason_counts)) > 0 ? skipped_reason_counts : [],
tags,
visible
}))
};
}
function prepare_subscription(config_path, outbounds_path, output_path, supports_xhttp_arg, plugin_supports_path) {
let prepared = prepare_subscription_object(
read_json_file(config_path),
read_json_file(outbounds_path),
supports_xhttp_arg,
object_or_empty(read_json_file(plugin_supports_path))
);
if (prepared == null || !write_file_json(output_path, prepared))
exit(1);
}
function skip_count() {
let prepared = object_or_empty(read_stdin_json());
function prepare_subscription_file(config_path, subscription_path, output_path, count_path, supports_xhttp_arg, plugin_supports_path) {
let subscription = object_or_empty(read_json_file(subscription_path));
let outbounds = candidate_outbound_list(subscription);
let prepared = prepare_subscription_object(
read_json_file(config_path),
outbounds,
supports_xhttp_arg,
object_or_empty(read_json_file(plugin_supports_path))
);
if (prepared == null)
exit(1);
if (as_string(count_path) != "" && !write_text_file(count_path, length(outbounds) + "\n"))
exit(1);
if (!write_file_json(output_path, prepared))
exit(1);
}
function print_skip_count(prepared) {
print(as_string(prepared.skipped || 0), "\n");
}
function skip_summary() {
let prepared = object_or_empty(read_stdin_json());
function skip_count() {
print_skip_count(object_or_empty(read_stdin_json()));
}
function skip_count_file(path) {
print_skip_count(object_or_empty(read_json_file(path)));
}
function print_skip_summary(prepared) {
let counts = object_or_empty(prepared.skipped_reason_counts);
let entries = [];
@@ -707,8 +744,15 @@ function skip_summary() {
print(join("; ", parts), "\n");
}
function plugin_names() {
let outbounds = array_or_empty(read_stdin_json());
function skip_summary() {
print_skip_summary(object_or_empty(read_stdin_json()));
}
function skip_summary_file(path) {
print_skip_summary(object_or_empty(read_json_file(path)));
}
function print_plugin_names_from_outbounds(outbounds) {
let seen = {};
for (let outbound in outbounds) {
if (type(outbound) != "object" || outbound.type != "shadowsocks")
@@ -725,6 +769,15 @@ function plugin_names() {
print(name, "\n");
}
function plugin_names() {
print_plugin_names_from_outbounds(array_or_empty(read_stdin_json()));
}
function subscription_plugin_names(path) {
let subscription = object_or_empty(read_json_file(path));
print_plugin_names_from_outbounds(candidate_outbound_list(subscription));
}
function plugin_supports_from_records(path) {
let result = {};
let data = fs.readfile(path);
@@ -963,6 +1016,44 @@ function prepared_state_to_files(source_section, tags_path, tags_csv_path, names
exit(1);
}
function prepared_outbounds_state_to_files(source_section, outbounds_path, count_path, tags_path, tags_csv_path, names_lines_path, link_refs_path, names_path, servers_path) {
let prepared = object_or_empty(read_stdin_json());
prepared_outbounds_state_value_to_files(prepared, source_section, outbounds_path, count_path, tags_path, tags_csv_path, names_lines_path, link_refs_path, names_path, servers_path);
}
function prepared_outbounds_state_value_to_files(prepared, source_section, outbounds_path, count_path, tags_path, tags_csv_path, names_lines_path, link_refs_path, names_path, servers_path) {
let outbounds = array_or_empty(prepared.outbounds);
let tags = prepared_visible_tags(prepared);
let tag_values = [];
for (let tag in tags)
push(tag_values, as_string(tag));
if (!write_file_json(outbounds_path, outbounds) ||
!write_text_file(count_path, length(outbounds) + "\n") ||
!write_file_json(tags_path, tags) ||
!write_text_file(tags_csv_path, join(",", tag_values) + "\n") ||
!write_text_file(names_lines_path, prepared_names_text(prepared)) ||
!write_file_json(link_refs_path, prepared_link_refs(prepared, source_section)) ||
!write_file_json(names_path, prepared_names(prepared)) ||
!write_file_json(servers_path, prepared_servers(prepared)))
exit(1);
}
function prepared_outbounds_state_file_to_files(prepared_path, source_section, outbounds_path, count_path, tags_path, tags_csv_path, names_lines_path, link_refs_path, names_path, servers_path) {
prepared_outbounds_state_value_to_files(
object_or_empty(read_json_file(prepared_path)),
source_section,
outbounds_path,
count_path,
tags_path,
tags_csv_path,
names_lines_path,
link_refs_path,
names_path,
servers_path
);
}
function append_prepared_state_to_files(source_section, tags_path, names_lines_path, link_refs_path, names_path, servers_path) {
let prepared = object_or_empty(read_stdin_json());
let tags = array_or_empty(read_json_file(tags_path));
@@ -1010,6 +1101,8 @@ let mode = ARGV[0] || "";
if (mode == "candidate-outbounds")
candidate_outbounds(ARGV[1]);
else if (mode == "subscription-plugin-names")
subscription_plugin_names(ARGV[1]);
else if (mode == "csv-to-json-array")
csv_to_json_array(ARGV[1]);
else if (mode == "tls-alpn-json-array")
@@ -1022,14 +1115,20 @@ else if (mode == "shadowsocks-userinfo-format-valid")
exit(shadowsocks_userinfo_format_valid(ARGV[1]) ? 0 : 1);
else if (mode == "skip-count")
skip_count();
else if (mode == "skip-count-file")
skip_count_file(ARGV[1]);
else if (mode == "skip-summary")
skip_summary();
else if (mode == "skip-summary-file")
skip_summary_file(ARGV[1]);
else if (mode == "plugin-names")
plugin_names();
else if (mode == "plugin-supports-from-records")
plugin_supports_from_records(ARGV[1]);
else if (mode == "prepare-subscription")
prepare_subscription(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]);
else if (mode == "prepare-subscription-file")
prepare_subscription_file(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]);
else if (mode == "prepare-validation")
prepare_validation(ARGV[1], ARGV[2], ARGV[3]);
else if (mode == "prepared-slice")
@@ -1052,6 +1151,10 @@ else if (mode == "validation-error-summary")
validation_error_summary();
else if (mode == "prepared-state-to-files")
prepared_state_to_files(as_string(ARGV[1]), ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7]);
else if (mode == "prepared-outbounds-state-to-files")
prepared_outbounds_state_to_files(as_string(ARGV[1]), ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9]);
else if (mode == "prepared-outbounds-state-file-to-files")
prepared_outbounds_state_file_to_files(ARGV[1], as_string(ARGV[2]), ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10]);
else if (mode == "append-prepared-state-to-files")
append_prepared_state_to_files(as_string(ARGV[1]), ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]);
else if (mode == "prepared-display-name")
@@ -243,6 +243,11 @@ sing_box_cm_add_route_rule() {
sing_box_cm_ucode add-route-rule "$1" "$2" "$3" "$4"
}
sing_box_cm_add_route_rule_with_matchers() {
sing_box_cm_ucode add-route-rule-with-matchers \
"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" "${12}" "${13}"
}
sing_box_cm_add_resolve_rule() {
sing_box_cm_ucode add-resolve-rule "$1" "$2" "$3" "${4:-dns-server}"
}
@@ -251,6 +256,14 @@ sing_box_cm_patch_route_rule() {
sing_box_cm_ucode patch-route-rule "$1" "$2" "$3" "$4"
}
sing_box_cm_patch_rule_set_reference() {
sing_box_cm_ucode patch-rule-set-reference "$1" "$2" "$3" "$4"
}
sing_box_cm_patch_dns_route_rule_matchers() {
sing_box_cm_ucode patch-dns-route-rule-matchers "$1" "$2" "$3" "$4" "$5" "$6"
}
sing_box_cm_add_reject_route_rule() {
sing_box_cm_ucode add-reject-route-rule "$1" "$2" "$3"
}
@@ -287,6 +300,14 @@ sing_box_cm_add_remote_ruleset() {
sing_box_cm_ucode add-remote-ruleset "$1" "$2" "$3" "$4" "$5" "$6"
}
sing_box_cm_add_remote_ruleset_reference() {
sing_box_cm_ucode add-remote-ruleset-reference "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8"
}
sing_box_cm_add_local_ruleset_reference() {
sing_box_cm_ucode add-local-ruleset-reference "$1" "$2" "$3" "$4" "$5" "$6"
}
sing_box_cm_configure_cache_file() {
sing_box_cm_ucode configure-cache-file "$1" "$2" "$3" "$4"
}
@@ -933,6 +933,62 @@ function add_route_rule(config, args) {
add_rule(config, "route", rule);
}
function json_arg_nonempty(value) {
let parsed = json_arg(value);
if (type(parsed) == "array")
return length(parsed) > 0 ? parsed : null;
if (type(parsed) == "object")
return length(keys(parsed)) > 0 ? parsed : null;
let string_value = as_string(parsed);
return string_value != "" ? parsed : null;
}
function patch_tagged_rule_if_nonempty(rules, tag, key, value) {
value = json_arg_nonempty(value);
if (value != null)
patch_tagged_rule(rules, tag, key, value);
}
function add_route_rule_with_matchers(config, args) {
let tag = as_string(args[0]);
let inbound = as_string(args[1]);
let outbound = as_string(args[2]);
let action = as_string(args[3]);
let route = ensure_object(config, "route");
let rules = ensure_array(route, "rules");
if (action == "reject") {
let rule = { action: "reject" };
optional_string(rule, "inbound", inbound);
rule[SERVICE_TAG] = tag;
push(rules, rule);
}
else {
let rule = {
action: "route",
inbound,
outbound
};
rule[SERVICE_TAG] = tag;
push(rules, rule);
}
let matcher_keys = [
"domain",
"domain_suffix",
"domain_keyword",
"domain_regex",
"ip_cidr",
"port",
"port_range",
"source_ip_cidr"
];
for (let i = 0; i < length(matcher_keys); i++)
patch_tagged_rule_if_nonempty(rules, tag, matcher_keys[i], args[4 + i]);
}
function copy_rule_matchers(rule) {
let copied = {};
for (let key in ["inbound", "source_ip_cidr", "domain", "domain_suffix", "domain_keyword", "domain_regex", "rule_set", "port", "port_range"]) {
@@ -965,6 +1021,30 @@ function patch_route_rule(config, args) {
patch_tagged_rule(ensure_array(ensure_object(config, "route"), "rules"), as_string(args[0]), as_string(args[1]), json_arg(args[2]));
}
function patch_rule_set_reference(config, args) {
let route_tag = as_string(args[0]);
let dns_tag = as_string(args[1]);
let rule_set_tag = as_string(args[2]);
if (route_tag != "" && rule_set_tag != "")
patch_tagged_rule(ensure_array(ensure_object(config, "route"), "rules"), route_tag, "rule_set", rule_set_tag);
if (dns_tag != "" && rule_set_tag != "")
patch_tagged_rule(ensure_array(ensure_object(config, "dns"), "rules"), dns_tag, "rule_set", rule_set_tag);
}
function patch_dns_route_rule_matchers(config, args) {
let rules = ensure_array(ensure_object(config, "dns"), "rules");
let tag = as_string(args[0]);
let matcher_keys = [
"domain",
"domain_suffix",
"domain_keyword",
"domain_regex"
];
for (let i = 0; i < length(matcher_keys); i++)
patch_tagged_rule_if_nonempty(rules, tag, matcher_keys[i], args[1 + i]);
}
function add_reject_route_rule(config, args) {
let rule = {
action: "reject"
@@ -1086,6 +1166,34 @@ function add_remote_ruleset(config, args) {
add_rule_set(config, rule_set);
}
function add_remote_ruleset_reference(config, args) {
add_remote_ruleset(config, [
args[2],
args[3],
args[4],
args[5],
args[6]
]);
patch_rule_set_reference(config, [
args[0],
args[1],
args[2]
]);
}
function add_local_ruleset_reference(config, args) {
add_local_ruleset(config, [
args[2],
args[3],
args[4]
]);
patch_rule_set_reference(config, [
args[0],
args[1],
args[2]
]);
}
function configure_cache_file(config, args) {
let experimental = ensure_object(config, "experimental");
experimental.cache_file = {
@@ -1153,8 +1261,11 @@ let handlers = {
"add-selector-outbound-file": add_selector_outbound_file,
"configure-route": configure_route,
"add-route-rule": add_route_rule,
"add-route-rule-with-matchers": add_route_rule_with_matchers,
"add-resolve-rule": add_resolve_rule,
"patch-route-rule": patch_route_rule,
"patch-rule-set-reference": patch_rule_set_reference,
"patch-dns-route-rule-matchers": patch_dns_route_rule_matchers,
"add-reject-route-rule": add_reject_route_rule,
"add-hijack-dns-route-rule": add_hijack_dns_route_rule,
"add-options-route-rule": add_options_route_rule,
@@ -1164,6 +1275,8 @@ let handlers = {
"add-inline-ruleset-rule": add_inline_ruleset_rule,
"add-local-ruleset": add_local_ruleset,
"add-remote-ruleset": add_remote_ruleset,
"add-remote-ruleset-reference": add_remote_ruleset_reference,
"add-local-ruleset-reference": add_local_ruleset_reference,
"configure-cache-file": configure_cache_file,
"configure-clash-api": configure_clash_api
};
+122 -54
View File
@@ -116,6 +116,11 @@ sing_box_configure_service() {
sing_box_init_config() {
local config='{"log":{},"dns":{},"ntp":{},"certificate":{},"endpoints":[],"inbounds":[],"outbounds":[],"route":{},"services":[],"experimental":{}}'
PODKOP_RULE_CONDITION_CACHE_ENABLED=1
rm -rf /tmp/podkop-plus-rule-cache.* 2>/dev/null || true
PODKOP_RULE_CONDITION_CACHE_DIR=""
PODKOP_RULE_CONDITION_CACHE_CLEANED=1
PODKOP_DNS_ROUTE_RULE_TAGS=""
PODKOP_URLTEST_SELECTOR_SWITCHES=""
configured_rulesets=""
sing_box_configure_log
@@ -1272,21 +1277,58 @@ create_route_rule_for_action() {
esac
}
resolve_route_rule_action_target() {
local section="$1"
local action="$2"
ROUTE_RULE_BUILDER_ACTION="route"
ROUTE_RULE_BUILDER_OUTBOUND_TAG=""
case "$action" in
proxy | outbound | vpn | byedpi | zapret | zapret2 | direct)
ROUTE_RULE_BUILDER_OUTBOUND_TAG="$(get_outbound_tag_by_section "$section")"
;;
block)
ROUTE_RULE_BUILDER_ACTION="reject"
;;
*)
log "Unsupported action '$action' for rule '$section'. Aborted." "fatal"
exit 1
;;
esac
}
dns_route_rule_exists() {
local tag="$1"
printf '%s' "$config" | sing_box_runtime_ucode dns-route-rule-exists "$SERVICE_TAG" "$tag" >/dev/null 2>&1
}
dns_route_rule_is_known() {
list_has_item "$PODKOP_DNS_ROUTE_RULE_TAGS" "$1"
}
mark_dns_route_rule_known() {
local tag="$1"
dns_route_rule_is_known "$tag" && return 0
PODKOP_DNS_ROUTE_RULE_TAGS="${PODKOP_DNS_ROUTE_RULE_TAGS}${PODKOP_DNS_ROUTE_RULE_TAGS:+ }$tag"
}
ensure_fakeip_dns_route_rule() {
local tag="$1"
local rewrite_ttl
dns_route_rule_exists "$tag" && return 0
dns_route_rule_is_known "$tag" && return 0
if dns_route_rule_exists "$tag"; then
mark_dns_route_rule_known "$tag"
return 0
fi
config=$(sing_box_cm_add_dns_route_rule "$config" "$SB_FAKEIP_DNS_SERVER_TAG" "$tag")
config_get rewrite_ttl "settings" "dns_rewrite_ttl" "60"
config=$(sing_box_cm_patch_dns_route_rule "$config" "$tag" "rewrite_ttl" "$rewrite_ttl")
mark_dns_route_rule_known "$tag"
}
route_rule_has_resolve_matchers() {
@@ -1298,16 +1340,17 @@ route_rule_has_resolve_matchers() {
configure_rule_set_reference_handler() {
local reference="$1"
local route_rule_tag="$2"
local dns_rule_tag=""
resolve_ruleset_reference "$reference"
if [ -n "$ENSURED_RULESET_TAG" ]; then
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ENSURED_RULESET_TAG")
if [ "$ENSURED_RULESET_KIND" = "domains" ]; then
ensure_fakeip_dns_route_rule "$SB_FAKEIP_RULESET_DNS_RULE_TAG"
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_RULESET_DNS_RULE_TAG" "rule_set" "$ENSURED_RULESET_TAG")
dns_rule_tag="$SB_FAKEIP_RULESET_DNS_RULE_TAG"
else
log "Skip DNS FakeIP rule patch for '$reference' ruleset kind '$ENSURED_RULESET_KIND'" "debug"
fi
config=$(sing_box_cm_patch_rule_set_reference "$config" "$route_rule_tag" "$dns_rule_tag" "$ENSURED_RULESET_TAG")
fi
}
@@ -1359,7 +1402,7 @@ cleanup_empty_domain_ip_list_ruleset() {
ensure_domain_ip_list_ruleset() {
local section="$1"
local route_rule_tag="$2"
local ruleset_tag ruleset_filepath
local ruleset_tag ruleset_filepath dns_rule_tag=""
ruleset_tag="$(get_domain_ip_list_ruleset_tag "$section")"
ruleset_filepath="$(get_domain_ip_list_ruleset_path "$section")"
@@ -1369,13 +1412,11 @@ ensure_domain_ip_list_ruleset() {
return 0
fi
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")
if source_ruleset_has_domain_matchers "$ruleset_filepath"; then
ensure_fakeip_dns_route_rule "$SB_FAKEIP_RULESET_DNS_RULE_TAG"
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_RULESET_DNS_RULE_TAG" "rule_set" "$ruleset_tag")
dns_rule_tag="$SB_FAKEIP_RULESET_DNS_RULE_TAG"
fi
config=$(sing_box_cm_add_local_ruleset_reference "$config" "$route_rule_tag" "$dns_rule_tag" "$ruleset_tag" "source" "$ruleset_filepath")
}
import_domain_ip_list_file_into_rulesets() {
@@ -1553,7 +1594,9 @@ rule_has_primary_matchers() {
configure_route_rule_handler() {
local section="$1"
local action route_rule_tag ip_values_json ip_values port_values_json port_ranges_json
local action route_rule_tag ip_values_json ip_values port_values_json port_ranges_json \
domain_json domain_suffix_json domain_keyword_json domain_regex_json source_ip_values_json \
ports community_lists rule_set rule_set_with_subnets domain_ip_lists
rule_is_enabled "$section" || return 0
if subscription_section_is_deferred "$section"; then
@@ -1580,63 +1623,78 @@ configure_route_rule_handler() {
configure_fully_routed_ips_for_section "$section" "$action"
if ! rule_has_primary_matchers "$section"; then
domain_json="$(get_rule_condition_json_array "$section" "domain" "domains")"
domain_suffix_json="$(get_rule_condition_json_array "$section" "domain_suffix" "domains")"
domain_keyword_json="$(get_rule_condition_json_array "$section" "domain_keyword" "generic")"
domain_regex_json="$(get_rule_condition_json_array "$section" "domain_regex" "generic")"
ip_values_json="$(get_rule_condition_json_array "$section" "ip_cidr" "subnets")"
port_values_json="$(get_rule_port_values_json_array "$section")"
port_ranges_json="$(get_rule_port_ranges_json_array "$section")"
source_ip_values_json="$(get_rule_condition_json_array "$section" "source_ip_cidr" "subnets")"
ports="$(get_rule_ports_commas_string "$section")"
config_get community_lists "$section" "community_lists"
config_get rule_set "$section" "rule_set"
config_get rule_set_with_subnets "$section" "rule_set_with_subnets"
config_get domain_ip_lists "$section" "domain_ip_lists"
if [ "$domain_json" = "[]" ] &&
[ "$domain_suffix_json" = "[]" ] &&
[ "$domain_keyword_json" = "[]" ] &&
[ "$domain_regex_json" = "[]" ] &&
[ "$ip_values_json" = "[]" ] &&
[ "$port_values_json" = "[]" ] &&
[ "$port_ranges_json" = "[]" ] &&
[ -z "$community_lists" ] &&
[ -z "$rule_set" ] &&
[ -z "$rule_set_with_subnets" ] &&
[ -z "$domain_ip_lists" ]; then
log "Rule '$section' has no destination matchers and action '$action', skipping regular route creation" "warn"
return 0
fi
route_rule_tag="$(gen_id)"
create_route_rule_for_action "$section" "$action" "$route_rule_tag"
resolve_route_rule_action_target "$section" "$action"
local domain_json domain_suffix_json domain_keyword_json domain_regex_json
domain_json="$(get_rule_condition_json_array "$section" "domain" "domains")"
[ "$domain_json" != "[]" ] && config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "domain" "$domain_json") && \
ensure_fakeip_dns_route_rule "$SB_FAKEIP_DNS_RULE_TAG" && \
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "domain" "$domain_json")
config=$(sing_box_cm_add_route_rule_with_matchers \
"$config" \
"$route_rule_tag" \
"$SB_TPROXY_INBOUND_TAG" \
"$ROUTE_RULE_BUILDER_OUTBOUND_TAG" \
"$ROUTE_RULE_BUILDER_ACTION" \
"$domain_json" \
"$domain_suffix_json" \
"$domain_keyword_json" \
"$domain_regex_json" \
"$ip_values_json" \
"$port_values_json" \
"$port_ranges_json" \
"$source_ip_values_json")
domain_suffix_json="$(get_rule_condition_json_array "$section" "domain_suffix" "domains")"
[ "$domain_suffix_json" != "[]" ] && config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "domain_suffix" "$domain_suffix_json") && \
ensure_fakeip_dns_route_rule "$SB_FAKEIP_DNS_RULE_TAG" && \
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "domain_suffix" "$domain_suffix_json")
if [ "$domain_json" != "[]" ] ||
[ "$domain_suffix_json" != "[]" ] ||
[ "$domain_keyword_json" != "[]" ] ||
[ "$domain_regex_json" != "[]" ]; then
ensure_fakeip_dns_route_rule "$SB_FAKEIP_DNS_RULE_TAG"
config=$(sing_box_cm_patch_dns_route_rule_matchers \
"$config" \
"$SB_FAKEIP_DNS_RULE_TAG" \
"$domain_json" \
"$domain_suffix_json" \
"$domain_keyword_json" \
"$domain_regex_json")
fi
domain_keyword_json="$(get_rule_condition_json_array "$section" "domain_keyword" "generic")"
[ "$domain_keyword_json" != "[]" ] && config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "domain_keyword" "$domain_keyword_json") && \
ensure_fakeip_dns_route_rule "$SB_FAKEIP_DNS_RULE_TAG" && \
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "domain_keyword" "$domain_keyword_json")
domain_regex_json="$(get_rule_condition_json_array "$section" "domain_regex" "generic")"
[ "$domain_regex_json" != "[]" ] && config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "domain_regex" "$domain_regex_json") && \
ensure_fakeip_dns_route_rule "$SB_FAKEIP_DNS_RULE_TAG" && \
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "domain_regex" "$domain_regex_json")
ip_values_json="$(get_rule_condition_json_array "$section" "ip_cidr" "subnets")"
if [ "$ip_values_json" != "[]" ]; then
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "ip_cidr" "$ip_values_json")
if [ "$PODKOP_NFT_POPULATE_ENABLED" = "1" ]; then
ip_values="$(get_rule_condition_commas_string "$section" "ip_cidr" "subnets")"
add_section_ip_cidr_to_nft_sets "$section" "$ip_values"
fi
fi
port_values_json="$(get_rule_port_values_json_array "$section")"
[ "$port_values_json" != "[]" ] && config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "port" "$port_values_json")
port_ranges_json="$(get_rule_port_ranges_json_array "$section")"
[ "$port_ranges_json" != "[]" ] && config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "port_range" "$port_ranges_json")
if [ "$PODKOP_NFT_POPULATE_ENABLED" = "1" ]; then
add_section_ports_to_nft_set_if_needed "$section"
fi
local source_ip_values_json
source_ip_values_json="$(get_rule_condition_json_array "$section" "source_ip_cidr" "subnets")"
if [ "$source_ip_values_json" != "[]" ]; then
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "source_ip_cidr" "$source_ip_values_json")
fi
local domain_ip_lists rule_set_with_subnets
config_get domain_ip_lists "$section" "domain_ip_lists"
config_get rule_set_with_subnets "$section" "rule_set_with_subnets"
config_list_foreach "$section" "community_lists" configure_community_list_handler "$section" "$route_rule_tag"
config_list_foreach "$section" "rule_set" configure_rule_set_reference_handler "$route_rule_tag"
config_list_foreach "$section" "rule_set_with_subnets" configure_rule_set_reference_handler "$route_rule_tag"
@@ -1715,10 +1773,16 @@ configure_community_list_handler() {
detour="$(get_download_detour_tag)"
update_interval="$(get_remote_ruleset_update_interval)"
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")
ensure_fakeip_dns_route_rule "$SB_FAKEIP_RULESET_DNS_RULE_TAG"
config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_RULESET_DNS_RULE_TAG" "rule_set" "$ruleset_tag")
config=$(sing_box_cm_add_remote_ruleset_reference \
"$config" \
"$route_rule_tag" \
"$SB_FAKEIP_RULESET_DNS_RULE_TAG" \
"$ruleset_tag" \
"$format" \
"$url" \
"$detour" \
"$update_interval")
}
sing_box_configure_experimental() {
@@ -1839,7 +1903,7 @@ configure_section_mixed_proxy() {
}
sing_box_save_config() {
local sing_box_config_path temp_file_path current_config_hash temp_config_hash
local sing_box_config_path temp_file_path current_config_hash temp_config_hash hash_line
config_get sing_box_config_path "settings" "config_path"
temp_file_path="$(mktemp)"
@@ -1848,8 +1912,12 @@ sing_box_save_config() {
sing_box_config_check "$temp_file_path"
current_config_hash=$(md5sum "$sing_box_config_path" 2> /dev/null | sing_box_runtime_ucode stdin-first-field)
temp_config_hash=$(md5sum "$temp_file_path" | sing_box_runtime_ucode stdin-first-field)
hash_line="$(md5sum "$sing_box_config_path" 2> /dev/null || true)"
set -- $hash_line
current_config_hash="$1"
hash_line="$(md5sum "$temp_file_path")"
set -- $hash_line
temp_config_hash="$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
@@ -1883,7 +1951,7 @@ import_builtin_subnets_reference_handler() {
local reference="$1"
local section="$2"
if helpers_ucode whitespace-list-contains "$COMMUNITY_SERVICES" "$reference" >/dev/null 2>&1; then
if list_has_item "$COMMUNITY_SERVICES" "$reference"; then
import_community_service_subnet_list_handler "$reference" "$section" || BUILTIN_SUBNET_IMPORT_STATUS=1
fi
return 0
+23 -17
View File
@@ -191,10 +191,11 @@ persist_subscription_cache() {
local subscription_json_path="$2"
local subscription_url="$3"
local effective_user_agent="$4"
local already_validated="${5:-0}"
local persistent_json_path persistent_url_path persistent_user_agent_path
subscription_cache_section_is_safe "$source_section" || return 1
subscription_cache_is_usable "$subscription_json_path" || return 1
[ "$already_validated" -eq 1 ] || subscription_cache_is_usable "$subscription_json_path" || return 1
mkdir -p "$PODKOP_PERSISTENT_SUBSCRIPTION_CACHE_DIR" || return 1
chmod 700 "$PODKOP_PERSISTENT_SUBSCRIPTION_CACHE_DIR" 2>/dev/null || true
@@ -518,9 +519,11 @@ subscription_config_is_current() {
subscription_auto_user_agent_is_supported() {
local user_agent="$1"
local default_user_agent="${2:-}"
[ -n "$user_agent" ] || return 1
[ "$user_agent" = "$(get_subscription_user_agent)" ] && return 0
[ -n "$default_user_agent" ] || default_user_agent="$(get_subscription_user_agent)"
[ "$user_agent" = "$default_user_agent" ] && return 0
case "$user_agent" in
Happ | v2rayN | Hiddify | Clash.Meta | ClashMetaForAndroid)
@@ -559,12 +562,25 @@ subscription_write_user_agent_candidates() {
default_user_agent="$(get_subscription_user_agent)"
for candidate in "$default_user_agent" "v2rayN" "$preferred_user_agent" "Happ" "Hiddify" "Clash.Meta" "ClashMetaForAndroid"; do
[ -n "$candidate" ] || continue
subscription_auto_user_agent_is_supported "$candidate" || continue
subscription_cache_ucode file-has-exact-line "$output_path" "$candidate" >/dev/null 2>&1 && continue
subscription_auto_user_agent_is_supported "$candidate" "$default_user_agent" || continue
file_has_exact_line "$output_path" "$candidate" && continue
printf '%s\n' "$candidate" >> "$output_path" || return 1
done
}
file_has_exact_line() {
local path="$1"
local needle="$2"
local line
[ -f "$path" ] || return 1
while IFS= read -r line || [ -n "$line" ]; do
[ "$line" = "$needle" ] && return 0
done < "$path"
return 1
}
subscription_cache_is_usable() {
local subscription_json_path="$1"
@@ -851,16 +867,6 @@ download_subscription_into_cache() {
continue
fi
if ! validate_subscription_file "$normalized_tmpfile"; then
[ -n "$metadata_output_path" ] && rm -f "$metadata_output_path"
if [ -n "$subscription_user_agent" ]; then
log "Normalized subscription for rule '$section' is invalid with configured User-Agent" "error"
else
log "Normalized subscription for rule '$section' is invalid with User-Agent '$effective_user_agent'; trying next fallback candidate" "warn"
fi
continue
fi
if ! subscription_config_is_current "$section" "$subscription_url" "$subscription_user_agent"; then
log "Subscription source settings changed while updating rule '$section'; discarding superseded download" "warn"
[ -n "$metadata_output_path" ] && rm -f "$metadata_output_path"
@@ -878,7 +884,7 @@ download_subscription_into_cache() {
rm -f "$raw_tmpfile" "$headers_tmpfile" "$normalized_tmpfile" "$metadata_tmpfile" "$user_agents_tmpfile"
printf '%s' "$subscription_url" > "$subscription_url_cache_path"
printf '%s' "$effective_user_agent" > "$subscription_user_agent_cache_path"
persist_subscription_cache "$cache_section" "$subscription_json_path" "$subscription_url" "$effective_user_agent" ||
persist_subscription_cache "$cache_section" "$subscription_json_path" "$subscription_url" "$effective_user_agent" 1 ||
log "Failed to persist last working subscription cache for source '$cache_section'" "warn"
log "Subscription for rule '$section' is unchanged" "info"
return 2
@@ -895,7 +901,7 @@ download_subscription_into_cache() {
rm -f "$raw_tmpfile" "$headers_tmpfile" "$metadata_tmpfile" "$user_agents_tmpfile"
printf '%s' "$subscription_url" > "$subscription_url_cache_path"
printf '%s' "$effective_user_agent" > "$subscription_user_agent_cache_path"
persist_subscription_cache "$cache_section" "$subscription_json_path" "$subscription_url" "$effective_user_agent" ||
persist_subscription_cache "$cache_section" "$subscription_json_path" "$subscription_url" "$effective_user_agent" 1 ||
log "Failed to persist last working subscription cache for source '$cache_section'" "warn"
log "Subscription runtime outbounds for rule '$section' are unchanged" "info"
return 2
@@ -910,7 +916,7 @@ download_subscription_into_cache() {
rm -f "$raw_tmpfile" "$headers_tmpfile" "$metadata_tmpfile" "$user_agents_tmpfile"
printf '%s' "$subscription_url" > "$subscription_url_cache_path"
printf '%s' "$effective_user_agent" > "$subscription_user_agent_cache_path"
persist_subscription_cache "$cache_section" "$subscription_json_path" "$subscription_url" "$effective_user_agent" ||
persist_subscription_cache "$cache_section" "$subscription_json_path" "$subscription_url" "$effective_user_agent" 1 ||
log "Failed to persist last working subscription cache for source '$cache_section'" "warn"
return 0
done < "$user_agents_tmpfile"
+1 -13
View File
@@ -486,19 +486,7 @@ ui_runtime_status_text() {
}
ui_runtime_active_service_action() {
local state_file action
for state_file in "$PODKOP_UI_SERVICE_ACTION_DIR"/*.json; do
[ -f "$state_file" ] || continue
[ "$(ui_runtime_json_field "$state_file" running false)" = "true" ] || continue
action="$(ui_runtime_json_field "$state_file" action "")"
[ -n "$action" ] && {
printf '%s\n' "$action"
return 0
}
done
return 1
ui_runtime_ucode active-service-action "$PODKOP_UI_SERVICE_ACTION_DIR" 2>/dev/null
}
ui_runtime_write_ui_state() {
+14
View File
@@ -234,6 +234,18 @@ function json_file_field(path, key, fallback) {
print(as_string(fallback), "\n");
}
function active_service_action(dir) {
for (let path in fs.glob(as_string(dir) + "/*.json")) {
let value = read_json_file(path);
if (type(value) == "object" && value.running === true && as_string(value.action) != "") {
print(as_string(value.action), "\n");
return;
}
}
exit(1);
}
let mode = ARGV[0] || "";
if (mode == "ui-state-json")
@@ -254,6 +266,8 @@ else if (mode == "ack-action-state")
ack_action_state(ARGV[1], ARGV[2]);
else if (mode == "json-file-field")
json_file_field(ARGV[1], ARGV[2], ARGV[3]);
else if (mode == "active-service-action")
active_service_action(ARGV[1]);
else {
warn("Usage: ui_runtime.uc <operation> ...\n");
exit(1);