release: 1.3.7 - Telegram alert deduplication & rate limiting, DNS detour deadlock resolution, ucode fs.opendir fixes

This commit is contained in:
Dushnilin
2026-08-29 21:28:41 +03:00
parent 939d9418a2
commit 123f5e6ccf
13 changed files with 73 additions and 51 deletions
+1 -1
View File
@@ -2254,9 +2254,9 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10239",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803",
"src/partials/modal/renderModal.ts:263",
"src/tachyon/tabs/diagnostic/initController.ts:1553",
"src/tachyon/tabs/monitoring/initController.ts:893",
"src/tachyon/tabs/monitoring/initController.ts:948",
"src/tachyon/tabs/diagnostic/initController.ts:1553",
"src/tachyon/tabs/dashboard/initController.ts:1267",
"src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:227",
"src/tachyon/tabs/diagnostic/partials/renderServiceCheckModal.ts:178",
+3 -3
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-29 10:56+0300\n"
"PO-Revision-Date: 2026-08-29 10:56+0300\n"
"POT-Creation-Date: 2026-08-29 18:28+0300\n"
"PO-Revision-Date: 2026-08-29 18:28+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -1421,9 +1421,9 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10239
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803
#: src/partials/modal/renderModal.ts:263
#: src/tachyon/tabs/diagnostic/initController.ts:1553
#: src/tachyon/tabs/monitoring/initController.ts:893
#: src/tachyon/tabs/monitoring/initController.ts:948
#: src/tachyon/tabs/diagnostic/initController.ts:1553
#: src/tachyon/tabs/dashboard/initController.ts:1267
#: src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:227
#: src/tachyon/tabs/diagnostic/partials/renderServiceCheckModal.ts:178
+2 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-29 13:56+0300\n"
"PO-Revision-Date: 2026-08-29 13:56+0300\n"
"POT-Creation-Date: 2026-08-29 21:28+0300\n"
"PO-Revision-Date: 2026-08-29 21:28+0300\n"
"Last-Translator: Dushnilin\n"
"Language-Team: none\n"
"Language: ru\n"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "fe-app-tachyon",
"version": "1.3.6",
"version": "1.3.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fe-app-tachyon",
"version": "1.3.6",
"version": "1.3.7",
"license": "MIT",
"devDependencies": {
"@babel/parser": "7.28.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "fe-app-tachyon",
"version": "1.3.6",
"version": "1.3.7",
"license": "MIT",
"type": "module",
"scripts": {
+2 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-29 13:56+0300\n"
"PO-Revision-Date: 2026-08-29 13:56+0300\n"
"POT-Creation-Date: 2026-08-29 21:28+0300\n"
"PO-Revision-Date: 2026-08-29 21:28+0300\n"
"Last-Translator: Dushnilin\n"
"Language-Team: none\n"
"Language: ru\n"
+3 -3
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-29 10:56+0300\n"
"PO-Revision-Date: 2026-08-29 10:56+0300\n"
"POT-Creation-Date: 2026-08-29 18:28+0300\n"
"PO-Revision-Date: 2026-08-29 18:28+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -1421,9 +1421,9 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10239
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:1803
#: src/partials/modal/renderModal.ts:263
#: src/tachyon/tabs/diagnostic/initController.ts:1553
#: src/tachyon/tabs/monitoring/initController.ts:893
#: src/tachyon/tabs/monitoring/initController.ts:948
#: src/tachyon/tabs/diagnostic/initController.ts:1553
#: src/tachyon/tabs/dashboard/initController.ts:1267
#: src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts:227
#: src/tachyon/tabs/diagnostic/partials/renderServiceCheckModal.ts:178
@@ -559,19 +559,20 @@ function stop_runtime() {
for (let section in native_tailscale_sections())
stop_section(section);
// Also clean leftovers for sections that disappeared from the config.
let entries = null;
try {
entries = fs.readdir(RUNTIME_DIR);
} catch (e) {
entries = null;
}
if (entries != null) {
for (let entry in entries) {
let pid_file = RUNTIME_DIR + "/" + as_string(entry) + "/tailscaled.pid";
let pid = read_pid(pid_file);
if (pid_alive(pid))
command_success_from_args([ "kill", as_string(pid) ]);
let dir = fs.opendir(RUNTIME_DIR);
if (dir) {
let entry;
while ((entry = dir.read()) != null) {
if (entry == "." || entry == "..") continue;
let pid_file = RUNTIME_DIR + "/" + as_string(entry) + "/tailscaled.pid";
let pid = read_pid(pid_file);
if (pid_alive(pid))
command_success_from_args([ "kill", as_string(pid) ]);
}
dir.close();
}
} catch (e) {
}
remove_kernel_routing(false);
return 0;
+5 -3
View File
@@ -1019,21 +1019,23 @@ function prepare_server_defaults(section) {
function cleanup_orphaned_server_certs() {
let cert_dir = "/etc/tachyon/server-certs";
try {
let entries = fs.readdir(cert_dir);
if (entries == null) return;
let dir = fs.opendir(cert_dir);
if (!dir) return;
let active_names = {};
for (let section in uci_sections("server")) {
let name = safe_filename_string(section);
active_names[name + ".crt"] = true;
active_names[name + ".key"] = true;
}
for (let entry in entries) {
let entry;
while ((entry = dir.read()) != null) {
if (substr(entry, 0, 1) == ".") continue;
if (active_names[entry]) continue;
let path = cert_dir + "/" + entry;
// Absent file already satisfies the caller; fs.unlink throws on ENOENT.
try { fs.unlink(path); } catch(e) {}
}
dir.close();
}
catch (e) {
// Best-effort housekeeping: a missing cert directory or an unreadable
@@ -973,19 +973,26 @@ function controller(bus, opts) {
for (let i = 0; i < length(pid_dirs); i++) {
let child_dir = pid_dirs[i];
let child_entries = fs.readdir(child_dir);
if (child_entries == null) continue;
let expected = length(child_entries);
if (expected == 0) continue;
let dir = fs.opendir(child_dir);
if (!dir) continue;
let expected = 0;
let running = 0;
for (let entry in child_entries) {
let entry;
while ((entry = dir.read()) != null) {
if (entry == "." || entry == "..") continue;
expected++;
let pid_data = fs.readfile(child_dir + "/" + entry);
if (pid_data == null) continue;
let pid = trim(as_string(pid_data));
if (pid == "") continue;
try { system("kill -0 " + pid + " 2>/dev/null"); running++; } catch (e) {}
if (pid == "" || match(pid, /^[0-9]+$/) == null) continue;
if (fs.stat("/proc/" + pid) != null) {
running++;
}
}
dir.close();
if (expected == 0) continue;
if (running == expected) {
bus.emit(EV.NFQUEUE_UP, { running: running, expected: expected });
+26 -14
View File
@@ -24,6 +24,7 @@ let proxy_restart_window_start = time();
const PROXY_RESTART_LOCK = "/var/run/tachyon_proxy_restart.lock";
let telegram_msg_count = 0;
let telegram_msg_window = time();
let telegram_msg_history = {};
let tailscale_fail_streak = 0;
let tailscale_last_alert = 0;
@@ -136,8 +137,17 @@ function write_state_file(path, value, description) {
return true;
}
function send_telegram_notification(message) {
function send_telegram_notification(message, cooldown_key, cooldown_seconds) {
let now = time();
if (cooldown_key != null && cooldown_key != "") {
let last_sent = int(telegram_msg_history[cooldown_key] || 0);
let cooldown = cooldown_seconds != null ? int(cooldown_seconds) : 900;
if (now - last_sent < cooldown) {
return;
}
telegram_msg_history[cooldown_key] = now;
}
if (now - telegram_msg_window > 300) {
telegram_msg_count = 0;
telegram_msg_window = now;
@@ -592,15 +602,16 @@ function ai_heal_report(event_type, description, resolution, outcome) {
// A repair still in flight is not news: notifying on `pending` would send
// one message on the attempt and a second on the outcome. The settled
// report carries the whole story, so only that one is sent.
// Repairs that were skipped (rate limit, lock, guard) perform no action,
// so notifying on `skipped` would flood notifications on every probe tick.
let tcfg = common.object_or_empty(uci_core.get_all(CONFIG_NAME, "telegram"));
if (status_code != "pending" &&
if (status_code != "pending" && status_code != "skipped" &&
tcfg.enabled == "1" && tcfg.bot_token && tcfg.admin_ids && tcfg.notify_crash != "0") {
let icon = status_code == "failed" ? "❌" : "🔧";
let verdict = status_code == "failed" ? "*Не помогло:*" :
(status_code == "skipped" ? "*Пропущено:*" : "*Авто-решение:*");
let verdict = status_code == "failed" ? "*Не помогло:*" : "*Авто-решение:*";
let tg_msg = sprintf("🤖 *[ИИ-Автомеханик Tachyon]*\n⚠️ *Проблема:* %s\n%s %s %s",
description, icon, verdict, resolution);
send_telegram_notification(tg_msg);
send_telegram_notification(tg_msg, "ai_heal_" + as_string(event_type) + "_" + status_code, 900);
}
ai_export_status();
@@ -877,7 +888,7 @@ function heal_wan_and_gateway(ev) {
if (settings().recovery_bypass == "1") return;
let tcfg = common.object_or_empty(uci_core.get_all(CONFIG_NAME, "telegram"));
if (tcfg.notify_crash != "0") {
send_telegram_notification("⚠️ *Watchdog:* WAN/Gateway проблема. Перезапуск wan...");
send_telegram_notification("⚠️ *Watchdog:* WAN/Gateway проблема. Перезапуск wan...", "heal_wan_and_gateway", 600);
}
// Claimed before the interface goes down, not after: ifdown itself is what
// makes the proxy and DNS probes fail, and those facts arrive while ifup is
@@ -908,14 +919,14 @@ function note_wan_recovered(ev) {
function heal_subscriptions(ev) {
let tcfg = common.object_or_empty(uci_core.get_all(CONFIG_NAME, "telegram"));
if (tcfg.notify_crash != "0") {
send_telegram_notification("⚠️ *Watchdog:* Подписка недоступна (HTTP " + int(ev.payload.code) + "). Обновите подписку вручную.");
send_telegram_notification("⚠️ *Watchdog:* Подписка недоступна (HTTP " + int(ev.payload.code) + "). Обновите подписку вручную.", "heal_subscriptions", 1800);
}
}
function heal_uci_config(ev) {
let tcfg = common.object_or_empty(uci_core.get_all(CONFIG_NAME, "telegram"));
if (tcfg.notify_crash != "0") {
send_telegram_notification("⚠️ *Watchdog:* Конфигурация Tachyon повреждена! Восстановление из backup...");
send_telegram_notification("⚠️ *Watchdog:* Конфигурация Tachyon повреждена! Восстановление из backup...", "heal_uci_config", 1800);
}
let backup = fs.readfile("/etc/backup/tachyon_config");
if (backup == null || backup == "") return;
@@ -1148,24 +1159,25 @@ function ai_heal_dns_loop() {
let urls = common.array_or_empty(cache.urls);
let usable = length(servers) + length(urls);
if (usable > 0) {
// Section has outbounds but DNS is still dead — may be transient
let dns_fails = controller.dns_consecutive_fails ? controller.dns_consecutive_fails() : 0;
if (usable > 0 && dns_fails < 3) {
// Section has outbounds but DNS failure is transient (< 3 consecutive fails)
return;
}
// Detour section empty AND DNS dead — disable DNS detour to recover
log_message("DNS loop detected: DNS detour section '" + detour_section + "' is empty, disabling DNS detour to recover", "warn");
// Detour section empty OR deadlocked with persistent DNS failure — disable DNS detour to recover
log_message("DNS loop detected: DNS detour section '" + detour_section + "' causing DNS failure (usable: " + as_string(usable) + ", dns_fails: " + as_string(dns_fails) + "), disabling DNS detour to recover", "warn");
system("/sbin/uci set tachyon.settings.dns_detour_enabled='0' >/dev/null 2>&1");
system("/sbin/uci commit tachyon >/dev/null 2>&1");
safe_proxy_restart("dns_loop_disable", ESCALATION_HEAVY);
write_dns_recovery_state({ phase: "detour_disabled", section: detour_section, ts: now });
// Trigger subscription update for the empty section
// Trigger subscription update for the detour section
bg_system("/usr/bin/tachyon subscription_update_async " + common.shell_quote(detour_section));
ai_heal_report(
"dns_loop_detected",
"DNS loop: detour section '" + detour_section + "' has 0 outbounds, DNS completely dead",
"DNS loop: detour section '" + detour_section + "' causing DNS failure (" + as_string(usable) + " outbounds, " + as_string(dns_fails) + " consecutive fails)",
"Temporarily disabled DNS detour to restore DNS, triggered subscription reload",
"fixed"
);
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -124,7 +124,7 @@ grep -q 'fuzzer_generate:' "$TACHYON_BIN" || fail "tachyon CLI missing fuzzer_ge
grep -q 'fuzzer_ai_synthesize:' "$TACHYON_BIN" || fail "tachyon CLI missing fuzzer_ai_synthesize"
# 5. Check combinatorial strategies generation
combo_tmp="$(mktemp -t fuzzer_combo_XXXXXX.json)"
combo_tmp="$(mktemp "${TMPDIR:-/tmp}/fuzzer_combo_XXXXXX")"
trap 'rm -f "$combo_tmp"' EXIT
ucode -L "$TACHYON_LIB" -- "$FUZZER" strategies combinatorial > "$combo_tmp"
COMBO_FILE="$combo_tmp" node <<'NODE'