release: 1.3.7 - Telegram alert deduplication & rate limiting, DNS detour deadlock resolution, ucode fs.opendir fixes
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Generated
+2
-2
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "fe-app-tachyon",
|
||||
"version": "1.3.6",
|
||||
"version": "1.3.7",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user