feat(rulesets): add native plain list (.lst/.txt) download and compilation in rule_set & fix OlcRTC/WDTT status

This commit is contained in:
Dushnilin
2026-09-16 22:41:52 +03:00
parent 5eeaa91864
commit 61c01109ad
16 changed files with 1543 additions and 1269 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+20 -11
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TACHYON\n" "Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-16 19:54+0300\n" "POT-Creation-Date: 2026-09-16 22:41+0300\n"
"PO-Revision-Date: 2026-09-16 19:54+0300\n" "PO-Revision-Date: 2026-09-16 22:41+0300\n"
"Last-Translator: Dushnilin\n" "Last-Translator: Dushnilin\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
@@ -272,11 +272,11 @@ msgstr "Добавьте URL или локальные пути к списка
msgid "Add URLs or local paths to .lst lists containing domains. IP entries are ignored." msgid "Add URLs or local paths to .lst lists containing domains. IP entries are ignored."
msgstr "Добавьте URL или локальные пути к спискам .lst, содержащим домены. IP-записи игнорируются." msgstr "Добавьте URL или локальные пути к спискам .lst, содержащим домены. IP-записи игнорируются."
msgid "Add URLs or local paths to .srs / .json lists. Only domain rules are supported." msgid "Add URLs or local paths to .srs / .json / .lst / .txt lists. Only domain rules are supported."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Поддерживаются только правила доменов." msgstr ""
msgid "Add URLs or local paths to .srs / .json lists. Subnets are ignored by default." msgid "Add URLs or local paths to .srs / .json / .lst / .txt lists. Subnets are ignored by default."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Подсети игнорируются по умолчанию." msgstr ""
msgid "Add URLTest / Priority group" msgid "Add URLTest / Priority group"
msgstr "Добавить группу URLTest / Приоритет" msgstr "Добавить группу URLTest / Приоритет"
@@ -3758,9 +3758,6 @@ msgstr "Закрепить на дашборде"
msgid "Pin P2P isolation to the torrent client's own ports, comma separated. Each entry is \"proto\", \"proto:port\" or \"proto:port-port\" (tcp/udp). Without a port the whole protocol is routed direct." msgid "Pin P2P isolation to the torrent client's own ports, comma separated. Each entry is \"proto\", \"proto:port\" or \"proto:port-port\" (tcp/udp). Without a port the whole protocol is routed direct."
msgstr "Привязка изоляции P2P к портам торрент-клиента через запятую. Каждая запись: \"proto\", \"proto:port\" или \"proto:port-port\" (tcp/udp). Без указания порта весь протокол направляется напрямую." msgstr "Привязка изоляции P2P к портам торрент-клиента через запятую. Каждая запись: \"proto\", \"proto:port\" или \"proto:port-port\" (tcp/udp). Без указания порта весь протокол направляется напрямую."
msgid "Plain .lst / .txt lists belong in \"Domain and IP lists\", not \"Rule sets\""
msgstr ""
msgid "Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers." msgid "Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers."
msgstr "Обычные UDP DNS-серверы, используемые как резерв при недоступности всех основных DNS. Всегда работают без шифрования (без DoH/DoT). Пример: 1.1.1.1, 8.8.8.8. В отличие от аварийного DNS провайдера, это выбранные вами серверы." msgstr "Обычные UDP DNS-серверы, используемые как резерв при недоступности всех основных DNS. Всегда работают без шифрования (без DoH/DoT). Пример: 1.1.1.1, 8.8.8.8. В отличие от аварийного DNS провайдера, это выбранные вами серверы."
@@ -4277,8 +4274,8 @@ msgstr "Правило"
msgid "Rule Name" msgid "Rule Name"
msgstr "Название правила" msgstr "Название правила"
msgid "Rule set must be an HTTP(S) URL or a local .srs / .json path" msgid "Rule set must be an HTTP(S) URL or a local .srs / .json / .lst / .txt path"
msgstr "Набор правил должен быть HTTP(S) URL или локальным путём .srs / .json" msgstr ""
msgid "Rule set settings" msgid "Rule set settings"
msgstr "Настройки набора правил" msgstr "Настройки набора правил"
@@ -5981,6 +5978,18 @@ msgstr "Правила маршрутизации sing-box для Zapret2 не
msgid "Zero config mesh VPN daemon" msgid "Zero config mesh VPN daemon"
msgstr "Mesh VPN-демон с нулевой конфигурацией" msgstr "Mesh VPN-демон с нулевой конфигурацией"
msgid "Add URLs or local paths to .srs / .json lists. Only domain rules are supported."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Поддерживаются только правила доменов."
msgid "Add URLs or local paths to .srs / .json lists. Subnets are ignored by default."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Подсети игнорируются по умолчанию."
msgid "Plain .lst / .txt lists belong in \"Domain and IP lists\", not \"Rule sets\""
msgstr ""
msgid "Rule set must be an HTTP(S) URL or a local .srs / .json path"
msgstr "Набор правил должен быть HTTP(S) URL или локальным путём .srs / .json"
msgid "Testing strategy" msgid "Testing strategy"
msgstr "Тестирование стратегии" msgstr "Тестирование стратегии"
@@ -7652,16 +7652,10 @@ function validateFileReference(value, extensions, errorMessage, options = {}) {
} }
function validateCustomRulesetReference(value) { function validateCustomRulesetReference(value) {
if (hasAllowedReferenceExtension(value, [".lst", ".txt"])) {
return _(
'Plain .lst / .txt lists belong in "Domain and IP lists", not "Rule sets"',
);
}
return validateFileReference( return validateFileReference(
value, value,
[".srs", ".json"], [".srs", ".json", ".lst", ".txt"],
_("Rule set must be an HTTP(S) URL or a local .srs / .json path"), _("Rule set must be an HTTP(S) URL or a local .srs / .json / .lst / .txt path"),
{ allowRemoteWithoutExtension: true }, { allowRemoteWithoutExtension: true },
); );
} }
@@ -11139,7 +11133,7 @@ function createSectionContent(section) {
"rule_set", "rule_set",
_("Rule sets"), _("Rule sets"),
_( _(
"Add URLs or local paths to .srs / .json lists. Subnets are ignored by default.", "Add URLs or local paths to .srs / .json / .lst / .txt lists. Subnets are ignored by default.",
), ),
); );
ruleSetOption.modalonly = true; ruleSetOption.modalonly = true;
@@ -11168,7 +11162,7 @@ function createSectionContent(section) {
"_dns_rule_set", "_dns_rule_set",
_("Rule sets"), _("Rule sets"),
_( _(
"Add URLs or local paths to .srs / .json lists. Only domain rules are supported.", "Add URLs or local paths to .srs / .json / .lst / .txt lists. Only domain rules are supported.",
), ),
); );
dnsRuleSetOption.depends("action", "dns"); dnsRuleSetOption.depends("action", "dns");
+20 -11
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TACHYON\n" "Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-16 19:54+0300\n" "POT-Creation-Date: 2026-09-16 22:41+0300\n"
"PO-Revision-Date: 2026-09-16 19:54+0300\n" "PO-Revision-Date: 2026-09-16 22:41+0300\n"
"Last-Translator: Dushnilin\n" "Last-Translator: Dushnilin\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
@@ -272,11 +272,11 @@ msgstr "Добавьте URL или локальные пути к списка
msgid "Add URLs or local paths to .lst lists containing domains. IP entries are ignored." msgid "Add URLs or local paths to .lst lists containing domains. IP entries are ignored."
msgstr "Добавьте URL или локальные пути к спискам .lst, содержащим домены. IP-записи игнорируются." msgstr "Добавьте URL или локальные пути к спискам .lst, содержащим домены. IP-записи игнорируются."
msgid "Add URLs or local paths to .srs / .json lists. Only domain rules are supported." msgid "Add URLs or local paths to .srs / .json / .lst / .txt lists. Only domain rules are supported."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Поддерживаются только правила доменов." msgstr ""
msgid "Add URLs or local paths to .srs / .json lists. Subnets are ignored by default." msgid "Add URLs or local paths to .srs / .json / .lst / .txt lists. Subnets are ignored by default."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Подсети игнорируются по умолчанию." msgstr ""
msgid "Add URLTest / Priority group" msgid "Add URLTest / Priority group"
msgstr "Добавить группу URLTest / Приоритет" msgstr "Добавить группу URLTest / Приоритет"
@@ -3758,9 +3758,6 @@ msgstr "Закрепить на дашборде"
msgid "Pin P2P isolation to the torrent client's own ports, comma separated. Each entry is \"proto\", \"proto:port\" or \"proto:port-port\" (tcp/udp). Without a port the whole protocol is routed direct." msgid "Pin P2P isolation to the torrent client's own ports, comma separated. Each entry is \"proto\", \"proto:port\" or \"proto:port-port\" (tcp/udp). Without a port the whole protocol is routed direct."
msgstr "Привязка изоляции P2P к портам торрент-клиента через запятую. Каждая запись: \"proto\", \"proto:port\" или \"proto:port-port\" (tcp/udp). Без указания порта весь протокол направляется напрямую." msgstr "Привязка изоляции P2P к портам торрент-клиента через запятую. Каждая запись: \"proto\", \"proto:port\" или \"proto:port-port\" (tcp/udp). Без указания порта весь протокол направляется напрямую."
msgid "Plain .lst / .txt lists belong in \"Domain and IP lists\", not \"Rule sets\""
msgstr ""
msgid "Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers." msgid "Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers."
msgstr "Обычные UDP DNS-серверы, используемые как резерв при недоступности всех основных DNS. Всегда работают без шифрования (без DoH/DoT). Пример: 1.1.1.1, 8.8.8.8. В отличие от аварийного DNS провайдера, это выбранные вами серверы." msgstr "Обычные UDP DNS-серверы, используемые как резерв при недоступности всех основных DNS. Всегда работают без шифрования (без DoH/DoT). Пример: 1.1.1.1, 8.8.8.8. В отличие от аварийного DNS провайдера, это выбранные вами серверы."
@@ -4277,8 +4274,8 @@ msgstr "Правило"
msgid "Rule Name" msgid "Rule Name"
msgstr "Название правила" msgstr "Название правила"
msgid "Rule set must be an HTTP(S) URL or a local .srs / .json path" msgid "Rule set must be an HTTP(S) URL or a local .srs / .json / .lst / .txt path"
msgstr "Набор правил должен быть HTTP(S) URL или локальным путём .srs / .json" msgstr ""
msgid "Rule set settings" msgid "Rule set settings"
msgstr "Настройки набора правил" msgstr "Настройки набора правил"
@@ -5981,6 +5978,18 @@ msgstr "Правила маршрутизации sing-box для Zapret2 не
msgid "Zero config mesh VPN daemon" msgid "Zero config mesh VPN daemon"
msgstr "Mesh VPN-демон с нулевой конфигурацией" msgstr "Mesh VPN-демон с нулевой конфигурацией"
msgid "Add URLs or local paths to .srs / .json lists. Only domain rules are supported."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Поддерживаются только правила доменов."
msgid "Add URLs or local paths to .srs / .json lists. Subnets are ignored by default."
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Подсети игнорируются по умолчанию."
msgid "Plain .lst / .txt lists belong in \"Domain and IP lists\", not \"Rule sets\""
msgstr ""
msgid "Rule set must be an HTTP(S) URL or a local .srs / .json path"
msgstr "Набор правил должен быть HTTP(S) URL или локальным путём .srs / .json"
msgid "Testing strategy" msgid "Testing strategy"
msgstr "Тестирование стратегии" msgstr "Тестирование стратегии"
File diff suppressed because it is too large Load Diff
@@ -2743,11 +2743,44 @@ function import_rule_sets_with_subnets_from_rule(section, settings) {
if (!import_custom_ruleset_subnets_from_local(reference, "source", section, "Rule set " + reference)) if (!import_custom_ruleset_subnets_from_local(reference, "source", section, "Rule set " + reference))
ok = false; ok = false;
} }
else if (match(reference, /^\/.*\.(lst|txt)$/) != null) {
if (!add_plain_subnet_file_to_nft_for_section(section, reference))
ok = false;
}
else if (match(reference, /^https?:\/\//) != null) { else if (match(reference, /^https?:\/\//) != null) {
if (extension == "lst" || extension == "txt" || singbox_rulesets_module().is_plain_list_reference(reference)) {
let tmpfile = temp_path();
if (tmpfile == "") {
ok = false;
} else {
if (download_to_file(reference, tmpfile, service_proxy_address(settings, "lists")) && file_nonempty(tmpfile)) {
convert_crlf_to_lf(tmpfile);
if (!add_plain_subnet_file_to_nft_for_section(section, tmpfile))
ok = false;
let tag_name = "inline-custom-" + singbox_rulesets_module().hash12(reference) + "-ruleset";
let srs_path = TMP_RULESET_FOLDER + "/" + tag_name + ".srs";
let json_path = TMP_RULESET_FOLDER + "/" + tag_name + ".json";
let etc_srs = "/etc/tachyon/rulesets/" + tag_name + ".srs";
let etc_json = "/etc/tachyon/rulesets/" + tag_name + ".json";
common.ensure_dir("/etc/tachyon/rulesets");
common.ensure_dir(TMP_RULESET_FOLDER);
let res = routing_rulesets_module().compile_plain_list(tmpfile, srs_path, json_path);
if (res.format == "binary")
common.copy_file(srs_path, etc_srs);
else
common.copy_file(json_path, etc_json);
} else {
log_message("Failed to download remote plain list " + reference + "; keeping existing cache", "warn");
}
remove_file(tmpfile);
}
}
else {
let format = extension == "json" ? "source" : (extension == "srs" ? "binary" : singbox_rulesets_module().remote_format(reference)); let format = extension == "json" ? "source" : (extension == "srs" ? "binary" : singbox_rulesets_module().remote_format(reference));
if (!import_custom_ruleset_subnets_from_remote(reference, format, section, "Rule set " + reference, settings)) if (!import_custom_ruleset_subnets_from_remote(reference, format, section, "Rule set " + reference, settings))
ok = false; ok = false;
} }
}
else { else {
log_message("Unsupported rule set reference for subnet import: " + reference, "error"); log_message("Unsupported rule set reference for subnet import: " + reference, "error");
ok = false; ok = false;
@@ -2757,6 +2790,52 @@ function import_rule_sets_with_subnets_from_rule(section, settings) {
return ok; return ok;
} }
function update_remote_plain_rulesets_from_rule(section, settings) {
if (!bool_option(section, "enabled", true))
return true;
let rulesets = connections.rule_sets(section);
if (length(rulesets) == 0)
return true;
let ok = true;
for (let reference in rulesets) {
reference = as_string(reference);
if (match(reference, /^https?:\/\//) == null)
continue;
if (!singbox_rulesets_module().is_plain_list_reference(reference))
continue;
log_message("Updating remote plain rule set for '" + section_name(section) + "': " + reference, "info");
let tmpfile = temp_path();
if (tmpfile == "") {
ok = false;
continue;
}
if (download_to_file(reference, tmpfile, service_proxy_address(settings, "lists")) && file_nonempty(tmpfile)) {
convert_crlf_to_lf(tmpfile);
let tag_name = "inline-custom-" + singbox_rulesets_module().hash12(reference) + "-ruleset";
let srs_path = TMP_RULESET_FOLDER + "/" + tag_name + ".srs";
let json_path = TMP_RULESET_FOLDER + "/" + tag_name + ".json";
let etc_srs = "/etc/tachyon/rulesets/" + tag_name + ".srs";
let etc_json = "/etc/tachyon/rulesets/" + tag_name + ".json";
common.ensure_dir("/etc/tachyon/rulesets");
common.ensure_dir(TMP_RULESET_FOLDER);
let res = routing_rulesets_module().compile_plain_list(tmpfile, srs_path, json_path);
if (res.format == "binary")
common.copy_file(srs_path, etc_srs);
else
common.copy_file(json_path, etc_json);
}
else {
log_message("Failed to download remote plain rule set " + reference + "; keeping existing cache", "warn");
}
remove_file(tmpfile);
}
return ok;
}
function import_domains_from_remote_plain_file(url, section, settings) { function import_domains_from_remote_plain_file(url, section, settings) {
let tmpfile = temp_path(); let tmpfile = temp_path();
if (tmpfile == "") if (tmpfile == "")
@@ -3155,6 +3234,9 @@ function list_update() {
for (let section in sections) for (let section in sections)
if (!import_domains_from_remote_domain_lists(section, settings)) if (!import_domains_from_remote_domain_lists(section, settings))
ok = false; ok = false;
for (let section in sections)
if (!update_remote_plain_rulesets_from_rule(section, settings))
ok = false;
// Фаза 2: применение к nft (захватываем лок reload, чтобы не конфликтовать // Фаза 2: применение к nft (захватываем лок reload, чтобы не конфликтовать
// с reload-firewall, который пересоздаёт таблицы и сеты nftables) // с reload-firewall, который пересоздаёт таблицы и сеты nftables)
+3 -4
View File
@@ -363,8 +363,9 @@ function ruleset_reference_valid(reference, community_services) {
return reference == "" || return reference == "" ||
community_service_valid(reference, community_services) || community_service_valid(reference, community_services) ||
(remote_reference(reference) && !remote_plain_list_reference(reference)) || remote_reference(reference) ||
absolute_reference_with_extension(reference, ".srs", ".json"); absolute_reference_with_extension(reference, ".srs", ".json") ||
absolute_reference_with_extension(reference, ".lst", ".txt");
} }
function plain_domain_ip_list_reference_valid(reference) { function plain_domain_ip_list_reference_valid(reference) {
@@ -1223,8 +1224,6 @@ function validate_service_value(service, context) {
} }
function validate_ruleset_reference_value(reference, context) { function validate_ruleset_reference_value(reference, context) {
if (remote_plain_list_reference(reference))
fail_validation("Plain .lst/.txt list '" + reference + "' belongs in domain_ip_lists, not rule_set. Aborted.");
if (ruleset_reference_valid(reference, context.community_services)) if (ruleset_reference_valid(reference, context.community_services))
return; return;
+13
View File
@@ -538,6 +538,18 @@ function remove_file(path) {
try { fs.unlink(path); return true; } catch (e) { return true; } try { fs.unlink(path); return true; } catch (e) { return true; }
} }
function copy_file(source, target) {
source = as_string(source);
target = as_string(target);
let data = fs.readfile(source);
if (data == null)
return false;
let slash = rindex(target, "/");
if (slash > 0)
ensure_dir(substr(target, 0, slash));
return fs.writefile(target, data) != null;
}
function unlink_file(path) { function unlink_file(path) {
return remove_file(path); return remove_file(path);
} }
@@ -705,6 +717,7 @@ return {
command_output_from_args, command_output_from_args,
ensure_dir, ensure_dir,
remove_file, remove_file,
copy_file,
unlink_file, unlink_file,
write_file, write_file,
file_exists, file_exists,
@@ -1218,6 +1218,10 @@ function build_system_info() {
let zapret2_version = zapret2_installed ? provider_version(ZAPRET2_RUNTIME_UC) : "not installed"; let zapret2_version = zapret2_installed ? provider_version(ZAPRET2_RUNTIME_UC) : "not installed";
let byedpi_installed = provider_installed(BYEDPI_RUNTIME_UC) ? 1 : 0; let byedpi_installed = provider_installed(BYEDPI_RUNTIME_UC) ? 1 : 0;
let byedpi_version = byedpi_installed ? provider_version(BYEDPI_RUNTIME_UC) : "not installed"; let byedpi_version = byedpi_installed ? provider_version(BYEDPI_RUNTIME_UC) : "not installed";
let wdtt_installed = provider_installed(WDTT_RUNTIME_UC) ? 1 : 0;
let wdtt_version = wdtt_installed ? provider_version(WDTT_RUNTIME_UC) : "not installed";
let olcrtc_installed = provider_installed(OLCRTC_RUNTIME_UC) ? 1 : 0;
let olcrtc_version = olcrtc_installed ? provider_version(OLCRTC_RUNTIME_UC) : "not installed";
let tailscale_installed = provider_installed(TAILSCALE_RUNTIME_UC) ? 1 : 0; let tailscale_installed = provider_installed(TAILSCALE_RUNTIME_UC) ? 1 : 0;
let tailscale_version = tailscale_installed ? provider_version(TAILSCALE_RUNTIME_UC) : "not installed"; let tailscale_version = tailscale_installed ? provider_version(TAILSCALE_RUNTIME_UC) : "not installed";
let fptn_installed = provider_installed(FPTN_RUNTIME_UC) ? 1 : 0; let fptn_installed = provider_installed(FPTN_RUNTIME_UC) ? 1 : 0;
@@ -1261,6 +1265,8 @@ function build_system_info() {
let zapret_meta = read_backup_meta("zapret"); let zapret_meta = read_backup_meta("zapret");
let zapret2_meta = read_backup_meta("zapret2"); let zapret2_meta = read_backup_meta("zapret2");
let byedpi_meta = read_backup_meta("byedpi"); let byedpi_meta = read_backup_meta("byedpi");
let wdtt_meta = read_backup_meta("wdtt");
let olcrtc_meta = read_backup_meta("olcrtc");
let tailscale_meta = read_backup_meta("tailscale"); let tailscale_meta = read_backup_meta("tailscale");
let fptn_meta = read_backup_meta("fptn"); let fptn_meta = read_backup_meta("fptn");
@@ -1290,6 +1296,14 @@ function build_system_info() {
byedpi_installed, byedpi_installed,
byedpi_backup_version: byedpi_meta ? as_string(byedpi_meta.version) : "", byedpi_backup_version: byedpi_meta ? as_string(byedpi_meta.version) : "",
byedpi_backup_time: byedpi_meta ? int(byedpi_meta.timestamp || 0) : 0, byedpi_backup_time: byedpi_meta ? int(byedpi_meta.timestamp || 0) : 0,
wdtt_version,
wdtt_installed,
wdtt_backup_version: wdtt_meta ? as_string(wdtt_meta.version) : "",
wdtt_backup_time: wdtt_meta ? int(wdtt_meta.timestamp || 0) : 0,
olcrtc_version,
olcrtc_installed,
olcrtc_backup_version: olcrtc_meta ? as_string(olcrtc_meta.version) : "",
olcrtc_backup_time: olcrtc_meta ? int(olcrtc_meta.timestamp || 0) : 0,
tailscale_version, tailscale_version,
tailscale_installed, tailscale_installed,
tailscale_backup_version: tailscale_meta ? as_string(tailscale_meta.version) : "", tailscale_backup_version: tailscale_meta ? as_string(tailscale_meta.version) : "",
@@ -31,6 +31,7 @@ let bool_option = common.bool_option;
let list_option = common.list_option; let list_option = common.list_option;
let section_name = common.section_name; let section_name = common.section_name;
let read_json_file = common.read_json_file; let read_json_file = common.read_json_file;
let write_json = common.write_json;
function yaml_quote(value) { function yaml_quote(value) {
value = as_string(value); value = as_string(value);
@@ -70,6 +71,12 @@ function package_version() {
let ver = trim(fs.readfile("/etc/olcrtc/.version") || ""); let ver = trim(fs.readfile("/etc/olcrtc/.version") || "");
if (ver != "") if (ver != "")
return ver; return ver;
let pkgs = require("core.packages");
if (pkgs && pkgs.version) {
let v = pkgs.version("olcrtc");
if (v != "")
return v;
}
return ""; return "";
} }
@@ -32,6 +32,7 @@ let list_option = common.list_option;
let section_name = common.section_name; let section_name = common.section_name;
let read_json_file = common.read_json_file; let read_json_file = common.read_json_file;
let write_json_file = common.write_json_file; let write_json_file = common.write_json_file;
let write_json = common.write_json;
function lan_mac_address() { function lan_mac_address() {
for (let iface in ["br-lan", "lan", "eth0", "eth1"]) { for (let iface in ["br-lan", "lan", "eth0", "eth1"]) {
@@ -84,6 +85,12 @@ function package_version() {
if (ver != "") if (ver != "")
return ver; return ver;
} }
let pkgs = require("core.packages");
if (pkgs && pkgs.version) {
let v = pkgs.version("wdtt") || pkgs.version("qwdtt");
if (v != "")
return v;
}
return ""; return "";
} }
+29
View File
@@ -469,6 +469,34 @@ function has_rules(path) {
return length(array_or_empty(ruleset.rules)) > 0; return length(array_or_empty(ruleset.rules)) > 0;
} }
function compile_plain_list(plain_path, ruleset_srs_path, ruleset_json_path) {
create_source(ruleset_json_path);
import_plain_list(plain_path, ruleset_json_path, "domain_suffix", "domains", "5000");
import_plain_list(plain_path, ruleset_json_path, "ip_cidr", "subnets", "5000");
let has_doms = has_domain_matchers(ruleset_json_path);
let rules_exist = has_rules(ruleset_json_path);
if (rules_exist) {
let compile_ok = common.command_success_from_args([
"sing-box", "rule-set", "compile", ruleset_json_path, "-o", ruleset_srs_path
]);
let srs_st = fs.stat(ruleset_srs_path);
if (compile_ok && srs_st != null && srs_st.size >= 16) {
fs.unlink(ruleset_json_path);
return { format: "binary", path: ruleset_srs_path, has_domains: has_doms };
}
return { format: "source", path: ruleset_json_path, has_domains: has_doms };
}
let rulesets_mod = null;
try { rulesets_mod = require("singbox.rulesets"); } catch (e) {}
if (rulesets_mod && rulesets_mod.ensure_empty_srs_stub)
rulesets_mod.ensure_empty_srs_stub(ruleset_srs_path);
fs.unlink(ruleset_json_path);
return { format: "binary", path: ruleset_srs_path, has_domains: false };
}
function module_exports() { function module_exports() {
return { return {
create_source, create_source,
@@ -480,6 +508,7 @@ function module_exports() {
extract_ip_cidr_nft_elements, extract_ip_cidr_nft_elements,
has_domain_matchers, has_domain_matchers,
has_rules, has_rules,
compile_plain_list,
ruleset_tag, ruleset_tag,
read_json_file, read_json_file,
write_json_file write_json_file
+17 -5
View File
@@ -751,11 +751,19 @@ function has_community_subnet_list(value) {
return rule_config.has_community_subnet_list(value); return rule_config.has_community_subnet_list(value);
} }
function rule_has_list_update_source(enabled, action, community_lists, remote_domain_lists, remote_subnet_lists, rule_set_with_subnets, domain_ip_lists) { function has_remote_plain_ruleset(value) {
for (let item in split(as_string(value), /[ \t\r\n]+/)) {
if (match(item, /^https?:\/\//) != null && match(item, /\.(lst|txt)$/i) != null)
return true;
}
return false;
}
function rule_has_list_update_source(enabled, action, community_lists, remote_domain_lists, remote_subnet_lists, rule_set_with_subnets, domain_ip_lists, rule_set) {
if (!arg_bool(enabled)) if (!arg_bool(enabled))
return false; return false;
if (as_string(action) == "dns") if (as_string(action) == "dns")
return list_has_remote_references(domain_ip_lists); return list_has_remote_references(domain_ip_lists) || has_remote_plain_ruleset(rule_set);
if (as_string(action) == "hosts") if (as_string(action) == "hosts")
return false; return false;
@@ -764,7 +772,8 @@ function rule_has_list_update_source(enabled, action, community_lists, remote_do
as_string(remote_domain_lists) != "" || as_string(remote_domain_lists) != "" ||
as_string(remote_subnet_lists) != "" || as_string(remote_subnet_lists) != "" ||
as_string(rule_set_with_subnets) != "" || as_string(rule_set_with_subnets) != "" ||
list_has_remote_references(domain_ip_lists) list_has_remote_references(domain_ip_lists) ||
has_remote_plain_ruleset(rule_set)
); );
} }
@@ -1244,6 +1253,7 @@ function append_list_update_signature_body(body, section) {
body = signature_add_value(body, "lists." + name + ".action", action); body = signature_add_value(body, "lists." + name + ".action", action);
if (action == "dns") { if (action == "dns") {
body = signature_add_value(body, "lists." + name + ".domain_ip_lists", option(section, "domain_ip_lists", "")); body = signature_add_value(body, "lists." + name + ".domain_ip_lists", option(section, "domain_ip_lists", ""));
body = signature_add_value(body, "lists." + name + ".rule_set", connections.rule_sets_value(section));
return body; return body;
} }
if (action == "hosts") { if (action == "hosts") {
@@ -1261,6 +1271,7 @@ function append_list_update_signature_body(body, section) {
body = signature_add_value(body, "lists." + name + ".remote_subnet_lists", option(section, "remote_subnet_lists", "")); body = signature_add_value(body, "lists." + name + ".remote_subnet_lists", option(section, "remote_subnet_lists", ""));
body = signature_add_value(body, "lists." + name + ".rule_set_with_subnets", connections.rule_sets_with_subnets_value(section)); body = signature_add_value(body, "lists." + name + ".rule_set_with_subnets", connections.rule_sets_with_subnets_value(section));
body = signature_add_value(body, "lists." + name + ".domain_ip_lists", option(section, "domain_ip_lists", "")); body = signature_add_value(body, "lists." + name + ".domain_ip_lists", option(section, "domain_ip_lists", ""));
body = signature_add_value(body, "lists." + name + ".rule_set", connections.rule_sets_value(section));
return body; return body;
} }
@@ -1959,7 +1970,8 @@ function has_list_update_sources_from_sections(sections) {
option(section, "remote_domain_lists", ""), option(section, "remote_domain_lists", ""),
option(section, "remote_subnet_lists", ""), option(section, "remote_subnet_lists", ""),
connections.rule_sets_with_subnets_value(section), connections.rule_sets_with_subnets_value(section),
option(section, "domain_ip_lists", "") option(section, "domain_ip_lists", ""),
connections.rule_sets_value(section)
)) ))
return true; return true;
@@ -2221,7 +2233,7 @@ else if (mode == "community-service-has-subnet-list")
else if (mode == "filter-community-subnet-lists") else if (mode == "filter-community-subnet-lists")
filter_community_subnet_lists(ARGV[1]); filter_community_subnet_lists(ARGV[1]);
else if (mode == "rule-has-list-update-source") else if (mode == "rule-has-list-update-source")
exit(rule_has_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7]) ? 0 : 1); exit(rule_has_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8]) ? 0 : 1);
else if (mode == "rule-has-nft-list-update-source") else if (mode == "rule-has-nft-list-update-source")
exit(rule_has_nft_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]) ? 0 : 1); exit(rule_has_nft_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]) ? 0 : 1);
else if (mode == "rule-has-subscription-update-source") else if (mode == "rule-has-subscription-update-source")
+2
View File
@@ -1011,6 +1011,8 @@ function capability_flags() {
zapret_installed: file_executable(ZAPRET_PROVIDER_NFQWS_BIN) ? 1 : 0, zapret_installed: file_executable(ZAPRET_PROVIDER_NFQWS_BIN) ? 1 : 0,
zapret2_installed: file_executable(ZAPRET2_PROVIDER_NFQWS2_BIN) ? 1 : 0, zapret2_installed: file_executable(ZAPRET2_PROVIDER_NFQWS2_BIN) ? 1 : 0,
byedpi_installed: file_executable(BYEDPI_BIN) ? 1 : 0, byedpi_installed: file_executable(BYEDPI_BIN) ? 1 : 0,
wdtt_installed: (file_executable("/usr/bin/qwdtt") || file_executable("/usr/bin/wdtt")) ? 1 : 0,
olcrtc_installed: file_executable("/usr/bin/olcrtc") ? 1 : 0,
server_inbounds_enabled_count: 0 server_inbounds_enabled_count: 0
}; };
@@ -952,9 +952,121 @@ function ensure_custom_ruleset(config, reference) {
return { tag: tag_name, kind }; return { tag: tag_name, kind };
let extension = runtime_rulesets.file_extension(reference); let extension = runtime_rulesets.file_extension(reference);
if (runtime_rulesets.is_plain_list_reference(reference)) let folder = ctx.runtime_ruleset_folder || runtime_ruleset_folder;
ctx.runtime_generate_unsupported("plain .lst/.txt list '" + reference + "' belongs in domain_ip_lists, not rule_set"); common.ensure_dir(folder);
if (runtime_rulesets.is_plain_list_reference(reference)) {
let srs_path = folder + "/" + tag_name + ".srs";
let json_path = folder + "/" + tag_name + ".json";
let etc_srs = "/etc/tachyon/rulesets/" + tag_name + ".srs";
let etc_json = "/etc/tachyon/rulesets/" + tag_name + ".json";
if (substr(reference, 0, 1) == "/") { if (substr(reference, 0, 1) == "/") {
if (!helpers.file_is_usable(reference, 1))
return null;
let ref_st = fs.stat(reference);
let srs_st = fs.stat(srs_path);
if (srs_st && runtime_rulesets.is_valid_srs_file(srs_path) && ref_st && srs_st.mtime >= ref_st.mtime) {
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "binary",
path: srs_path
});
return { tag: tag_name, kind: "domains" };
}
let res = source_rulesets.compile_plain_list(reference, srs_path, json_path);
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: res.format,
path: res.path
});
return { tag: tag_name, kind: res.has_domains ? "domains" : "unknown" };
}
else if (substr(reference, 0, 7) == "http://" || substr(reference, 0, 8) == "https://") {
if (runtime_rulesets.is_valid_srs_file(srs_path)) {
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "binary",
path: srs_path
});
return { tag: tag_name, kind: "domains" };
}
else if (runtime_rulesets.is_valid_srs_file(etc_srs)) {
common.copy_file(etc_srs, srs_path);
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "binary",
path: srs_path
});
return { tag: tag_name, kind: "domains" };
}
else if (fs.stat(json_path) != null) {
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "source",
path: json_path
});
return { tag: tag_name, kind: "domains" };
}
else if (fs.stat(etc_json) != null) {
common.copy_file(etc_json, json_path);
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "source",
path: json_path
});
return { tag: tag_name, kind: "domains" };
}
else {
let tmp_plain = folder + "/" + tag_name + ".tmp.plain";
let dl_ok = false;
if (common.command_success("command -v curl"))
dl_ok = common.command_success_from_args([ "curl", "-fsSL", "--connect-timeout", "5", "-m", "10", reference, "-o", tmp_plain ]);
else if (common.command_success("command -v wget"))
dl_ok = common.command_success_from_args([ "wget", "-q", "-T", "10", "-O", tmp_plain, reference ]);
if (dl_ok && helpers.file_is_usable(tmp_plain, 1)) {
common.ensure_dir("/etc/tachyon/rulesets");
let res = source_rulesets.compile_plain_list(tmp_plain, srs_path, json_path);
if (res.format == "binary")
common.copy_file(srs_path, etc_srs);
else
common.copy_file(json_path, etc_json);
fs.unlink(tmp_plain);
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: res.format,
path: res.path
});
return { tag: tag_name, kind: res.has_domains ? "domains" : "unknown" };
}
else {
fs.unlink(tmp_plain);
runtime_rulesets.ensure_empty_srs_stub(srs_path);
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "binary",
path: srs_path
});
return { tag: tag_name, kind: "domains" };
}
}
}
else {
ctx.runtime_generate_unsupported("plain list reference is not supported by sing-box config generation");
}
}
else if (substr(reference, 0, 1) == "/") {
if (extension != "srs" && extension != "json") if (extension != "srs" && extension != "json")
ctx.runtime_generate_unsupported("local rule_set extension is not supported by sing-box config generation"); ctx.runtime_generate_unsupported("local rule_set extension is not supported by sing-box config generation");
// Skip broken local ruleset files — nonexistent or suspiciously small // Skip broken local ruleset files — nonexistent or suspiciously small