fix(service): экспорт write_text_file и mixed-вотч по конфигу

- helpers.uc определяла write_text_file, но не экспортировала:
  биндинг в telegram-воркере был null, write_heartbeat падал каждый
  цикл — «Worker loop error: left-hand side is not a function»
  каждые ~25с, heartbeat не писался, воркер зря перезапускался
- watchdog: mixed-порт теперь читается из сгенерённого конфига
  sing-box (inbounds[].type=mixed), а не хардкод 4534. Когда mixed
  выключен (Main.mixed_proxy_enabled=0) — вотч встаёт в сторону
  вместо бесконечных «Restarting sing-box» с пересозданием nftables
  каждые ~5 минут
- проверено на живом роутере: 0 рестартов и 0 ошибок воркера после
  деплоя, E2E зелёный
This commit is contained in:
Dushnilin
2026-08-24 13:56:29 +03:00
parent 890450d619
commit 4b80deed5f
2 changed files with 63 additions and 6 deletions
+62 -6
View File
@@ -1729,15 +1729,62 @@ function worker() {
let mixed_port_fail_streak = 0;
let mixed_port_last_notify = 0;
function mixed_proxy_port_listening() {
// Mixed-proxy ports the running config actually declares. Reading the
// generated sing-box config keeps the watchdog in sync with the generator:
// when no section enables the mixed proxy there is nothing to guard, and
// demanding port 4534 anyway caused endless restart loops that rebuilt the
// nftables ruleset every few minutes.
function mixed_ports_from_config() {
let settings = common.object_or_empty(uci_core.get_all(CONFIG_NAME, "settings"));
let config_path = trim(as_string(settings.config_path || "")) || "/etc/sing-box/config.json";
let data = fs.readfile(config_path);
if (data == null)
return [];
let parsed;
try {
parsed = json(data);
} catch (e) {
return [];
}
if (parsed == null || parsed.inbounds == null)
return [];
let ports = [];
for (let inbound in parsed.inbounds) {
if (inbound.type != "mixed" || inbound.listen_port == null)
continue;
let port = int(inbound.listen_port, 10);
if (port > 0) {
let known = false;
for (let p in ports)
if (p == port) { known = true; break; }
if (!known)
push(ports, port);
}
}
return ports;
}
function port_listening(port) {
let out = command_output_from_args([ "netstat", "-ltn" ]);
let needle = ":" + port + " ";
for (let line in split(out, "\n")) {
if (index(line, ":4534 ") >= 0)
if (index(line, needle) >= 0)
return true;
}
return false;
}
function mixed_proxy_port_listening() {
let ports = mixed_ports_from_config();
for (let port in ports) {
if (!port_listening(port))
return false;
}
return true;
}
function check_mixed_proxy_port() {
let sb_pid = trim(command_output_from_args(["pidof", "sing-box"]));
if (sb_pid == "") {
@@ -1748,16 +1795,25 @@ function check_mixed_proxy_port() {
return;
}
let ports = mixed_ports_from_config();
if (length(ports) == 0) {
// Mixed proxy is not part of the generated config: nothing to watch.
if (mixed_port_fail_streak > 0)
log_message("Mixed port watch: config declares no mixed inbound, standing down.", "debug");
mixed_port_fail_streak = 0;
return;
}
if (mixed_proxy_port_listening()) {
if (mixed_port_fail_streak >= 2)
log_message("Mixed port 4534 recovered after core restart.", "info");
log_message("Mixed port watch: port(s) " + join(", ", ports) + " recovered after core restart.", "info");
mixed_port_fail_streak = 0;
return;
}
mixed_port_fail_streak++;
log_message(sprintf("sing-box runs but mixed port 4534 is not listening (streak %d).",
mixed_port_fail_streak), "warn");
log_message(sprintf("sing-box runs but mixed port(s) %s are not listening (streak %d).",
join(", ", ports), mixed_port_fail_streak), "warn");
if (mixed_port_fail_streak == 2) {
log_message("Restarting sing-box to restore the mixed inbound.", "warn");
@@ -1766,7 +1822,7 @@ function check_mixed_proxy_port() {
let now = time();
if (now - mixed_port_last_notify >= 3600) {
mixed_port_last_notify = now;
send_telegram_notification("⚠️ Порт 4534 не слушается при работающем sing-box. Рестарт не помог — проверьте конфигурацию ядра (лог sing-box), раздел диагностики покажет детали.");
send_telegram_notification("⚠️ Порт " + join(", ", ports) + " не слушается при работающем sing-box. Рестарт не помог — проверьте конфигурацию ядра (лог sing-box), раздел диагностики покажет детали.");
}
}
}