fix(runtime): устранение сбоев запуска sing-box, исправление правил nftables и защита от гонок вочдога

- Восстановление кэша правил: восстановление .srs, .lst, .json файлов из постоянного каталога /etc/tachyon/rulesets/ при старте; защита от удаления кэша при остановке; добавление фоллбэка пустых ruleset в runtime.uc перед валидацией
- nftables: удаление невалидного auto-merge для интервальных составных сетов (ipv4_addr/ipv6_addr . inet_service)
- core/ip: исправление нормализации IPv6-адреса и порта для TPROXY без дублирования скобок
- dns block: исправление возвращаемого статуса nft_add_dns_block_rules_from_schedules при отсутствии правил блокировки
- watchdog & lifecycle: добавление флага блокировки /var/run/tachyon/starting для исключения сброса правил вочдогом во время старта
- ui & i18n: форматирование бейджей устройств в родительском контроле и обновление переводов
This commit is contained in:
Dushnilin
2026-08-21 08:52:23 +03:00
parent adf5c7a22f
commit e4e2d19c6d
13 changed files with 128 additions and 40 deletions
+3 -3
View File
@@ -1628,9 +1628,9 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803", "../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803",
"src/partials/modal/renderModal.ts:263", "src/partials/modal/renderModal.ts:263",
"src/tachyon/tabs/dashboard/initController.ts:1262", "src/tachyon/tabs/dashboard/initController.ts:1262",
"src/tachyon/tabs/diagnostic/initController.ts:1542",
"src/tachyon/tabs/monitoring/initController.ts:884", "src/tachyon/tabs/monitoring/initController.ts:884",
"src/tachyon/tabs/monitoring/initController.ts:934", "src/tachyon/tabs/monitoring/initController.ts:934",
"src/tachyon/tabs/diagnostic/initController.ts:1542",
"src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:213", "src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:213",
"src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:439", "src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:439",
"src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:485" "src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:485"
@@ -8106,8 +8106,8 @@
] ]
}, },
{ {
"call": "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually.", "call": "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually. MAC addresses (e.g. AA:BB:CC:DD:EE:FF) work even if the device's IP changes. IP addresses (e.g. 192.168.1.150) are used for DNS-level blocking and require a stable lease.",
"key": "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually.", "key": "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually. MAC addresses (e.g. AA:BB:CC:DD:EE:FF) work even if the device's IP changes. IP addresses (e.g. 192.168.1.150) are used for DNS-level blocking and require a stable lease.",
"places": [ "places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390" "../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390"
] ]
+4 -4
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-08-21 04:11+0300\n" "POT-Creation-Date: 2026-08-21 04:51+0300\n"
"PO-Revision-Date: 2026-08-21 04:11+0300\n" "PO-Revision-Date: 2026-08-21 04:51+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n" "Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
@@ -1023,9 +1023,9 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803
#: src/partials/modal/renderModal.ts:263 #: src/partials/modal/renderModal.ts:263
#: src/tachyon/tabs/dashboard/initController.ts:1262 #: src/tachyon/tabs/dashboard/initController.ts:1262
#: src/tachyon/tabs/diagnostic/initController.ts:1542
#: src/tachyon/tabs/monitoring/initController.ts:884 #: src/tachyon/tabs/monitoring/initController.ts:884
#: src/tachyon/tabs/monitoring/initController.ts:934 #: src/tachyon/tabs/monitoring/initController.ts:934
#: src/tachyon/tabs/diagnostic/initController.ts:1542
#: src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:213 #: src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:213
#: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:439 #: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:439
#: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:485 #: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:485
@@ -4976,7 +4976,7 @@ msgid "Select DNS protocol to use"
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually." msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually. MAC addresses (e.g. AA:BB:CC:DD:EE:FF) work even if the device's IP changes. IP addresses (e.g. 192.168.1.150) are used for DNS-level blocking and require a stable lease."
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8828 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8828
+7 -4
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-08-21 07:11+0300\n" "POT-Creation-Date: 2026-08-21 07:51+0300\n"
"PO-Revision-Date: 2026-08-21 07:11+0300\n" "PO-Revision-Date: 2026-08-21 07:51+0300\n"
"Last-Translator: Dushnilin\n" "Last-Translator: Dushnilin\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
@@ -3164,8 +3164,8 @@ msgstr "Выберите и упорядочите секции, через ко
msgid "Select DNS protocol to use" msgid "Select DNS protocol to use"
msgstr "Выберите протокол DNS" msgstr "Выберите протокол DNS"
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually." msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually. MAC addresses (e.g. AA:BB:CC:DD:EE:FF) work even if the device's IP changes. IP addresses (e.g. 192.168.1.150) are used for DNS-level blocking and require a stable lease."
msgstr "Выберите устройства из локальной сети для применения расписания, либо укажите IP или MAC-адреса вручную." msgstr "Выберите устройства из локальной сети, либо укажите IP или MAC-адреса вручную. MAC-адреса (например AA:BB:CC:DD:EE:FF) работают даже если IP устройства меняется. IP-адреса (например 192.168.1.150) используются для блокировки на уровне DNS и требуют стабильного адреса."
msgid "Select network interface for VPN connection" msgid "Select network interface for VPN connection"
msgstr "Выберите интерфейс для VPN-подключения" msgstr "Выберите интерфейс для VPN-подключения"
@@ -4256,6 +4256,9 @@ msgstr "sing-box outbound для Zapret2 настроен"
msgid "Zapret2 sing-box outbound is not configured" msgid "Zapret2 sing-box outbound is not configured"
msgstr "sing-box outbound для Zapret2 не настроен" msgstr "sing-box outbound для Zapret2 не настроен"
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually."
msgstr "Выберите устройства из локальной сети для применения расписания, либо укажите IP или MAC-адреса вручную."
msgid "API returned error — token may be invalid" msgid "API returned error — token may be invalid"
msgstr "API вернул ошибку — возможно, токен недействителен" msgstr "API вернул ошибку — возможно, токен недействителен"
@@ -54,22 +54,22 @@ function addLocalDeviceChoice(choices, ip, name, mac) {
if (normalizedIp && main.validateIP(normalizedIp).valid) { if (normalizedIp && main.validateIP(normalizedIp).valid) {
if (normalizedName) { if (normalizedName) {
choices[normalizedIp] = `${normalizedName} (${normalizedIp})`; choices[normalizedIp] = `IP: ${normalizedIp} — ${normalizedName}`;
} else if (normalizedMac) { } else if (normalizedMac) {
choices[normalizedIp] = `${normalizedIp} [${normalizedMac}]`; choices[normalizedIp] = `IP: ${normalizedIp} — ${normalizedMac}`;
} else if (!choices[normalizedIp]) { } else if (!choices[normalizedIp]) {
choices[normalizedIp] = `${normalizedIp}`; choices[normalizedIp] = `IP: ${normalizedIp}`;
} }
} }
if (normalizedMac && /^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/.test(normalizedMac)) { if (normalizedMac && /^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/.test(normalizedMac)) {
const formattedMac = normalizedMac.replace(/-/g, ":"); const formattedMac = normalizedMac.replace(/-/g, ":");
if (normalizedName) { if (normalizedName) {
choices[formattedMac] = `${normalizedName} [MAC: ${formattedMac}]`; choices[formattedMac] = `MAC: ${formattedMac} — ${normalizedName}`;
} else if (normalizedIp) { } else if (normalizedIp) {
choices[formattedMac] = `${normalizedIp} [MAC: ${formattedMac}]`; choices[formattedMac] = `MAC: ${formattedMac} — ${normalizedIp}`;
} else if (!choices[formattedMac]) { } else if (!choices[formattedMac]) {
choices[formattedMac] = `${formattedMac}`; choices[formattedMac] = `MAC: ${formattedMac}`;
} }
} }
} }
@@ -200,15 +200,19 @@ function createParentalContent(section) {
if (devs.length === 0) { if (devs.length === 0) {
return '<span style="opacity:0.5;">' + _("All devices") + "</span>"; return '<span style="opacity:0.5;">' + _("All devices") + "</span>";
} }
const badges = devs const isMacAddr = (dev) =>
/^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/.test(dev);
const macs = devs.filter(isMacAddr);
const ips = devs.filter((d) => !isMacAddr(d));
const badges = [...macs, ...ips]
.map((dev) => { .map((dev) => {
const isMac = /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/.test(dev); const isMac = isMacAddr(dev);
const bg = isMac ? "#2d3748" : "#2a3441"; const bg = isMac ? "#2d3748" : "#2a3441";
const color = isMac ? "#b794f4" : "#63b3ed"; const color = isMac ? "#b794f4" : "#63b3ed";
const icon = isMac ? "🏷 " : "🖥 "; const label = isMac ? "MAC" : "IP";
return ( return (
`<span class="badge" style="background:${bg};color:${color};padding:3px 8px;border-radius:6px;font-size:11px;font-family:monospace;margin-right:4px;font-weight:600;display:inline-block;margin-bottom:2px;">` + `<span class="badge" style="background:${bg};color:${color};padding:3px 8px;border-radius:6px;font-size:11px;font-family:monospace;margin-right:4px;font-weight:600;display:inline-block;margin-bottom:2px;border:1px solid ${color}44;">` +
icon + `<span style="opacity:0.75;font-weight:700;margin-right:3px;">${label}:</span>` +
dev + dev +
"</span>" "</span>"
); );
@@ -387,7 +391,7 @@ function createParentalContent(section) {
form.DynamicList, form.DynamicList,
"device_ip", "device_ip",
_("Target Devices (IP / MAC)"), _("Target Devices (IP / MAC)"),
_("Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually."), _("Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually. MAC addresses (e.g. AA:BB:CC:DD:EE:FF) work even if the device's IP changes. IP addresses (e.g. 192.168.1.150) are used for DNS-level blocking and require a stable lease."),
); );
o.modalonly = true; o.modalonly = true;
o.rmempty = false; o.rmempty = false;
+7 -4
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-08-21 07:11+0300\n" "POT-Creation-Date: 2026-08-21 07:51+0300\n"
"PO-Revision-Date: 2026-08-21 07:11+0300\n" "PO-Revision-Date: 2026-08-21 07:51+0300\n"
"Last-Translator: Dushnilin\n" "Last-Translator: Dushnilin\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
@@ -3164,8 +3164,8 @@ msgstr "Выберите и упорядочите секции, через ко
msgid "Select DNS protocol to use" msgid "Select DNS protocol to use"
msgstr "Выберите протокол DNS" msgstr "Выберите протокол DNS"
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually." msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually. MAC addresses (e.g. AA:BB:CC:DD:EE:FF) work even if the device's IP changes. IP addresses (e.g. 192.168.1.150) are used for DNS-level blocking and require a stable lease."
msgstr "Выберите устройства из локальной сети для применения расписания, либо укажите IP или MAC-адреса вручную." msgstr "Выберите устройства из локальной сети, либо укажите IP или MAC-адреса вручную. MAC-адреса (например AA:BB:CC:DD:EE:FF) работают даже если IP устройства меняется. IP-адреса (например 192.168.1.150) используются для блокировки на уровне DNS и требуют стабильного адреса."
msgid "Select network interface for VPN connection" msgid "Select network interface for VPN connection"
msgstr "Выберите интерфейс для VPN-подключения" msgstr "Выберите интерфейс для VPN-подключения"
@@ -4256,6 +4256,9 @@ msgstr "sing-box outbound для Zapret2 настроен"
msgid "Zapret2 sing-box outbound is not configured" msgid "Zapret2 sing-box outbound is not configured"
msgstr "sing-box outbound для Zapret2 не настроен" msgstr "sing-box outbound для Zapret2 не настроен"
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually."
msgstr "Выберите устройства из локальной сети для применения расписания, либо укажите IP или MAC-адреса вручную."
msgid "API returned error — token may be invalid" msgid "API returned error — token may be invalid"
msgstr "API вернул ошибку — возможно, токен недействителен" msgstr "API вернул ошибку — возможно, токен недействителен"
+4 -4
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-08-21 04:11+0300\n" "POT-Creation-Date: 2026-08-21 04:51+0300\n"
"PO-Revision-Date: 2026-08-21 04:11+0300\n" "PO-Revision-Date: 2026-08-21 04:51+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n" "Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
@@ -1023,9 +1023,9 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803
#: src/partials/modal/renderModal.ts:263 #: src/partials/modal/renderModal.ts:263
#: src/tachyon/tabs/dashboard/initController.ts:1262 #: src/tachyon/tabs/dashboard/initController.ts:1262
#: src/tachyon/tabs/diagnostic/initController.ts:1542
#: src/tachyon/tabs/monitoring/initController.ts:884 #: src/tachyon/tabs/monitoring/initController.ts:884
#: src/tachyon/tabs/monitoring/initController.ts:934 #: src/tachyon/tabs/monitoring/initController.ts:934
#: src/tachyon/tabs/diagnostic/initController.ts:1542
#: src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:213 #: src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:213
#: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:439 #: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:439
#: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:485 #: src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts:485
@@ -4976,7 +4976,7 @@ msgid "Select DNS protocol to use"
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually." msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually. MAC addresses (e.g. AA:BB:CC:DD:EE:FF) work even if the device's IP changes. IP addresses (e.g. 192.168.1.150) are used for DNS-level blocking and require a stable lease."
msgstr "" msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8828 #: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8828
+3 -1
View File
@@ -132,6 +132,8 @@ function ip_family(value) {
function format_ipv6_tproxy_target(address, port) { function format_ipv6_tproxy_target(address, port) {
address = as_string(address); address = as_string(address);
if (index(address, "]:") >= 0)
return address;
if (substr(address, 0, 1) == "[" && substr(address, length(address) - 1, 1) == "]") if (substr(address, 0, 1) == "[" && substr(address, length(address) - 1, 1) == "]")
return address + ":" + as_string(port); return address + ":" + as_string(port);
return "[" + address + "]:" + as_string(port); return "[" + address + "]:" + as_string(port);
@@ -148,4 +150,4 @@ return {
nft_ip_or_cidr, nft_ip_or_cidr,
ip_family, ip_family,
format_ipv6_tproxy_target format_ipv6_tproxy_target
}; };
+19 -4
View File
@@ -411,11 +411,11 @@ function nft_create_inet_service_set(table, name) {
} }
function nft_create_ipv4_port_set(table, name) { function nft_create_ipv4_port_set(table, name) {
return nft_create_set(table, name, "{ type ipv4_addr . inet_service; flags interval; auto-merge; }"); return nft_create_set(table, name, "{ type ipv4_addr . inet_service; flags interval; }");
} }
function nft_create_ipv6_port_set(table, name) { function nft_create_ipv6_port_set(table, name) {
return nft_create_set(table, name, "{ type ipv6_addr . inet_service; flags interval; auto-merge; }"); return nft_create_set(table, name, "{ type ipv6_addr . inet_service; flags interval; }");
} }
function nft_create_ifname_set(table, name) { function nft_create_ifname_set(table, name) {
@@ -1053,7 +1053,7 @@ function nft_add_dns_block_rules_from_schedules(schedules, table) {
} }
} }
} }
return added; return true;
} }
function nft_add_dns_block_rules_from_uci(table) { function nft_add_dns_block_rules_from_uci(table) {
@@ -2166,7 +2166,22 @@ function nft_populate_runtime_sets_from_uci(populate_enabled, deferred_section_n
if (!nft_table_present(table)) { if (!nft_table_present(table)) {
log_warn("nft_populate_runtime_sets_from_uci: Table " + table + " does not exist. Rebuilding firewall rules dynamically."); log_warn("nft_populate_runtime_sets_from_uci: Table " + table + " does not exist. Rebuilding firewall rules dynamically.");
system(common.background_command("/usr/bin/tachyon reload_firewall")); let rt_table = getenv("RT_TABLE_NAME") || "tachyon";
let localv4 = default_arg(localv4_set, "localv4");
let common = default_arg(common_set, "tachyon_subnets");
let port = default_arg(port_set, "tachyon_ports");
let ip_port = default_arg(ip_port_set, "tachyon_ip_ports");
let iface = default_arg(interface_set, "tachyon_interfaces");
let fmark = default_arg(mark, "0x04000000");
let omark = "0x08000000";
let frange4 = "198.18.0.0/15";
let tport = "1602";
let localv6 = default_arg(localv6_set, "localv6");
let common6 = default_arg(common6_set, "tachyon_subnets6");
let ip_port6 = default_arg(ip_port6_set, "tachyon_ip6_ports");
let frange6 = "fc00::/18";
let taddr6 = "[::1]:1602";
nft_rebuild_runtime_from_uci(rt_table, table, localv4, common, port, ip_port, iface, fmark, omark, frange4, tport, "", "", "", "", "", "/opt/zapret2/bin/nfqws2", "0x02000000", "200", "0x00000002", "0x00000004", localv6, common6, ip_port6, frange6, taddr6);
} }
return nft_populate_runtime_sets_from_sections(uci_sections("section"), populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set); return nft_populate_runtime_sets_from_sections(uci_sections("section"), populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set);
+33 -2
View File
@@ -701,6 +701,31 @@ function prepare_subscription_caches(mode) {
return result.status; return result.status;
} }
function restore_rulesets_from_cache() {
let etc_dir = "/etc/tachyon/rulesets";
let tmp_dir = TMP_RULESET_FOLDER;
command_success_from_args(["mkdir", "-p", tmp_dir]);
let dir = fs.opendir(etc_dir);
if (!dir) return;
let entry;
while ((entry = dir.read()) != null) {
if (!match(entry, /\.(srs|lst|json)$/)) continue;
let tmp_path = tmp_dir + "/" + entry;
let etc_path = etc_dir + "/" + entry;
let tmp_st = fs.stat(tmp_path);
let etc_st = fs.stat(etc_path);
if (etc_st && etc_st.size > 0 && (!tmp_st || tmp_st.size == 0)) {
let content = fs.readfile(etc_path);
if (content)
fs.writefile(tmp_path, content);
}
}
dir.close();
}
function start_main() { function start_main() {
let status; let status;
@@ -719,6 +744,7 @@ function start_main() {
module_success(STATE_UC, [ "sync-time-if-needed" ]); module_success(STATE_UC, [ "sync-time-if-needed" ]);
status = module_status(SUBSCRIPTION_CACHE_UC, [ "ensure-runtime-dirs" ]); status = module_status(SUBSCRIPTION_CACHE_UC, [ "ensure-runtime-dirs" ]);
restore_rulesets_from_cache();
if (status != 0) if (status != 0)
return status; return status;
@@ -751,7 +777,8 @@ function start_main() {
module_success(BYEDPI_UC, [ "start-runtime" ]); module_success(BYEDPI_UC, [ "start-runtime" ]);
if (!command_success_from_args([ "/etc/init.d/sing-box", "start" ])) { let sb_start = command_success_from_args([ "/etc/init.d/sing-box", "start" ]);
if (!sb_start) {
log_message("Failed to start sing-box. Aborted.", "fatal"); log_message("Failed to start sing-box. Aborted.", "fatal");
return 1; return 1;
} }
@@ -790,6 +817,7 @@ function start_main() {
} }
function start_impl() { function start_impl() {
fs.writefile("/var/run/tachyon/starting", as_string(time()));
// (e.g. config generator killed during restart). 30-min threshold is safe since // (e.g. config generator killed during restart). 30-min threshold is safe since
// all tachyon temp files are created and deleted within seconds under normal operation. // all tachyon temp files are created and deleted within seconds under normal operation.
command_status("find /tmp -maxdepth 1 -name 'tachyon-*' -mmin +30 -delete 2>/dev/null; true"); command_status("find /tmp -maxdepth 1 -name 'tachyon-*' -mmin +30 -delete 2>/dev/null; true");
@@ -840,6 +868,7 @@ function start_impl() {
module_background(DIAGNOSTICS_UC, [ "get-system-info" ]); module_background(DIAGNOSTICS_UC, [ "get-system-info" ]);
module_background(TELEGRAM_UC, [ "start-runtime" ]); module_background(TELEGRAM_UC, [ "start-runtime" ]);
fs.unlink("/var/run/tachyon/starting");
return 0; return 0;
} }
@@ -854,7 +883,7 @@ function stop_main() {
module_success(SUBSCRIPTION_CACHE_UC, [ "stop-deferred-bootstrap-worker" ]); module_success(SUBSCRIPTION_CACHE_UC, [ "stop-deferred-bootstrap-worker" ]);
module_success(UPDATES_UC, [ "stop-list-update" ]); module_success(UPDATES_UC, [ "stop-list-update" ]);
remove_cron_jobs(); remove_cron_jobs();
command_success_from_args([ "find", TMP_RULESET_FOLDER, "-mindepth", "1", "-maxdepth", "1", "-type", "f", "-delete" ]); // command_success_from_args([ "find", TMP_RULESET_FOLDER, "-mindepth", "1", "-maxdepth", "1", "-type", "f", "-delete" ]);
if (!module_success(ZAPRET_UC, [ "stop-runtime" ])) if (!module_success(ZAPRET_UC, [ "stop-runtime" ]))
log_message("Zapret stop failed (non-fatal)", "warn"); log_message("Zapret stop failed (non-fatal)", "warn");
@@ -887,6 +916,8 @@ function stop_main() {
} }
function cleanup_failed_runtime() { function cleanup_failed_runtime() {
fs.unlink("/var/run/tachyon/starting");
fs.unlink("/var/run/tachyon/reloading");
let status = 0; let status = 0;
log_message("Cleaning up Tachyon runtime after failed start/reload", "info"); log_message("Cleaning up Tachyon runtime after failed start/reload", "info");
+3 -1
View File
@@ -708,6 +708,8 @@ function safe_proxy_restart(reason, force_level) {
// ─── Reload dedup: prevent multiple reload_firewall per cycle ───────────────── // ─── Reload dedup: prevent multiple reload_firewall per cycle ─────────────────
function safe_reload_firewall() { function safe_reload_firewall() {
if (fs.stat("/var/run/tachyon/starting") != null || fs.stat("/var/run/tachyon/reloading") != null)
return;
let now = time(); let now = time();
let min_interval = 120; let min_interval = 120;
if (now - last_reload_time < min_interval) return; if (now - last_reload_time < min_interval) return;
@@ -810,7 +812,7 @@ function ai_heal_subnet_cache() {
let restored = []; let restored = [];
let entry; let entry;
while ((entry = dir.read()) != null) { while ((entry = dir.read()) != null) {
if (!match(entry, /^community-subnets-.+\.lst$/)) continue; if (!match(entry, /\.(srs|lst|json)$/)) continue;
let tmp_path = tmp_dir + "/" + entry; let tmp_path = tmp_dir + "/" + entry;
let etc_path = etc_dir + "/" + entry; let etc_path = etc_dir + "/" + entry;
let tmp_st = fs.stat(tmp_path); let tmp_st = fs.stat(tmp_path);
+28
View File
@@ -771,6 +771,33 @@ function restore_dns_config(backup_path) {
return command_success_from_args([ "mv", "-f", backup_path, config_path ]); return command_success_from_args([ "mv", "-f", backup_path, config_path ]);
} }
function ensure_local_rulesets_exist(temp_config) {
try {
let cfg_raw = as_string(fs.readfile(temp_config) || "");
let cfg_obj = length(cfg_raw) > 0 ? json(cfg_raw) : null;
if (type(cfg_obj) == "object" && type(cfg_obj.route) == "object" && type(cfg_obj.route.rule_set) == "array") {
command_success_from_args(["mkdir", "-p", TMP_RULESET_FOLDER]);
for (let rs in cfg_obj.route.rule_set) {
if (type(rs) == "object" && rs.type == "local" && rs.path) {
let st = fs.stat(rs.path);
if (!st || st.size == 0) {
let bname = replace(rs.path, /^.*\//, "");
let etc_file = "/etc/tachyon/rulesets/" + bname;
let etc_st = fs.stat(etc_file);
if (etc_st && etc_st.size > 0) {
let content = fs.readfile(etc_file);
if (content) fs.writefile(rs.path, content);
} else if (rs.format == "source" || match(rs.path, /\.json$/)) {
fs.writefile(rs.path, sprintf("%J", { version: 3, rules: [] }));
}
}
}
}
}
} catch (e) {}
}
function init_config(populate_nft, caches_prepared, no_refresh) { function init_config(populate_nft, caches_prepared, no_refresh) {
let settings = uci_settings(); let settings = uci_settings();
let config_path = option(settings, "config_path", ""); let config_path = option(settings, "config_path", "");
@@ -827,6 +854,7 @@ function init_config(populate_nft, caches_prepared, no_refresh) {
} }
log_file_lines(runtime_log, "warn", "sing-box config generator: "); log_file_lines(runtime_log, "warn", "sing-box config generator: ");
ensure_local_rulesets_exist(temp_config);
let check_result = sing_box_check(temp_config, runtime_log); let check_result = sing_box_check(temp_config, runtime_log);
while (check_result.status != 0) { while (check_result.status != 0) {
// Graceful degradation: if the installed sing-box does not support an inbound // Graceful degradation: if the installed sing-box does not support an inbound
+1 -1
View File
@@ -7,7 +7,7 @@
set -u set -u
UNINSTALLER_VERSION="1.2.86" UNINSTALLER_VERSION="1.2.88"
# ─── TUI helpers & Color detection ─────────────────────────────────────────── # ─── TUI helpers & Color detection ───────────────────────────────────────────
ESC="$(printf '\033')" ESC="$(printf '\033')"