Optimize Podkop runtime process batching
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user