Критические исправления: - safe_proxy_restart: удаление lock-файла после фонового перезапуска с защитой от stale lock (>300 сек) - write_json_file: атомарная запись через tmp+mv вместо прямой - write_reload_state: атомарная запись через tmp+mv - state_add_list: отклонение значений с пробелами Исправления безопасности: - killall logread заменён на pkill -f 'logread -f' - SIGTERM→SIGKILL для Telegram и sing-box при остановке - Recover OOM scale: инкремент +0.05 каждые 10 минут - nfqueue и byedpi: trap для корректного завершения дочерних процессов - nfqueue: валидация стратегий и откат при ошибке запуска - nfqueue: flush mangle_output перед добавлением правил - Исправлен парсинг exit code в 14 файлах (status & 127) Улучшения стабильности: - init.d: trap для start_service/stop_service - watchdog: signal traps для legacy fallback loop - lifecycle: логирование ошибок при остановке компонентов - singbox/priority: SIGTERM→SIGKILL при остановке - telegram: exponential backoff (1x→300s) при ошибках API - telegram: process_updates возвращает boolean для отслеживания успеха - watchdog: кэш proxy port для уменьшения чтения конфига sing-box - singbox/generator: упрощён atomic_write_json_file - packages.uc: переупорядочивание apk_version (лёгкие запросы первыми) - dns/apply: проверка PID sing-box перед настройкой dnsmasq - server/service: cleanup_orphaned_server_certs перед вызовом
229 lines
5.2 KiB
Ucode
229 lines
5.2 KiB
Ucode
#!/usr/bin/env ucode
|
|
|
|
let fs = require("fs");
|
|
|
|
global.double = function(v) {
|
|
if (v == null || v == "")
|
|
return 0.0;
|
|
return v * 1.0;
|
|
};
|
|
|
|
function as_string(value) {
|
|
return value == null ? "" : "" + value;
|
|
}
|
|
|
|
function read_json_file(path) {
|
|
let data = fs.readfile(path);
|
|
if (data == null)
|
|
return null;
|
|
|
|
try {
|
|
return json(data);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function read_stdin() {
|
|
let input = fs.open("/dev/stdin", "r");
|
|
if (!input)
|
|
return "";
|
|
let data = input.read("all");
|
|
input.close();
|
|
return data == null ? "" : data;
|
|
}
|
|
|
|
function read_stdin_json() {
|
|
let data = read_stdin();
|
|
try {
|
|
return json(data);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function write_json(value) {
|
|
print(sprintf("%J", value), "\n");
|
|
}
|
|
|
|
function write_compact_string_array(values) {
|
|
print("[");
|
|
for (let i = 0; i < length(values); i++) {
|
|
if (i > 0)
|
|
print(",");
|
|
print(sprintf("%J", as_string(values[i])));
|
|
}
|
|
print("]\n");
|
|
}
|
|
|
|
function csv_to_json_array(value) {
|
|
value = as_string(value);
|
|
write_compact_string_array(value == "" ? [] : split(value, ","));
|
|
}
|
|
|
|
function write_json_file(path, value) {
|
|
path = as_string(path);
|
|
let stamp = clock();
|
|
let tmp_path = sprintf("%s.%d.%d.tmp", path, stamp[0], stamp[1]);
|
|
let result = fs.writefile(tmp_path, sprintf("%J\n", value));
|
|
if (result == null || (type(result) == "boolean" && !result)) {
|
|
try { fs.unlink(tmp_path); } catch(e) {}
|
|
return false;
|
|
}
|
|
if (!fs.rename(tmp_path, path)) {
|
|
try { fs.unlink(tmp_path); } catch(e) {}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function strip_internal_fields(value) {
|
|
if (type(value) == "array") {
|
|
for (let i = 0; i < length(value); i++)
|
|
value[i] = strip_internal_fields(value[i]);
|
|
return value;
|
|
}
|
|
|
|
if (type(value) == "object") {
|
|
for (let key in keys(value)) {
|
|
if (substr(key, 0, 2) == "__") {
|
|
delete value[key];
|
|
continue;
|
|
}
|
|
value[key] = strip_internal_fields(value[key]);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function array_or_empty(value) {
|
|
return type(value) == "array" ? value : [];
|
|
}
|
|
|
|
function object_or_empty(value) {
|
|
return type(value) == "object" ? value : {};
|
|
}
|
|
|
|
function object_key_count(value) {
|
|
return type(value) == "object" ? length(keys(value)) : 0;
|
|
}
|
|
|
|
function option(section, key, fallback) {
|
|
if (fallback == null)
|
|
fallback = "";
|
|
let value = object_or_empty(section)[key];
|
|
if (value == null)
|
|
return fallback;
|
|
if (type(value) == "array")
|
|
return join(" ", value);
|
|
return as_string(value);
|
|
}
|
|
|
|
function list_option(section, key) {
|
|
let value = object_or_empty(section)[key];
|
|
if (value == null)
|
|
return [];
|
|
if (type(value) == "array")
|
|
return value;
|
|
let text = trim(as_string(value));
|
|
return text == "" ? [] : split(text, /[ \t\r\n]+/);
|
|
}
|
|
|
|
function bool_option(section, key, fallback) {
|
|
if (fallback == null)
|
|
fallback = false;
|
|
let value = option(section, key, fallback ? "1" : "0");
|
|
return value == "1" || value == "true" || value == "yes" || value == "on";
|
|
}
|
|
|
|
function int_option(section, key, fallback) {
|
|
let value = option(section, key, fallback);
|
|
if (match(value, /[^0-9]/))
|
|
return int(fallback, 10);
|
|
return int(value, 10);
|
|
}
|
|
|
|
function shell_quote(value) {
|
|
return "'" + replace(as_string(value), /'/g, "'\\''") + "'";
|
|
}
|
|
|
|
function command_from_args(args) {
|
|
let parts = [];
|
|
for (let arg in args)
|
|
push(parts, shell_quote(arg));
|
|
return join(" ", parts);
|
|
}
|
|
|
|
function command_status(command) {
|
|
let status = int(system(command));
|
|
if (status == -1)
|
|
return 255;
|
|
let signal = status & 127;
|
|
if (signal != 0)
|
|
return 128 + signal;
|
|
return (status >> 8) & 255;
|
|
}
|
|
|
|
function command_success(command) {
|
|
return command_status("(" + command + ") >/dev/null 2>&1") == 0;
|
|
}
|
|
|
|
function command_success_from_args(args) {
|
|
return command_success(command_from_args(args));
|
|
}
|
|
|
|
function command_capture(command) {
|
|
let p = fs.popen(command, "r");
|
|
if (!p) return null;
|
|
let output = as_string(p.read("all") || "");
|
|
let status = p.close();
|
|
if (status == -1)
|
|
status = 255;
|
|
else {
|
|
let signal = status & 127;
|
|
if (signal != 0)
|
|
status = 128 + signal;
|
|
else
|
|
status = (status >> 8) & 255;
|
|
}
|
|
return { status: status, output: output };
|
|
}
|
|
|
|
function command_output(command) {
|
|
let res = command_capture(command);
|
|
return res ? res.output : "";
|
|
}
|
|
|
|
function command_output_from_args(args) {
|
|
return command_output(command_from_args(args) + " 2>/dev/null");
|
|
}
|
|
|
|
return {
|
|
as_string,
|
|
read_json_file,
|
|
read_stdin,
|
|
read_stdin_json,
|
|
write_json,
|
|
write_compact_string_array,
|
|
csv_to_json_array,
|
|
write_json_file,
|
|
strip_internal_fields,
|
|
array_or_empty,
|
|
object_or_empty,
|
|
object_key_count,
|
|
option,
|
|
list_option,
|
|
bool_option,
|
|
int_option,
|
|
shell_quote,
|
|
command_from_args,
|
|
command_status,
|
|
command_success,
|
|
command_success_from_args,
|
|
command_capture,
|
|
command_output,
|
|
command_output_from_args
|
|
}; |