#!/usr/bin/env ucode let common = require("core.common"); let uci_core = require("core.uci"); let as_string = common.as_string; let object_or_empty = common.object_or_empty; let bool_value_common = common.bool_value; let remove_file = common.remove_file; function bool_value(value, fallback) { if (value == null || value == "") return !!fallback; return bool_value_common(value); } const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon"; const ITEM_TYPES = [ "subscription_url", "section_interface", "urltest", "priority_group", "priority_level" ]; let item_sections = null; function raw_option(section, key) { return object_or_empty(section)[key]; } function option(section, key, fallback) { return common.option(section, key, fallback); } function bool_option(section, key, fallback) { let val = raw_option(section, key); if (val == null) return !!fallback; return bool_value(val); } function list_value(section, key) { let value = raw_option(section, key); if (value == null) return []; if (type(value) == "array") return value; value = as_string(value); return value == "" ? [] : [ value ]; } function whitespace_list_value(section, key) { let value = raw_option(section, key); if (type(value) == "array") return value; return common.list_option(section, key); } function section_name(section) { return option(section, ".name", ""); } function empty_item_sections() { let result = {}; for (let type_name in ITEM_TYPES) { result[type_name] = { by_name: {}, list: [] }; } return result; } function add_item_section(index, type_name, section) { section = object_or_empty(section); let name = section_name(section); if (name == "") return; if (index[type_name] == null) index[type_name] = { by_name: {}, list: [] }; index[type_name].by_name[name] = section; push(index[type_name].list, section); } function item_index_from_cursor(cursor, config_name) { let index = empty_item_sections(); if (cursor == null) return index; config_name = as_string(config_name || CONFIG_NAME); for (let type_name in ITEM_TYPES) { try { cursor.foreach(config_name, type_name, function(section) { add_item_section(index, type_name, section); }); } catch (e) { // An absent config or an unknown section type throws here. The // index simply ends up without entries of that type, which is the // same state as a config that declares none; uci reports the parse // error itself to the system log. } } return index; } function fixture_section_list(data, type_name) { data = object_or_empty(data); let value = data[type_name]; if (type(value) == "array") return value; if (type(value) == "object") return [ value ]; let plural = data[type_name + "s"]; return type(plural) == "array" ? plural : []; } function item_index_from_data(data) { let index = empty_item_sections(); for (let type_name in ITEM_TYPES) for (let section in fixture_section_list(data, type_name)) add_item_section(index, type_name, section); return index; } function item_index_from_uci() { let index = empty_item_sections(); for (let type_name in ITEM_TYPES) for (let section in uci_core.section_objects(CONFIG_NAME, type_name)) add_item_section(index, type_name, section); return index; } function set_item_sections(index) { item_sections = type(index) == "object" ? index : empty_item_sections(); } function set_item_sections_from_cursor(cursor, config_name) { set_item_sections(item_index_from_cursor(cursor, config_name)); } function set_item_sections_from_data(data) { set_item_sections(item_index_from_data(data)); } function get_item_sections() { if (item_sections == null) item_sections = item_index_from_uci(); return item_sections; } function child_section(type_name, item_id) { return object_or_empty(object_or_empty(object_or_empty(get_item_sections())[type_name]).by_name)[as_string(item_id)]; } function owned_child_section(parent, type_name, item_id) { let child = child_section(type_name, item_id); if (type(child) != "object") return null; return option(child, "section", "") == section_name(parent) ? child : null; } function child_items(parent, type_name) { let result = []; for (let child in object_or_empty(object_or_empty(get_item_sections())[type_name]).list || []) if (option(child, "section", "") == section_name(parent)) push(result, child); return result; } function child_items_by_owner(type_name, owner_key, owner_value) { owner_value = as_string(owner_value); let result = []; for (let child in object_or_empty(object_or_empty(get_item_sections())[type_name]).list || []) if (option(child, owner_key, "") == owner_value) push(result, child); return result; } function child_item_by_value(parent, type_name, value_key, value) { value = as_string(value); for (let child in child_items(parent, type_name)) if (option(child, value_key, "") == value) return child; return null; } function child_option(child, key, fallback) { if (type(child) != "object") return as_string(fallback); return option(child, key, fallback); } function child_option_alias(child, keys, fallback) { if (type(child) != "object") return as_string(fallback); for (let key in keys) { let value = raw_option(child, key); if (value != null) return as_string(value); } return as_string(fallback); } function child_bool(child, key, fallback) { if (type(child) != "object") return !!fallback; return bool_option(child, key, fallback); } function child_list(child, key, fallback) { if (type(child) != "object") return fallback == null ? [] : fallback; let value = raw_option(child, key); if (value == null) value = fallback; if (value == null) return []; if (type(value) == "array") return value; value = as_string(value); return value == "" ? [] : [ value ]; } function child_list_alias(child, keys, fallback) { if (type(child) == "object") { for (let key in keys) { let value = raw_option(child, key); if (value != null) return child_list(child, key, []); } } return fallback == null ? [] : fallback; } function child_values(parent, type_name, value_key, legacy_key) { let items = child_items(parent, type_name); if (length(items) > 0) { let result = []; for (let child in items) { let value = option(child, value_key, ""); if (value != "") push(result, value); } return result; } return legacy_key == "" ? [] : whitespace_list_value(parent, legacy_key); } function settings_map(section, key) { let value = option(section, key, ""); if (value == "") return {}; try { value = json(value); } catch (e) { return {}; } return object_or_empty(value); } function item_settings(section, key, item) { return object_or_empty(settings_map(section, key)[as_string(item)]); } function item_option(section, key, item, option_name, fallback) { let value = item_settings(section, key, item)[option_name]; if (value == null) return as_string(fallback); return as_string(value); } function item_bool(section, key, item, option_name, fallback) { return bool_value(item_settings(section, key, item)[option_name], fallback); } function item_list(section, key, item, option_name, fallback) { let value = item_settings(section, key, item)[option_name]; if (value == null) value = fallback; if (value == null) return []; if (type(value) == "array") return value; value = as_string(value); return value == "" ? [] : [ value ]; } function is_legacy_connection_action(action) { action = as_string(action); return action == "proxy" || action == "outbound" || action == "vpn"; } function is_connections_action(action) { action = as_string(action); return action == "connection" || is_legacy_connection_action(action); } function is_wdtt_action(action) { return as_string(action) == "wdtt"; } function is_olcrtc_action(action) { return as_string(action) == "olcrtc"; } function wdtt_sections() { let result = []; let sections = uci_core.section_objects(CONFIG_NAME, "section"); for (let section in sections) { if (bool_option(section, "enabled", true) && option(section, "action", "") == "wdtt") push(result, section); } return result; } function olcrtc_sections() { let result = []; let sections = uci_core.section_objects(CONFIG_NAME, "section"); for (let section in sections) { if (bool_option(section, "enabled", true) && option(section, "action", "") == "olcrtc") push(result, section); } return result; } function normalize_action(action) { action = as_string(action); return is_connections_action(action) ? "connection" : action; } function action(section) { return normalize_action(option(section, "action", "")); } function connection_urls(section) { let links = whitespace_list_value(section, "selector_proxy_links"); let text = option(section, "selector_proxy_links_text", ""); if (text != "") { for (let line in split(text, /[\r\n]+/)) { line = trim(line); if (line != "" && match(line, /^[^#\s]/)) push(links, line); } } return links; } function urltest_text_links(section) { let text = option(section, "urltest_proxy_links_text", ""); if (text == "") return []; let links = []; for (let line in split(text, /[\r\n]+/)) { line = trim(line); if (line != "" && match(line, /^[^#\s]/)) push(links, line); } return links; } function subscription_urls(section) { return child_values(section, "subscription_url", "url", "subscription_urls"); } function interfaces(section) { let items = child_values(section, "section_interface", "name", ""); if (length(items) > 0) return items; let result = list_value(section, "interfaces"); if (length(result) == 0) { let value = option(section, "interface", ""); if (value != "") push(result, value); } return result; } function outbound_jsons(section) { let result = list_value(section, "outbound_jsons"); if (length(result) == 0) { let value = option(section, "outbound_json", ""); if (value != "") push(result, value); } return result; } function urltests(section) { let result = []; for (let child in child_items(section, "urltest")) { let id = section_name(child); if (id != "") push(result, id); } if (length(result) > 0) return result; return bool_option(section, "urltest_enabled", false) ? [ "urltest" ] : []; } function priority_groups(section) { let result = []; for (let child in child_items(section, "priority_group")) { let id = section_name(child); if (id != "") push(result, id); } return result; } function dashboard_filter_mode(section) { return option(section, "dashboard_filter_mode", "disabled") || "disabled"; } function dashboard_detect_server_country(section) { return option(section, "dashboard_detect_server_country", "flag_emoji") || "flag_emoji"; } function dashboard_include_countries(section) { return list_value(section, "dashboard_include_countries"); } function dashboard_include_outbounds(section) { return list_value(section, "dashboard_include_outbounds"); } function dashboard_include_regex(section) { return list_value(section, "dashboard_include_regex"); } function dashboard_include_proxy_parameters(section) { return bool_option(section, "dashboard_include_proxy_parameters", false); } function dashboard_include_protocols(section) { return list_value(section, "dashboard_include_protocols"); } function dashboard_include_transports(section) { return list_value(section, "dashboard_include_transports"); } function dashboard_include_securities(section) { return list_value(section, "dashboard_include_securities"); } function dashboard_include_groups(section) { return list_value(section, "dashboard_include_groups"); } function dashboard_exclude_countries(section) { return list_value(section, "dashboard_exclude_countries"); } function dashboard_exclude_outbounds(section) { return list_value(section, "dashboard_exclude_outbounds"); } function dashboard_exclude_regex(section) { return list_value(section, "dashboard_exclude_regex"); } function dashboard_exclude_proxy_parameters(section) { return bool_option(section, "dashboard_exclude_proxy_parameters", false); } function dashboard_exclude_protocols(section) { return list_value(section, "dashboard_exclude_protocols"); } function dashboard_exclude_transports(section) { return list_value(section, "dashboard_exclude_transports"); } function dashboard_exclude_securities(section) { return list_value(section, "dashboard_exclude_securities"); } function dashboard_exclude_groups(section) { return list_value(section, "dashboard_exclude_groups"); } function priority_group_child(section, value) { return owned_child_section(section, "priority_group", value); } function priority_group_settings(section, value) { let child = priority_group_child(section, value); return child != null ? child : {}; } function priority_level_child(group_id, value) { let child = child_section("priority_level", value); if (type(child) != "object") return null; return option(child, "group", "") == as_string(group_id) ? child : null; } function priority_level_sort(left, right) { let left_order = int(option(left, "order", "0"), 10); let right_order = int(option(right, "order", "0"), 10); if (left_order != right_order) return left_order < right_order ? -1 : 1; let left_name = section_name(left); let right_name = section_name(right); return left_name == right_name ? 0 : (left_name < right_name ? -1 : 1); } function priority_level_items(group_id) { let result = child_items_by_owner("priority_level", "group", group_id); sort(result, priority_level_sort); return result; } function priority_levels(group_id) { let result = []; for (let child in priority_level_items(group_id)) { let id = section_name(child); if (id != "") push(result, id); } return result; } function community_lists(section) { return whitespace_list_value(section, "community_lists"); } function rule_sets(section) { return whitespace_list_value(section, "rule_set"); } function rule_sets_with_subnets(section) { return whitespace_list_value(section, "rule_set_with_subnets"); } function list_option_value_from_array(values) { return join(" ", values || []); } function community_lists_value(section) { return list_option_value_from_array(community_lists(section)); } function rule_sets_value(section) { return list_option_value_from_array(rule_sets(section)); } function combined_domain_condition_text(section) { if (type(raw_option(section, "domain")) != "array") { let value = option(section, "domain", ""); if (value != "") return value; } return option(section, "domain_suffix_text", ""); } function rule_config_or_null() { try { return require("config.rule"); } catch (e) { return null; } } function rule_condition_csv(section, key, kind) { let rule_config = rule_config_or_null(); if (!rule_config) return ""; return rule_config.rule_condition_csv_value( key, kind, option(section, key + "_text_mode", "0"), option(section, "conditions_text_mode", "0"), option(section, key + "_text", ""), option(section, key, ""), combined_domain_condition_text(section), option(section, "domain_suffix", "") ); } function has_dns_matchers(section) { return rule_condition_csv(section, "domain", "domains") != "" || rule_condition_csv(section, "domain_suffix", "domains") != "" || rule_condition_csv(section, "domain_keyword", "generic") != "" || rule_condition_csv(section, "domain_regex", "generic") != "" || length(community_lists(section)) > 0 || length(rule_sets(section)) > 0 || length(rule_sets_with_subnets(section)) > 0 || option(section, "domain_ip_lists", "") != ""; } const DSCP_ALIASES = { "cs0": 0, "cs1": 8, "cs2": 16, "cs3": 24, "cs4": 32, "cs5": 40, "cs6": 48, "cs7": 56, "ef": 46, "af11": 10, "af12": 12, "af13": 14, "af21": 18, "af22": 20, "af23": 22, "af31": 26, "af32": 28, "af33": 30, "af41": 34, "af42": 36, "af43": 38 }; function parse_dscp_value(item) { item = lc(trim(as_string(item))); if (item == "") return null; if (DSCP_ALIASES[item] != null) return DSCP_ALIASES[item]; let num = null; if (substr(item, 0, 2) == "0x") num = int(item, 16); else num = int(item, 10); if (num != null && num >= 0 && num <= 63) return num; return null; } function dscp_list(section) { let raw = whitespace_list_value(section, "dscp"); let result = []; for (let item in raw) { let parsed = parse_dscp_value(item); if (parsed != null) push(result, parsed); } return result; } function dscp_value(section) { return list_option_value_from_array(dscp_list(section)); } function routed_dns_enabled(section) { return bool_option(section, "routed_dns_enabled", false); } function routed_dns_type(section) { let t = option(section, "routed_dns_type", "udp"); if (!contains(["udp", "dot", "doh", "doq"], t)) t = "udp"; return t; } function routed_dns_servers(section) { let servers = list_option(section, "routed_dns_server"); if (length(servers) == 0) { let single = option(section, "routed_dns_server", ""); servers = [single]; } return servers; } function contains(arr, val) { if (type(arr) != "array") return false; for (let item in arr) { if (item == val) return true; } return false; } function geoip_country_list(section) { let raw = whitespace_list_value(section, "geoip_country"); let result = []; for (let item in raw) { item = lc(trim(as_string(item))); if (item == "" || item == "all") continue; if (item == "non-ru") { if (!contains(result, "ru")) push(result, "ru"); continue; } if (match(item, /^[a-z]{2}$/) != null && !contains(result, item)) push(result, item); } return result; } function geoip_country_mode(section) { let raw = option(section, "geoip_country", "all"); let mode = lc(trim(option(section, "geoip_mode", ""))); if (mode == "exclude" || mode == "include") return mode; if (raw == "non-ru") return "exclude"; return "include"; } function geoip_country_value(section) { return list_option_value_from_array(geoip_country_list(section)); } function rule_sets_with_subnets_value(section) { return list_option_value_from_array(rule_sets_with_subnets(section)); } function has_connection_sources(section) { return length(connection_urls(section)) > 0 || length(subscription_urls(section)) > 0 || length(interfaces(section)) > 0 || length(outbound_jsons(section)) > 0; } function subscription_url_settings(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); return child != null ? child : item_settings(section, "subscription_url_settings", value); } function interface_settings(section, value) { let child = child_item_by_value(section, "section_interface", "name", value); return child != null ? child : item_settings(section, "interface_settings", value); } function interface_domain_resolver_enabled(section, value) { let child = child_item_by_value(section, "section_interface", "name", value); if (child != null) return child_bool(child, "domain_resolver_enabled", false); return item_bool(section, "interface_settings", value, "domain_resolver_enabled", bool_option(section, "domain_resolver_enabled", false)); } function interface_domain_resolver_dns_type(section, value) { let child = child_item_by_value(section, "section_interface", "name", value); if (child != null) return child_option(child, "domain_resolver_dns_type", "udp"); return item_option(section, "interface_settings", value, "domain_resolver_dns_type", option(section, "domain_resolver_dns_type", "udp") || "udp"); } function interface_domain_resolver_dns_server(section, value) { let child = child_item_by_value(section, "section_interface", "name", value); if (child != null) return child_option(child, "domain_resolver_dns_server", "8.8.8.8"); return item_option(section, "interface_settings", value, "domain_resolver_dns_server", option(section, "domain_resolver_dns_server", "8.8.8.8") || "8.8.8.8"); } function urltest_child(section, value) { let child = owned_child_section(section, "urltest", value); if (child != null) return child; return child_item_by_value(section, "urltest", "id", value); } function urltest_settings(section, value) { let child = urltest_child(section, value); return child != null ? child : item_settings(section, "urltest_settings", value); } function subscription_update_enabled(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_bool(child, "subscription_update_enabled", true); return item_bool(section, "subscription_url_settings", value, "subscription_update_enabled", bool_option(section, "subscription_update_enabled", true)); } function subscription_update_interval(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_option(child, "subscription_update_interval", "1h"); return item_option(section, "subscription_url_settings", value, "subscription_update_interval", option(section, "subscription_update_interval", "1h") || "1h"); } function subscription_dashboard_metadata_enabled(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_bool(child, "show_dashboard_metadata", true); return item_bool(section, "subscription_url_settings", value, "show_dashboard_metadata", true); } function subscription_auto_user_agent(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) { if (raw_option(child, "auto_user_agent") != null) return child_bool(child, "auto_user_agent", true); return child_option(child, "user_agent", "") == ""; } let settings = item_settings(section, "subscription_url_settings", value); if (raw_option(settings, "auto_user_agent") != null) return item_bool(section, "subscription_url_settings", value, "auto_user_agent", true); return item_option(section, "subscription_url_settings", value, "user_agent", "") == ""; } function subscription_auto_hwid(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) { if (raw_option(child, "auto_hwid") != null) return child_bool(child, "auto_hwid", true); return child_option(child, "hwid", "") == ""; } let settings = item_settings(section, "subscription_url_settings", value); if (raw_option(settings, "auto_hwid") != null) return item_bool(section, "subscription_url_settings", value, "auto_hwid", true); return item_option(section, "subscription_url_settings", value, "hwid", "") == ""; } function subscription_include_urltest_groups(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_bool(child, "include_urltest_groups", true); return item_bool(section, "subscription_url_settings", value, "include_urltest_groups", true); } function subscription_hide_urltest_group_outbounds(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_bool(child, "hide_urltest_group_outbounds", true); return item_bool(section, "subscription_url_settings", value, "hide_urltest_group_outbounds", true); } function subscription_hide_detour_outbounds(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_bool(child, "hide_detour_outbounds", true); return item_bool(section, "subscription_url_settings", value, "hide_detour_outbounds", true); } function subscription_prefix_nodes(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_bool(child, "prefix_nodes", false); return item_bool(section, "subscription_url_settings", value, "prefix_nodes", false); } function subscription_node_prefix(section, value) { if (!subscription_prefix_nodes(section, value)) return ""; let child = child_item_by_value(section, "subscription_url", "url", value); let prefix = child != null ? child_option(child, "node_prefix", "") : item_option(section, "subscription_url_settings", value, "node_prefix", ""); return trim(as_string(prefix)); } function subscription_user_agent(section, value) { if (subscription_auto_user_agent(section, value)) return ""; let child = child_item_by_value(section, "subscription_url", "url", value); let configured = child != null ? child_option(child, "user_agent", "") : item_option(section, "subscription_url_settings", value, "user_agent", ""); return configured != "" ? configured : "sing-box"; } function subscription_hwid(section, value) { if (subscription_auto_hwid(section, value)) return ""; let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_option(child, "hwid", ""); return item_option(section, "subscription_url_settings", value, "hwid", ""); } function subscription_custom_device_headers(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_bool(child, "custom_device_headers", false); return item_bool(section, "subscription_url_settings", value, "custom_device_headers", false); } function subscription_device_header(section, value, name) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return child_option(child, name, ""); return item_option(section, "subscription_url_settings", value, name, ""); } function subscription_device_os(section, value) { return subscription_device_header(section, value, "device_os"); } function subscription_ver_os(section, value) { return subscription_device_header(section, value, "ver_os"); } function subscription_device_model(section, value) { return subscription_device_header(section, value, "device_model"); } function subscription_device_locale(section, value) { return subscription_device_header(section, value, "device_locale"); } function subscription_app_version(section, value) { return subscription_device_header(section, value, "app_version"); } function subscription_accept_language(section, value) { return subscription_device_header(section, value, "accept_language"); } function subscription_device_headers(section, value) { return { enabled: subscription_custom_device_headers(section, value), device_os: subscription_device_os(section, value), ver_os: subscription_ver_os(section, value), device_model: subscription_device_model(section, value), device_locale: subscription_device_locale(section, value), app_version: subscription_app_version(section, value), accept_language: subscription_accept_language(section, value) }; } function device_headers_signature(headers) { headers = object_or_empty(headers); if (!headers.enabled) return ""; return sprintf("%J", { enabled: "1", device_os: as_string(headers.device_os), ver_os: as_string(headers.ver_os), device_model: as_string(headers.device_model), device_locale: as_string(headers.device_locale), app_version: as_string(headers.app_version), accept_language: as_string(headers.accept_language) }); } function subscription_device_headers_signature(section, value) { return device_headers_signature(subscription_device_headers(section, value)); } function subscription_download_section(section, value) { let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) { if (!child_bool(child, "download_via_proxy_enabled", false)) return ""; return child_option(child, "download_via_proxy_section", ""); } if (!item_bool(section, "subscription_url_settings", value, "download_via_proxy_enabled", false)) return ""; return item_option(section, "subscription_url_settings", value, "download_via_proxy_section", ""); } function subscription_insecure(section, value) { if (section != null && section.subscription_insecure != null) return bool_option(section, "subscription_insecure", false) ? "1" : "0"; let child = child_item_by_value(section, "subscription_url", "url", value); if (child != null) return bool_option(child, "subscription_insecure", false) ? "1" : "0"; return item_bool(section, "subscription_url_settings", value, "subscription_insecure", false) ? "1" : "0"; } function urltest_check_interval(section, value) { let child = urltest_child(section, value); if (child != null) return child_option_alias(child, [ "check_interval", "urltest_check_interval" ], "3m"); return item_option(section, "urltest_settings", value, "urltest_check_interval", option(section, "urltest_check_interval", "3m") || "3m"); } function urltest_tolerance(section, value) { let child = urltest_child(section, value); if (child != null) return child_option_alias(child, [ "tolerance", "urltest_tolerance" ], "50"); return item_option(section, "urltest_settings", value, "urltest_tolerance", option(section, "urltest_tolerance", "50") || "50"); } function urltest_testing_url(section, value) { let child = urltest_child(section, value); if (child != null) return child_option_alias(child, [ "testing_url", "urltest_testing_url" ], "https://www.gstatic.com/generate_204"); return item_option(section, "urltest_settings", value, "urltest_testing_url", option(section, "urltest_testing_url", "https://www.gstatic.com/generate_204") || "https://www.gstatic.com/generate_204"); } function urltest_display_name(section, value) { let fallback = as_string(value); if (fallback == "urltest" && length(child_items(section, "urltest")) == 0) fallback = "Fastest"; let child = urltest_child(section, value); if (child != null) return child_option_alias(child, [ "name", "display_name" ], fallback); return item_option(section, "urltest_settings", value, "display_name", fallback); } function urltest_idle_timeout(section, value) { let child = urltest_child(section, value); if (child != null) return child_option(child, "idle_timeout", ""); return item_option(section, "urltest_settings", value, "idle_timeout", ""); } function urltest_interrupt_exist_connections(section, value) { let child = urltest_child(section, value); if (child != null) return child_bool(child, "interrupt_exist_connections", true); return item_bool(section, "urltest_settings", value, "interrupt_exist_connections", true); } function urltest_filter_mode(section, value) { let child = urltest_child(section, value); if (child != null) return child_option_alias(child, [ "filter_mode", "urltest_filter_mode" ], "disabled"); return item_option(section, "urltest_settings", value, "urltest_filter_mode", option(section, "urltest_filter_mode", "disabled") || "disabled"); } function urltest_detect_server_country(section, value) { let child = urltest_child(section, value); if (child != null) return child_option(child, "detect_server_country", "flag_emoji"); return item_option(section, "urltest_settings", value, "detect_server_country", option(section, "detect_server_country", "flag_emoji") || "flag_emoji"); } function urltest_pin_dashboard(section, value) { let child = urltest_child(section, value); if (child != null) return child_bool(child, "pin_dashboard", true); return item_bool(section, "urltest_settings", value, "pin_dashboard", true); } function urltest_include_countries(section, value) { let child = urltest_child(section, value); if (child != null) return child_list_alias(child, [ "include_countries", "urltest_include_countries" ], []); return item_list(section, "urltest_settings", value, "urltest_include_countries", list_value(section, "urltest_include_countries")); } function urltest_include_outbounds(section, value) { let child = urltest_child(section, value); if (child != null) return child_list_alias(child, [ "include_outbounds", "urltest_include_outbounds" ], []); return item_list(section, "urltest_settings", value, "urltest_include_outbounds", list_value(section, "urltest_include_outbounds")); } function urltest_include_regex(section, value) { let child = urltest_child(section, value); if (child != null) return child_list_alias(child, [ "include_regex", "urltest_include_regex" ], []); return item_list(section, "urltest_settings", value, "urltest_include_regex", list_value(section, "urltest_include_regex")); } function urltest_include_proxy_parameters(section, value) { let child = urltest_child(section, value); return child != null ? child_bool(child, "include_proxy_parameters", false) : false; } function urltest_include_protocols(section, value) { let child = urltest_child(section, value); return child != null ? child_list(child, "include_protocols", []) : []; } function urltest_include_transports(section, value) { let child = urltest_child(section, value); return child != null ? child_list(child, "include_transports", []) : []; } function urltest_include_securities(section, value) { let child = urltest_child(section, value); return child != null ? child_list(child, "include_securities", []) : []; } function urltest_exclude_countries(section, value) { let child = urltest_child(section, value); if (child != null) return child_list_alias(child, [ "exclude_countries", "urltest_exclude_countries" ], []); return item_list(section, "urltest_settings", value, "urltest_exclude_countries", list_value(section, "urltest_exclude_countries")); } function urltest_exclude_outbounds(section, value) { let child = urltest_child(section, value); if (child != null) return child_list_alias(child, [ "exclude_outbounds", "urltest_exclude_outbounds" ], []); return item_list(section, "urltest_settings", value, "urltest_exclude_outbounds", list_value(section, "urltest_exclude_outbounds")); } function urltest_exclude_regex(section, value) { let child = urltest_child(section, value); if (child != null) return child_list_alias(child, [ "exclude_regex", "urltest_exclude_regex" ], []); return item_list(section, "urltest_settings", value, "urltest_exclude_regex", list_value(section, "urltest_exclude_regex")); } function urltest_exclude_proxy_parameters(section, value) { let child = urltest_child(section, value); return child != null ? child_bool(child, "exclude_proxy_parameters", false) : false; } function urltest_exclude_protocols(section, value) { let child = urltest_child(section, value); return child != null ? child_list(child, "exclude_protocols", []) : []; } function urltest_exclude_transports(section, value) { let child = urltest_child(section, value); return child != null ? child_list(child, "exclude_transports", []) : []; } function urltest_exclude_securities(section, value) { let child = urltest_child(section, value); return child != null ? child_list(child, "exclude_securities", []) : []; } function priority_group_display_name(section, value) { let child = priority_group_child(section, value); return child != null ? child_option(child, "name", as_string(value)) : as_string(value); } function priority_group_health_url(section, value) { let child = priority_group_child(section, value); return child != null ? child_option(child, "health_url", "https://www.gstatic.com/generate_204") : "https://www.gstatic.com/generate_204"; } function priority_group_active_check_interval(section, value) { let child = priority_group_child(section, value); return child != null ? child_option(child, "active_check_interval", "5s") : "5s"; } function priority_group_check_timeout(section, value) { let child = priority_group_child(section, value); return child != null ? child_option(child, "check_timeout", "2s") : "2s"; } function priority_group_recovery_check_interval(section, value) { let child = priority_group_child(section, value); return child != null ? child_option(child, "recovery_check_interval", "15s") : "15s"; } function priority_group_pick_fastest(section, value) { let child = priority_group_child(section, value); return child != null ? child_bool(child, "pick_fastest", false) : false; } function priority_group_switch_to_faster_same_priority(section, value) { let child = priority_group_child(section, value); return child != null ? child_bool(child, "switch_to_faster_same_priority", false) : false; } function priority_group_fastest_check_interval(section, value) { let child = priority_group_child(section, value); return child != null ? child_option(child, "fastest_check_interval", "3m") : "3m"; } function priority_group_interrupt_exist_connections(section, value) { let child = priority_group_child(section, value); return child != null ? child_bool(child, "interrupt_exist_connections", true) : true; } function priority_group_pin_dashboard(section, value) { let child = priority_group_child(section, value); return child != null ? child_bool(child, "pin_dashboard", true) : true; } function priority_level_display_name(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_option(child, "name", as_string(value)) : as_string(value); } function priority_level_order(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_option(child, "order", "0") : "0"; } function priority_level_direct(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_bool(child, "direct", false) : false; } function priority_level_filter_mode(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_option(child, "filter_mode", "include") : "include"; } function priority_level_include_countries(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list_alias(child, [ "include_countries", "country" ], []) : []; } function priority_level_include_outbounds(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list_alias(child, [ "include_outbounds", "server_name" ], []) : []; } function priority_level_include_regex(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list_alias(child, [ "include_regex", "regex" ], []) : []; } function priority_level_include_proxy_parameters(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_bool(child, "include_proxy_parameters", false) : false; } function priority_level_include_protocols(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "include_protocols", []) : []; } function priority_level_include_transports(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "include_transports", []) : []; } function priority_level_include_securities(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "include_securities", []) : []; } function priority_level_exclude_countries(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "exclude_countries", []) : []; } function priority_level_exclude_outbounds(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "exclude_outbounds", []) : []; } function priority_level_exclude_regex(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "exclude_regex", []) : []; } function priority_level_exclude_proxy_parameters(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_bool(child, "exclude_proxy_parameters", false) : false; } function priority_level_exclude_protocols(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "exclude_protocols", []) : []; } function priority_level_exclude_transports(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "exclude_transports", []) : []; } function priority_level_exclude_securities(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_list(child, "exclude_securities", []) : []; } function priority_level_countries(group_id, value) { return priority_level_include_countries(group_id, value); } function priority_level_outbounds(group_id, value) { return priority_level_include_outbounds(group_id, value); } function priority_level_regex(group_id, value) { return priority_level_include_regex(group_id, value); } function priority_level_detect_server_country(group_id, value) { let child = priority_level_child(group_id, value); return child != null ? child_option(child, "detect_server_country", "flag_emoji") : "flag_emoji"; } function append_unique(result, seen, value) { value = as_string(value); if (value == "" || seen[value]) return; seen[value] = true; push(result, value); } function subscription_download_targets(sections) { let result = []; let seen = {}; for (let section in sections) { section = object_or_empty(section); let name = option(section, ".name", ""); for (let source in subscription_urls(section)) { let target = subscription_download_section(section, source); if (target != "") append_unique(result, seen, target); } } return result; } function subscription_download_target_port(sections, target, base_port) { target = as_string(target); base_port = int(base_port || 0); let targets = subscription_download_targets(sections); for (let i = 0; i < length(targets); i++) if (targets[i] == target) return base_port + 2 + i; return 0; } // Cascade-delete all child sections (subscription_url, urltest, etc.) that // belong to the given parent section, along with cross-references and disk caches. // Returns the number of children removed. function cascade_delete_section(cursor, config_name, parent_name) { let removed = 0; config_name = as_string(config_name || CONFIG_NAME); parent_name = as_string(parent_name || ""); if (parent_name == "") return 0; // 1. Delete priority_level belonging to priority_group in this section try { let group_names = []; cursor.foreach(config_name, "priority_group", function(section) { if (option(section, "section", "") == parent_name) push(group_names, section_name(section)); }); for (let gname in group_names) { let level_names = []; cursor.foreach(config_name, "priority_level", function(section) { if (option(section, "group", "") == gname || option(section, "section", "") == parent_name) push(level_names, section_name(section)); }); for (let lname in level_names) { cursor.delete(config_name, lname); removed++; } cursor.delete(config_name, gname); removed++; } } catch (e) {} // 2. Delete all other child sections (subscription_url, section_interface, urltest, priority_level) for (let type_name in [ "subscription_url", "section_interface", "urltest", "priority_level" ]) { try { let to_delete = []; cursor.foreach(config_name, type_name, function(section) { if (option(section, "section", "") == parent_name) push(to_delete, section_name(section)); }); for (let name in to_delete) { cursor.delete(config_name, name); removed++; } } catch (e) {} } // 3. Clean references in settings try { let smart_detect = cursor.get(config_name, "settings", "smart_detect_sections"); if (type(smart_detect) == "array") { let filtered = []; for (let sec in smart_detect) { if (as_string(sec) != parent_name) push(filtered, sec); } if (length(filtered) != length(smart_detect)) cursor.set(config_name, "settings", "smart_detect_sections", filtered); } else if (as_string(smart_detect) == parent_name) { cursor.delete(config_name, "settings", "smart_detect_sections"); } for (let opt in [ "dns_detour_section", "download_lists_via_proxy_section", "download_components_via_proxy_section", "warp_proxy_section" ]) { if (as_string(cursor.get(config_name, "settings", opt)) == parent_name) cursor.delete(config_name, "settings", opt); } } catch (e) {} // 4. Clean references in other sections try { cursor.foreach(config_name, "section", function(sec) { let sname = section_name(sec); if (sname == parent_name) return; if (option(sec, "outbound_detour_section", "") == parent_name) { cursor.delete(config_name, sname, "outbound_detour_section"); cursor.set(config_name, sname, "outbound_detour_enabled", "0"); } if (option(sec, "dns_detour_section", "") == parent_name) { cursor.delete(config_name, sname, "dns_detour_section"); cursor.set(config_name, sname, "dns_detour_enabled", "0"); } }); } catch (e) {} // 5. Clean references in servers try { cursor.foreach(config_name, "server", function(srv) { let sname = section_name(srv); if (option(srv, "routing_section", "") == parent_name) { cursor.delete(config_name, sname, "routing_section"); if (option(srv, "routing_mode", "") == "section") cursor.set(config_name, sname, "routing_mode", "rules"); } }); } catch (e) {} // 6. Delete section cache files try { remove_file("/var/run/tachyon/section-cache/" + parent_name + ".json"); remove_file("/etc/tachyon/subscription-cache/" + parent_name + ".json"); remove_file("/etc/tachyon/subscription-cache/" + parent_name + ".yaml"); remove_file("/etc/tachyon/subscription-cache/" + parent_name + ".txt"); } catch (e) {} return removed; } function cli_delete_section(sec_name) { sec_name = trim(as_string(sec_name)); if (sec_name == "") { warn("Usage: tachyon delete_section \n"); return 1; } let c = uci_core.cursor(); c.load(CONFIG_NAME); let s = c.get_all(CONFIG_NAME, sec_name); if (!s) { warn("Section '" + sec_name + "' not found\n"); return 1; } cascade_delete_section(c, CONFIG_NAME, sec_name); c.delete(CONFIG_NAME, sec_name); c.commit(CONFIG_NAME); print(sprintf("{\"success\":true,\"deleted\":%J}\n", sec_name)); return 0; } let cli_mode = as_string(ARGV[0]); if (cli_mode == "delete-section") exit(cli_delete_section(ARGV[1])); return { option, bool_option, list_value, whitespace_list_value, cascade_delete_section, set_item_sections, set_item_sections_from_cursor, set_item_sections_from_data, settings_map, item_settings, item_option, item_bool, is_connections_action, normalize_action, action, connection_urls, subscription_urls, interfaces, outbound_jsons, urltests, dashboard_filter_mode, dashboard_detect_server_country, dashboard_include_countries, dashboard_include_outbounds, dashboard_include_regex, dashboard_include_proxy_parameters, dashboard_include_protocols, dashboard_include_transports, dashboard_include_securities, dashboard_include_groups, dashboard_exclude_countries, dashboard_exclude_outbounds, dashboard_exclude_regex, dashboard_exclude_proxy_parameters, dashboard_exclude_protocols, dashboard_exclude_transports, dashboard_exclude_securities, dashboard_exclude_groups, community_lists, rule_sets, rule_sets_with_subnets, dscp_list, dscp_value, community_lists_value, rule_sets_value, rule_sets_with_subnets_value, has_connection_sources, subscription_url_settings, interface_settings, urltest_settings, subscription_update_enabled, subscription_update_interval, subscription_dashboard_metadata_enabled, subscription_auto_user_agent, subscription_auto_hwid, subscription_include_urltest_groups, subscription_hide_urltest_group_outbounds, subscription_hide_detour_outbounds, subscription_prefix_nodes, subscription_node_prefix, subscription_user_agent, subscription_hwid, subscription_custom_device_headers, subscription_device_os, subscription_ver_os, subscription_device_model, subscription_device_locale, subscription_app_version, subscription_accept_language, subscription_device_headers, device_headers_signature, subscription_device_headers_signature, subscription_download_section, subscription_insecure, urltest_text_links, interface_domain_resolver_enabled, interface_domain_resolver_dns_type, interface_domain_resolver_dns_server, urltest_check_interval, urltest_tolerance, urltest_testing_url, urltest_display_name, urltest_idle_timeout, urltest_interrupt_exist_connections, urltest_filter_mode, urltest_detect_server_country, urltest_pin_dashboard, urltest_include_countries, urltest_include_outbounds, urltest_include_regex, urltest_include_proxy_parameters, urltest_include_protocols, urltest_include_transports, urltest_include_securities, urltest_exclude_countries, urltest_exclude_outbounds, urltest_exclude_regex, urltest_exclude_proxy_parameters, urltest_exclude_protocols, urltest_exclude_transports, urltest_exclude_securities, priority_groups, priority_group_settings, priority_levels, priority_group_display_name, priority_group_health_url, priority_group_active_check_interval, priority_group_check_timeout, priority_group_recovery_check_interval, priority_group_pick_fastest, priority_group_switch_to_faster_same_priority, priority_group_fastest_check_interval, priority_group_interrupt_exist_connections, priority_group_pin_dashboard, priority_level_display_name, priority_level_order, priority_level_direct, priority_level_filter_mode, priority_level_include_countries, priority_level_include_outbounds, priority_level_include_regex, priority_level_include_proxy_parameters, priority_level_include_protocols, priority_level_include_transports, priority_level_include_securities, priority_level_exclude_countries, priority_level_exclude_outbounds, priority_level_exclude_regex, priority_level_exclude_proxy_parameters, priority_level_exclude_protocols, priority_level_exclude_transports, priority_level_exclude_securities, priority_level_countries, priority_level_outbounds, priority_level_regex, priority_level_detect_server_country, parse_dscp_value, dscp_list, dscp_value, geoip_country_list, geoip_country_mode, geoip_country_value, subscription_download_targets, subscription_download_target_port, has_dns_matchers, routed_dns_enabled, routed_dns_type, routed_dns_servers, item_index_from_cursor, cli_delete_section, wdtt_sections, olcrtc_sections, is_wdtt_action, is_olcrtc_action };