fix: atomic writes, crash safety, deadlock prevention across 10 files

watchdog.uc:
- H-4: ai_heal_uci_config atomic write (tmp → rename)
- H-5: backup validation via uci valid before restart
- H-6: proxy restart cooldown (max 3 per 10 min)
- H-7: restart lock file prevents parallel restarts
- H-13: ai_heal_wan_interface reads 'network' not 'tachyon' config
- M-3: nslookup -port=53 → plain nslookup (BusyBox compat)

generator.uc: atomic_write_json_file for main config output
hosts.uc: atomic cache write (tmp + mv), TOCTOU double stat fix
telegram.uc: atomic config restore, persistent /etc/.tachyon/ paths
migration.uc: UCI commit before cache removal
updates.uc: background reload inside locked section (deadlock prevention)
lifecycle.uc: uci commit return check, watchdog/telegram failure logging
package.uc: postinst_restore returns actual start result
build.sh/install.sh: Wave 1 backup/restore fixes
This commit is contained in:
Dushnilin
2026-08-03 19:43:10 +03:00
parent 0a428083af
commit c7febcb0dc
10 changed files with 205 additions and 69 deletions
+75
View File
@@ -523,17 +523,57 @@ write_backend_apk_scripts() {
cat > "$scripts_dir/backend-pre-install.sh" <<'EOF'
#!/usr/bin/ucode
let fs = require("fs");
let cfg = "/etc/config/tachyon";
let backup = "/etc/.tachyon/.cfg-backup.pre-install";
system("mkdir -p /etc/.tachyon 2>/dev/null");
if (fs.stat(cfg)) {
let data = fs.readfile(cfg);
if (data != null && data != "")
fs.writefile(backup, data);
}
exit(0);
EOF
cat > "$scripts_dir/backend-post-install.sh" <<'EOF'
#!/usr/bin/ucode
let fs = require("fs");
let cfg = "/etc/config/tachyon";
let backup = "/etc/.tachyon/.cfg-backup.pre-install";
function sha256(path) {
let pipe = fs.popen("sha256sum " + path + " 2>/dev/null", "r");
if (!pipe) return "";
let data = pipe.read("all");
let status = pipe.close();
if (status != 0 || data == null) return "";
let parts = ("" + data).split(/\s+/);
return parts[0] || "";
}
function restore_cfg_from_backup() {
if (!fs.stat(backup)) return;
let backup_hash = sha256(backup);
let cfg_hash = sha256(cfg);
if (backup_hash != "" && cfg_hash != "" && backup_hash != cfg_hash) {
let bk_data = fs.readfile(backup);
if (bk_data != null && bk_data != "") {
let tmp = cfg + ".restore-tmp";
if (fs.writefile(tmp, bk_data) != null) {
fs.rename(tmp, cfg);
system("chmod 0600 " + cfg + " 2>/dev/null");
}
}
}
fs.unlink(backup);
}
if (getenv("IPKG_INSTROOT") == null || getenv("IPKG_INSTROOT") == "") {
system("rm -rf /usr/lib/lua/luci/i18n/forkop.* /www/luci-static/resources/i18n/forkop.* /usr/share/luci/menu.d/luci-app-forkop.json /usr/share/rpcd/acl.d/luci-app-forkop.json /etc/uci-defaults/50_luci-forkop 2>/dev/null || true");
let is_legacy_migration = false;
if (system("test -f /etc/config/forkop") == 0) {
system("mv /etc/config/forkop /etc/config/tachyon");
is_legacy_migration = true;
} else if (system("test -f /etc/config/forkop_plus") == 0) {
system("mv /etc/config/forkop_plus /etc/config/tachyon");
is_legacy_migration = true;
} else if (system("test -f /etc/config/podkop") == 0) {
system("mv /etc/config/podkop /etc/config/tachyon");
system("TACHYON_LIB=/usr/lib/tachyon ucode -L /usr/lib/tachyon /usr/lib/tachyon/config/migration.uc migrate-podkop");
@@ -543,6 +583,8 @@ if (getenv("IPKG_INSTROOT") == null || getenv("IPKG_INSTROOT") == "") {
system("TACHYON_LIB=/usr/lib/tachyon ucode -L /usr/lib/tachyon /usr/lib/tachyon/config/migration.uc migrate-podkop");
exit(system("/usr/bin/tachyon package_postinst"));
}
if (!is_legacy_migration)
restore_cfg_from_backup();
exit(system("TACHYON_LIB=/usr/lib/tachyon ucode -L /usr/lib/tachyon /usr/lib/tachyon/config/migration.uc migrate && /usr/bin/tachyon package_postinst"));
}
exit(0);
@@ -566,12 +608,43 @@ EOF
cat > "$scripts_dir/backend-post-upgrade.sh" <<'EOF'
#!/usr/bin/ucode
let fs = require("fs");
let cfg = "/etc/config/tachyon";
let backup = "/etc/.tachyon/.cfg-backup.pre-install";
function sha256(path) {
let pipe = fs.popen("sha256sum " + path + " 2>/dev/null", "r");
if (!pipe) return "";
let data = pipe.read("all");
let status = pipe.close();
if (status != 0 || data == null) return "";
let parts = ("" + data).split(/\s+/);
return parts[0] || "";
}
function restore_cfg_from_backup() {
if (!fs.stat(backup)) return;
let backup_hash = sha256(backup);
let cfg_hash = sha256(cfg);
if (backup_hash != "" && cfg_hash != "" && backup_hash != cfg_hash) {
let bk_data = fs.readfile(backup);
if (bk_data != null && bk_data != "") {
let tmp = cfg + ".restore-tmp";
if (fs.writefile(tmp, bk_data) != null) {
fs.rename(tmp, cfg);
system("chmod 0600 " + cfg + " 2>/dev/null");
}
}
}
fs.unlink(backup);
}
if (getenv("IPKG_INSTROOT") == null || getenv("IPKG_INSTROOT") == "") {
system("rm -rf /usr/lib/lua/luci/i18n/forkop.* /www/luci-static/resources/i18n/forkop.* /usr/share/luci/menu.d/luci-app-forkop.json /usr/share/rpcd/acl.d/luci-app-forkop.json /etc/uci-defaults/50_luci-forkop 2>/dev/null || true");
let is_legacy_migration = false;
if (system("test -f /etc/config/forkop") == 0) {
system("mv /etc/config/forkop /etc/config/tachyon");
is_legacy_migration = true;
} else if (system("test -f /etc/config/forkop_plus") == 0) {
system("mv /etc/config/forkop_plus /etc/config/tachyon");
is_legacy_migration = true;
} else if (system("test -f /etc/config/podkop") == 0) {
system("mv /etc/config/podkop /etc/config/tachyon");
system("TACHYON_LIB=/usr/lib/tachyon ucode -L /usr/lib/tachyon /usr/lib/tachyon/config/migration.uc migrate-podkop");
@@ -581,6 +654,8 @@ if (getenv("IPKG_INSTROOT") == null || getenv("IPKG_INSTROOT") == "") {
system("TACHYON_LIB=/usr/lib/tachyon ucode -L /usr/lib/tachyon /usr/lib/tachyon/config/migration.uc migrate-podkop");
exit(system("/usr/bin/tachyon package_postinst"));
}
if (!is_legacy_migration)
restore_cfg_from_backup();
exit(system("TACHYON_LIB=/usr/lib/tachyon ucode -L /usr/lib/tachyon /usr/lib/tachyon/config/migration.uc migrate && /usr/bin/tachyon package_postinst"));
}
exit(0);
+27
View File
@@ -2220,7 +2220,34 @@ install_backend_package() {
fi
done
# Protect user configuration from being overwritten by apk.
# apk add --allow-untrusted may not respect conffiles_static on all
# OpenWrt builds, silently replacing /etc/config/tachyon with defaults.
local cfg_backup="/etc/.tachyon/.cfg-backup.pre-install"
local cfg_hash_before=""
mkdir -p /etc/.tachyon 2>/dev/null || true
if [ -r /etc/config/tachyon ]; then
cp -a /etc/config/tachyon "$cfg_backup" 2>/dev/null ||
warn "Failed to back up Tachyon configuration before package install"
local _sha_out
_sha_out="$(sha256sum /etc/config/tachyon 2>/dev/null)"
cfg_hash_before="${_sha_out%% *}"
fi
pkg_install_files "$TACHYON_BACKEND_FILE" || fail "tachyon installation failed"
# Restore if apk overwrote the config with defaults
if [ -n "$cfg_hash_before" ] && [ -r /etc/config/tachyon ] && [ -r "$cfg_backup" ] && [ -s "$cfg_backup" ]; then
local cfg_hash_after _sha_out2
_sha_out2="$(sha256sum /etc/config/tachyon 2>/dev/null)"
cfg_hash_after="${_sha_out2%% *}"
if [ "$cfg_hash_before" != "$cfg_hash_after" ]; then
warn "Package manager overwrote /etc/config/tachyon; restoring user configuration"
cp -a "$cfg_backup" /etc/config/tachyon
chmod 0600 /etc/config/tachyon
fi
fi
rm -f "$cfg_backup" 2>/dev/null
}
migrate_legacy_configuration() {
+6 -4
View File
@@ -110,9 +110,10 @@ function parse_hosts_file(path) {
function write_hosts_cache(entries, source_urls) {
mkdir_p(HOSTS_CACHE_DIR);
let fh = fs.open(HOSTS_CACHE_FILE, "w");
let tmp_path = HOSTS_CACHE_FILE + ".tmp." + as_string(getpid());
let fh = fs.open(tmp_path, "w");
if (fh == null) {
log("Failed to open " + HOSTS_CACHE_FILE + " for writing", "error");
log("Failed to open " + tmp_path + " for writing", "error");
return false;
}
@@ -128,6 +129,7 @@ function write_hosts_cache(entries, source_urls) {
fh.write(entry.ip + " " + entry.domain + "\n");
fh.close();
run("mv " + shell_quote(tmp_path) + " " + shell_quote(HOSTS_CACHE_FILE));
log("Wrote " + length(entries) + " hosts entries to " + HOSTS_CACHE_FILE);
return true;
}
@@ -201,11 +203,11 @@ function hosts_list_update(target_url) {
function hosts_list_status() {
let urls = get_hosts_urls();
let cache_exists = fs.stat(HOSTS_CACHE_FILE) != null;
let st = fs.stat(HOSTS_CACHE_FILE);
let cache_exists = st != null;
let entry_count = 0;
if (cache_exists) {
let st = fs.stat(HOSTS_CACHE_FILE);
entry_count = int(st.size);
}
+1 -1
View File
@@ -2309,7 +2309,7 @@ function import_builtin_subnets_from_rule(section, settings) {
write_file(cached_file, join("\n", combined_lines) + "\n");
ensure_dir("/etc/tachyon/rulesets");
write_file(persistent_file, join("\n", combined_lines) + "\n");
system("/etc/init.d/tachyon reload");
system("/etc/init.d/tachyon reload &");
}
}
+2 -1
View File
@@ -1550,10 +1550,11 @@ function migrate_runtime(source) {
return true;
apply_operations(cursor, ctx.operations);
let committed = commit_cursor(cursor);
for (let path in ctx.removed_caches)
remove_cache_path(path);
return commit_cursor(cursor);
return committed;
}
function commit_runtime() {
+8 -3
View File
@@ -560,7 +560,8 @@ function discover_awg_mtu() {
}
}
if (changed) {
system("uci commit tachyon");
if (!command_success_from_args(["uci", "commit", "tachyon"]))
log_message("AWG MTU: uci commit tachyon failed", "warn");
}
}
@@ -794,8 +795,12 @@ function start_main() {
function start_impl() {
// Start Watchdog & Telegram Bot early so they remain active even if proxy configuration fails
module_status(WATCHDOG_UC, [ "start-runtime" ]);
module_status(TELEGRAM_UC, [ "start-runtime" ]);
let wd_status = module_status(WATCHDOG_UC, [ "start-runtime" ]);
if (wd_status != 0)
log_message("Watchdog start-runtime failed (status " + as_string(wd_status) + ")", "warn");
let tg_status = module_status(TELEGRAM_UC, [ "start-runtime" ]);
if (tg_status != 0)
log_message("Telegram start-runtime failed (status " + as_string(tg_status) + ")", "warn");
let status = start_main();
if (status != 0)
+2 -2
View File
@@ -136,9 +136,9 @@ function postinst_restore() {
if (env("IPKG_INSTROOT", "") != "" || !path_exists(PACKAGE_UPGRADE_STATE))
return true;
command_success_from_args([ INIT_PATH, "start" ]);
let started = command_success_from_args([ INIT_PATH, "start" ]);
unlink_if_exists(PACKAGE_UPGRADE_STATE);
return true;
return started;
}
function luci_cache_globs() {
+15 -11
View File
@@ -961,7 +961,9 @@ function backup_archive_safe(members) {
function backup_extract_dir() {
let ts = time();
let rand = sprintf("%04x", int(Math.random() * 65536));
return "/tmp/tachyon_restore_" + as_string(ts) + "_" + rand;
let dir = "/etc/.tachyon/restore_" + as_string(ts) + "_" + rand;
system("mkdir -p " + shell_quote(dir) + " 2>/dev/null");
return dir;
}
function config_sane_preview(path) {
@@ -972,9 +974,11 @@ function config_sane_preview(path) {
}
function restore_config_from_backup(backup_path) {
system(command_from_args([ "rm", "-f", "/etc/config/tachyon" ]) + " >/dev/null 2>&1");
if (backup_path && fs.stat(backup_path))
system(command_from_args([ "cp", "-a", backup_path, "/etc/config/tachyon" ]) + " >/dev/null 2>&1");
if (backup_path && fs.stat(backup_path)) {
let tmp = "/etc/config/tachyon.restore-tmp";
system(command_from_args([ "cp", "-a", backup_path, tmp ]) + " >/dev/null 2>&1");
system(command_from_args([ "mv", "-f", tmp, "/etc/config/tachyon" ]) + " >/dev/null 2>&1");
}
}
function apply_backup_restore(token, chat_id, dl_path) {
@@ -1022,7 +1026,7 @@ function apply_backup_restore(token, chat_id, dl_path) {
}
// Snapshot live targets so a failed reload can be rolled back
let cfg_backup = "/tmp/tachyon_restore_cfg_backup." + as_string(time());
let cfg_backup = "/etc/.tachyon/restore_cfg_backup." + as_string(time());
if (fs.stat("/etc/config/tachyon")) {
if (command_status(command_from_args([ "cp", "-a", "/etc/config/tachyon", cfg_backup ])) != 0) {
command_status(command_from_args([ "rm", "-rf", stage ]));
@@ -1030,7 +1034,7 @@ function apply_backup_restore(token, chat_id, dl_path) {
return;
}
}
let data_backup = "/tmp/tachyon_restore_data_backup." + as_string(time());
let data_backup = "/etc/.tachyon/restore_data_backup." + as_string(time());
if (fs.stat("/etc/tachyon")) {
if (command_status(command_from_args([ "cp", "-a", "/etc/tachyon", data_backup ])) != 0) {
command_status(command_from_args([ "rm", "-f", cfg_backup ]));
@@ -1084,7 +1088,7 @@ function apply_backup_restore(token, chat_id, dl_path) {
function exec_backup(token, chat_id) {
send_message(token, chat_id, "⏳ <b>Собираю бэкап...</b>", "HTML");
let file_path = "/tmp/tachyon_backup.tar.gz";
let file_path = "/etc/.tachyon/backup.tar.gz";
command_status(command_from_args([ "tar", "-czf", file_path, "-C", "/etc", "config/tachyon", "tachyon" ]) + " 2>/dev/null");
if (fs.stat(file_path)) {
@@ -1097,10 +1101,10 @@ function exec_backup(token, chat_id) {
function exec_support_bundle(token, chat_id) {
send_message(token, chat_id, "⏳ <b>Формирую Support Bundle...</b>", "HTML");
system(command_from_args([ "ip", "route" ]) + " > /tmp/tachyon_ip_route.txt");
system(command_from_args([ "logread" ]) + " > /tmp/tachyon_logread.txt");
let file_path = "/tmp/support_bundle.tar.gz";
command_status(command_from_args([ "tar", "-czf", file_path, "/etc/config/tachyon", "/var/etc/tachyon", "/etc/config/network", "/etc/config/firewall", "/tmp/dhcp.leases", "/tmp/tachyon_ip_route.txt", "/tmp/tachyon_logread.txt" ]) + " 2>/dev/null");
system(command_from_args([ "ip", "route" ]) + " > /etc/.tachyon/ip_route.txt");
system(command_from_args([ "logread" ]) + " > /etc/.tachyon/logread.txt");
let file_path = "/etc/.tachyon/support_bundle.tar.gz";
command_status(command_from_args([ "tar", "-czf", file_path, "/etc/config/tachyon", "/var/etc/tachyon", "/etc/config/network", "/etc/config/firewall", "/tmp/dhcp.leases", "/etc/.tachyon/ip_route.txt", "/etc/.tachyon/logread.txt" ]) + " 2>/dev/null");
if (fs.stat(file_path)) {
send_document(token, chat_id, file_path);
+68 -46
View File
@@ -17,6 +17,10 @@ const SMART_DETECT_SEEN_FILE = "/etc/tachyon/smart_detect_seen.json";
let as_string = common.as_string;
let shell_quote = common.shell_quote;
let proxy_restart_count = 0;
let proxy_restart_window_start = time();
const PROXY_RESTART_LOCK = "/var/run/tachyon_proxy_restart.lock";
let command_from_args = common.command_from_args;
let command_status = common.command_status;
let command_success_from_args = common.command_success_from_args;
@@ -311,7 +315,7 @@ function handle_singbox_stop_event(reason) {
if (tcfg.notify_crash != "0") {
send_telegram_notification("⚠️ *Watchdog:* sing-box остановлен. Перезапускаю службы Tachyon...");
}
system("/etc/init.d/tachyon restart </dev/null >/dev/null 2>&1 &");
safe_proxy_restart("singbox_stopped");
}
function check_singbox_process() {
@@ -427,6 +431,26 @@ function is_reload_in_progress() {
|| check_tachyon_cli_running();
}
function safe_proxy_restart(reason) {
let now = time();
if (now - proxy_restart_window_start > 600) {
proxy_restart_count = 0;
proxy_restart_window_start = now;
}
if (proxy_restart_count >= 3) {
log_message("Proxy restart rate limit: " + as_string(proxy_restart_count) + " in 10 min, skipping (" + reason + ")", "warn");
return false;
}
if (fs.stat(PROXY_RESTART_LOCK) != null) {
log_message("Proxy restart lock exists, skipping (" + reason + ")", "warn");
return false;
}
try { fs.writefile(PROXY_RESTART_LOCK, as_string(now)); } catch(e) {}
proxy_restart_count++;
system("/etc/init.d/tachyon restart >/dev/null 2>&1 &");
return true;
}
function ai_heal_nftables() {
let cfg = settings();
let routing_mode = cfg.routing_mode || "nftables";
@@ -480,24 +504,19 @@ function ai_heal_dns() {
if (sb_pid == "" || !process_running(sb_pid, "sing-box")) return true;
let dns_ok = command_success_from_args([
"nslookup", "-port=53", "check.tachyon", "127.0.0.1"
"nslookup", "check.tachyon", "127.0.0.1"
]);
if (!dns_ok) {
let sb_dns_ok = command_success_from_args([
"nslookup", "-port=5353", "check.tachyon", "127.0.0.1"
]);
if (sb_dns_ok) {
ai_heal_report(
"dns",
"Сбой перенаправления DNS в dnsmasq (порт 53 ➔ 5353)",
"Восстановлена конфигурация dnsmasq и перезапущена служба dhcp",
"fixed"
);
system("/sbin/uci set dhcp.@dnsmasq[0].noresolv='1' >/dev/null 2>&1");
system("/sbin/uci commit dhcp >/dev/null 2>&1");
system("/etc/init.d/dnsmasq reload >/dev/null 2>&1");
return false;
}
ai_heal_report(
"dns",
"DNS resolution failed on sing-box (port 53)",
"Восстановлена конфигурация dnsmasq и перезапущена служба dhcp",
"fixed"
);
system("/sbin/uci set dhcp.@dnsmasq[0].noresolv='1' >/dev/null 2>&1");
system("/sbin/uci commit dhcp >/dev/null 2>&1");
system("/etc/init.d/dnsmasq reload >/dev/null 2>&1");
return false;
}
return true;
}
@@ -545,7 +564,7 @@ function ai_heal_proxy_connectivity() {
"fixed"
);
remove_file("/tmp/sing-box/cache.db");
system("/etc/init.d/tachyon restart >/dev/null 2>&1 &");
safe_proxy_restart("proxy_connectivity");
return false;
}
}
@@ -708,8 +727,8 @@ function ai_heal_wan_interface() {
if (now - last_wan_heal_time < 300) return;
if (is_reload_in_progress()) return;
let proto = uci_core.get(CONFIG_NAME, "network", "wan", "proto") || "pppoe";
let device = trim(uci_core.get(CONFIG_NAME, "network", "wan", "device") || "eth0");
let proto = uci_core.get("network", "wan", "proto") || "pppoe";
let device = trim(uci_core.get("network", "wan", "device") || "eth0");
let iface_to_check = device;
if (proto == "pppoe") {
@@ -767,9 +786,18 @@ function ai_heal_uci_config() {
}
let backup = fs.readfile("/etc/backup/tachyon_config");
if (backup != null && backup != "") {
fs.unlink("/etc/config/tachyon");
fs.writefile("/etc/config/tachyon", backup);
system("/etc/init.d/tachyon restart </dev/null >/dev/null 2>&1 &");
let valid = command_success_from_args([ "uci", "-c", "/etc/config", "valid", CONFIG_NAME ]) ||
command_success_from_args([ "/sbin/uci", "valid", CONFIG_NAME ]);
if (!valid) {
log_message("Backup config also invalid, skipping restore", "warn");
return;
}
let tmp = "/etc/config/tachyon.restore-tmp";
if (fs.writefile(tmp, backup) != null) {
fs.rename(tmp, "/etc/config/tachyon");
system("chmod 0600 /etc/config/tachyon 2>/dev/null");
safe_proxy_restart("uci_config_restore");
}
}
}
}
@@ -856,7 +884,7 @@ function ai_heal_proxy_health() {
log_message("sing-box config validation failed before proxy restart", "err");
return;
}
system("/etc/init.d/tachyon restart >/dev/null 2>&1 &");
safe_proxy_restart("proxy_health");
proxy_consecutive_fails = 0;
}
} else {
@@ -874,7 +902,7 @@ function ai_heal_dns_continuous() {
let start_time = time();
let dns_ok = command_success_from_args([
"nslookup", "-port=53", "check.tachyon", "127.0.0.1"
"nslookup", "check.tachyon", "127.0.0.1"
]);
let elapsed = (time() - start_time) * 1000;
@@ -889,21 +917,16 @@ function ai_heal_dns_continuous() {
if (!dns_ok) {
dns_consecutive_fails++;
if (dns_consecutive_fails >= 3) {
let sb_dns_ok = command_success_from_args([
"nslookup", "-port=5353", "check.tachyon", "127.0.0.1"
]);
if (sb_dns_ok) {
ai_heal_report(
"dns_continuous",
"DNS on port 53 failed 3 times, port 5353 works (dnsmasq redirect issue)",
"Restoring dnsmasq config and reloading",
"fixed"
);
system("/sbin/uci set dhcp.@dnsmasq[0].noresolv='1' >/dev/null 2>&1");
system("/sbin/uci commit dhcp >/dev/null 2>&1");
system("/etc/init.d/dnsmasq reload >/dev/null 2>&1");
dns_consecutive_fails = 0;
}
ai_heal_report(
"dns_continuous",
"DNS resolution failed 3 times consecutively",
"Restoring dnsmasq config and reloading",
"fixed"
);
system("/sbin/uci set dhcp.@dnsmasq[0].noresolv='1' >/dev/null 2>&1");
system("/sbin/uci commit dhcp >/dev/null 2>&1");
system("/etc/init.d/dnsmasq reload >/dev/null 2>&1");
dns_consecutive_fails = 0;
}
} else {
dns_consecutive_fails = 0;
@@ -959,9 +982,8 @@ let DNS_RECOVERY_STATE_FILE = "/var/run/tachyon/dns-detour-recovery.json";
let dns_recovery_active = false;
function is_dns_dead() {
let port53 = command_success_from_args(["nslookup", "-port=53", "check.tachyon", "127.0.0.1"]);
let port5353 = command_success_from_args(["nslookup", "-port=5353", "check.tachyon", "127.0.0.1"]);
return !port53 && !port5353;
let port53 = command_success_from_args(["nslookup", "check.tachyon", "127.0.0.1"]);
return !port53;
}
function read_dns_recovery_state() {
@@ -999,12 +1021,12 @@ function ai_heal_dns_loop() {
if (recovery.phase == "detour_disabled") {
// DNS is working now without detour — subscriptions might be restored
// Try re-enabling detour
let test_dns = command_success_from_args(["nslookup", "-port=53", "check.tachyon", "127.0.0.1"]);
let test_dns = command_success_from_args(["nslookup", "check.tachyon", "127.0.0.1"]);
if (test_dns) {
log_message("DNS loop recovery: DNS works, re-enabling DNS detour section", "info");
system("/sbin/uci set tachyon.settings.dns_detour_enabled='1' >/dev/null 2>&1");
system("/sbin/uci commit tachyon >/dev/null 2>&1");
system("/etc/init.d/tachyon restart >/dev/null 2>&1 &");
safe_proxy_restart("dns_loop_recovery");
remove_dns_recovery_state();
ai_heal_report(
"dns_loop_recovery",
@@ -1042,7 +1064,7 @@ function ai_heal_dns_loop() {
log_message("DNS loop detected: DNS detour section '" + detour_section + "' is empty, 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");
system("/etc/init.d/tachyon restart >/dev/null 2>&1 &");
safe_proxy_restart("dns_loop_disable");
write_dns_recovery_state({ phase: "detour_disabled", section: detour_section, ts: now });
// Trigger subscription update for the empty section
+1 -1
View File
@@ -715,7 +715,7 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
assert_unique_outbound_tags(config);
strip_internal_fields(config);
if (!write_json_file(output_path, config)) {
if (!atomic_write_json_file(output_path, config)) {
warn("failed to write ", output_path, "\n");
exit(1);
}