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:
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 &");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user