Release 1.0.6: синхронизировать изменения из upstream и исправить баг фильтра URLTest/Priority

This commit is contained in:
Dushnilin
2026-07-14 08:56:31 +03:00
parent bb1de1fd83
commit 46ef97fe89
130 changed files with 4146 additions and 4445 deletions
+211 -75
View File
@@ -18,8 +18,11 @@ const LATENCY_ACTION_DIR = getenv("TACHYON_UI_LATENCY_ACTION_DIR") || STATE_DIR
const COMPONENT_ACTION_DIR = getenv("TACHYON_UI_COMPONENT_ACTION_DIR") || getenv("UPDATES_JOB_DIR") || "/var/run/tachyon/component-actions";
const SUBSCRIPTION_ACTION_DIR = getenv("TACHYON_UI_SUBSCRIPTION_ACTION_DIR") || getenv("TACHYON_SUBSCRIPTION_UPDATE_JOB_DIR") || "/var/run/tachyon/subscription-update-jobs";
const SING_BOX_VERSION_CACHE_FILE = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_FILE") || STATE_DIR + "/sing-box-version";
const SING_BOX_VERSION_STATE_FILE = getenv("TACHYON_UI_SING_BOX_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version";
const SING_BOX_VERSION_CACHE_LOCK_DIR = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_LOCK_DIR") || SING_BOX_VERSION_CACHE_FILE + ".lock";
const SING_BOX_VARIANT_STATE_FILE = getenv("TACHYON_UI_SING_BOX_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
const SING_BOX_BIN_PATH = getenv("TACHYON_UI_SING_BOX_BIN_PATH") || "/usr/bin/sing-box";
const SING_BOX_VERSION_PROBE_TIMEOUT_SECONDS = getenv("TACHYON_UI_SING_BOX_VERSION_PROBE_TIMEOUT_SECONDS") || "1";
const SING_BOX_VERSION_PROBE_FAILURE_TTL_SECONDS = getenv("TACHYON_UI_SING_BOX_VERSION_PROBE_FAILURE_TTL_SECONDS") || "30";
const ACTION_FINISHED_TTL_MINUTES = getenv("TACHYON_UI_ACTION_FINISHED_TTL_MINUTES") || "60";
const ACTION_ACKED_TTL_SECONDS = getenv("TACHYON_UI_ACTION_ACKED_TTL_SECONDS") || "15";
const ACTION_STALE_GRACE_SECONDS = getenv("TACHYON_UI_ACTION_STALE_GRACE_SECONDS") || "15";
@@ -34,21 +37,31 @@ const ZAPRET_PROVIDER_NFQWS_BIN = getenv("ZAPRET_PROVIDER_NFQWS_BIN") || "/opt/z
const ZAPRET2_PROVIDER_NFQWS2_BIN = getenv("ZAPRET2_PROVIDER_NFQWS2_BIN") || "/opt/zapret2/nfq2/nfqws2";
const BYEDPI_BIN = getenv("BYEDPI_BIN") || "/usr/bin/ciadpi";
let common = require("core.common");
let as_string = common.as_string;
let shell_quote = common.shell_quote;
let read_json_file = common.read_json_file;
function as_string(value) {
return value == null ? "" : "" + value;
}
let write_json = common.write_json;
let command_status = common.command_status;
let command_success_from_args = common.command_success_from_args;
let command_output_from_args = common.command_output_from_args;
let command_from_args = common.command_from_args;
let object_or_empty = common.object_or_empty;
let read_stdin = common.read_stdin;
let command_output = common.command_output;
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_json_file(path) {
let data = fs.readfile(path);
if (data == null)
return null;
try {
return json(data);
}
catch (e) {
return null;
}
}
function parse_json_or_null(value) {
try {
@@ -59,11 +72,26 @@ function parse_json_or_null(value) {
}
}
function write_json(value) {
print(sprintf("%J", value), "\n");
}
function json_text(value) {
return sprintf("%J", value) + "\n";
}
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_env(assignments) {
let parts = [];
@@ -74,13 +102,35 @@ function command_env(assignments) {
return join(" ", parts);
}
function command_output(command) {
let pipe = fs.popen(command, "r");
if (!pipe)
return "";
let data = pipe.read("all");
let status = pipe.close();
if (status != 0 || data == null)
return "";
return as_string(data);
}
function command_output_from_args(args) {
return command_output(command_from_args(args));
}
function command_status(command) {
let status = int(system(command));
return status > 255 ? int(status / 256) : status;
}
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 module_success(module_path, args) {
let command_args = [ "ucode", "-L", LIB_DIR, module_path ];
@@ -94,7 +144,7 @@ function now_seconds() {
}
function ensure_dir(path) {
command_success_from_args([ "mkdir", "-p", path ]);
return fs.mkdir(path, 0755) || fs.stat(path) != null;
}
function ensure_dirs() {
@@ -170,6 +220,9 @@ function unsigned_number(value) {
return int(value);
}
function object_or_empty(value) {
return type(value) == "object" ? value : {};
}
function path_basename(path) {
let parts = split(as_string(path), "/");
@@ -649,6 +702,12 @@ function pid_running(pid) {
return job_pid_valid(pid) && command_success_from_args([ "kill", "-0", pid ]);
}
function current_pid() {
let stat = as_string(fs.readfile("/proc/self/stat"));
let separator = index(stat, " ");
return separator > 0 ? substr(stat, 0, separator) : "";
}
function refresh_pid_job_state(path, stale_message) {
let value = read_json_file(path);
if (type(value) != "object" || value.running !== true)
@@ -678,23 +737,26 @@ function state_file_ack_expired(path) {
function cleanup_dir(dir) {
dir = as_string(dir);
let now = now_seconds();
let ttl_minutes = unsigned_number(ACTION_FINISHED_TTL_MINUTES);
for (let path in fs.glob(dir + "/*.json")) {
let value = read_json_file(path);
if (!valid_action_state(value)) {
remove_state_file(path);
continue;
}
if (value.running === false && state_file_ack_expired(path))
remove_state_file(path);
}
let old = command_output_from_args([ "find", dir, "-type", "f", "-name", "*.json", "-mmin", "+" + as_string(ACTION_FINISHED_TTL_MINUTES) ]);
for (let path in split(old, "\n")) {
path = trim(as_string(path));
if (path == "")
if (value.running !== false)
continue;
let value = read_json_file(path);
if (type(value) == "object" && value.running === false)
if (state_file_ack_expired(path)) {
remove_state_file(path);
continue;
}
let stat = fs.stat(path);
if (stat != null && ttl_minutes != null && now - int(stat.mtime || 0) > ttl_minutes * 60)
remove_state_file(path);
}
}
@@ -747,23 +809,37 @@ function action_state_from_dirs() {
function acquire_dir_lock(lock_dir) {
lock_dir = as_string(lock_dir);
let owner_pid = trim(command_output("sh -c 'echo $$'"));
let owner_pid = current_pid();
if (!job_pid_valid(owner_pid))
return false;
if (command_success_from_args([ "mkdir", lock_dir ])) {
write_file(lock_dir + "/pid", owner_pid + "\n");
return true;
if (write_file(lock_dir + "/pid", owner_pid + "\n"))
return true;
release_dir_lock(lock_dir);
return false;
}
let current_owner_pid = first_line(lock_dir + "/pid");
if (pid_running(current_owner_pid))
return false;
let lock_stat = fs.stat(lock_dir);
if (current_owner_pid == "" && lock_stat != null) {
let lock_age = now_seconds() - int(lock_stat.mtime || 0);
if (lock_age >= 0 && lock_age < 5)
return false;
}
remove_file(lock_dir + "/pid");
command_success_from_args([ "rmdir", lock_dir ]);
if (!command_success_from_args([ "mkdir", lock_dir ]))
return false;
write_file(lock_dir + "/pid", owner_pid + "\n");
return true;
if (write_file(lock_dir + "/pid", owner_pid + "\n"))
return true;
release_dir_lock(lock_dir);
return false;
}
function release_dir_lock(lock_dir) {
@@ -828,43 +904,111 @@ function component_action_running_for(component) {
}
function sing_box_signature() {
let fields = split(trim(command_output_from_args([ "ls", "-ln", "/usr/bin/sing-box" ])), /[ \t]+/);
if (length(fields) < 8)
let stat = fs.stat(SING_BOX_BIN_PATH);
if (stat == null)
return "";
return join(":", [ fields[4], fields[5], fields[6], fields[7] ]);
return join(":", [ stat.inode, stat.size, stat.mtime, stat.ctime ]);
}
function sing_box_version_state() {
return first_line(SING_BOX_VERSION_STATE_FILE);
}
function sing_box_version() {
if (!command_success_from_args([ "sh", "-c", "command -v sing-box" ]))
return "";
if (marker_is("extended-compressed"))
return sing_box_version_state();
let signature = sing_box_signature();
if (signature != "" && fs.stat(SING_BOX_VERSION_CACHE_FILE) != null) {
let data = split(as_string(fs.readfile(SING_BOX_VERSION_CACHE_FILE)), "\n");
if ((data[0] || "") == signature && as_string(data[1] || "") != "")
return as_string(data[1]);
}
let first = split(command_output_from_args([ "sing-box", "version" ]), "\n")[0] || "";
function sing_box_version_info_from_output(output) {
let first = split(as_string(output), "\n")[0] || "";
let fields = split(trim(as_string(first)), /[ \t]+/);
let version = length(fields) > 0 ? fields[length(fields) - 1] : "";
if (version == "")
return "";
return null;
if (signature != "") {
ensure_dir(STATE_DIR);
write_file(SING_BOX_VERSION_CACHE_FILE, signature + "\n" + version + "\n");
let tags = "";
for (let line in split(as_string(output), "\n")) {
line = as_string(line);
if (substr(line, 0, 5) == "Tags:") {
tags = trim(substr(line, 5));
break;
}
}
return version;
return { version, tags };
}
function sing_box_cached_version_info(signature) {
let cache = read_json_file(SING_BOX_VERSION_CACHE_FILE);
if (type(cache) == "object" && as_string(cache.signature) == signature) {
if (cache.success === true && as_string(cache.version) != "")
return { hit: true, info: { version: as_string(cache.version), tags: as_string(cache.tags) } };
let checked_at = arg_number(cache.checked_at);
let failure_ttl = arg_number(SING_BOX_VERSION_PROBE_FAILURE_TTL_SECONDS);
if (cache.success === false && checked_at > 0) {
let failure_age = now_seconds() - checked_at;
if (failure_age >= 0 && failure_age < failure_ttl)
return { hit: true, info: null };
}
}
let legacy = split(as_string(fs.readfile(SING_BOX_VERSION_CACHE_FILE)), "\n");
let legacy_version = as_string(legacy[1] || "");
if (as_string(legacy[0] || "") == signature && index(legacy_version, "extended") >= 0)
return { hit: true, info: { version: legacy_version, tags: "" } };
return null;
}
function bounded_command_output_from_args(args, timeout_seconds) {
timeout_seconds = arg_number(timeout_seconds);
if (timeout_seconds <= 0)
timeout_seconds = 1;
let pid = current_pid();
if (!job_pid_valid(pid))
return "";
let output_path = SING_BOX_VERSION_CACHE_FILE + "." + pid + ".out";
remove_file(output_path);
let script = command_from_args(args) + " >" + shell_quote(output_path) + " 2>/dev/null & child=$!; " +
"(sleep " + as_string(timeout_seconds) + "; kill -KILL \"$child\" 2>/dev/null) & watchdog=$!; " +
"wait \"$child\"; rc=$?; kill \"$watchdog\" 2>/dev/null; wait \"$watchdog\" 2>/dev/null; exit \"$rc\"";
let status = command_status(command_from_args([ "sh", "-c", script ]) + " 2>/dev/null");
let output = as_string(fs.readfile(output_path));
remove_file(output_path);
return status == 0 ? output : "";
}
function sing_box_version_info() {
if (!file_executable(SING_BOX_BIN_PATH))
return null;
let signature = sing_box_signature();
if (signature == "")
return null;
let cached = sing_box_cached_version_info(signature);
if (cached != null)
return cached.info;
ensure_dir(STATE_DIR);
if (!acquire_dir_lock(SING_BOX_VERSION_CACHE_LOCK_DIR)) {
cached = sing_box_cached_version_info(signature);
return cached != null ? cached.info : null;
}
cached = sing_box_cached_version_info(signature);
if (cached != null) {
release_dir_lock(SING_BOX_VERSION_CACHE_LOCK_DIR);
return cached.info;
}
let output = bounded_command_output_from_args([ SING_BOX_BIN_PATH, "version" ], SING_BOX_VERSION_PROBE_TIMEOUT_SECONDS);
let info = sing_box_version_info_from_output(output);
write_state_file(SING_BOX_VERSION_CACHE_FILE, {
signature,
success: info != null,
version: info != null ? info.version : "",
tags: info != null ? info.tags : "",
checked_at: now_seconds()
});
release_dir_lock(SING_BOX_VERSION_CACHE_LOCK_DIR);
return info;
}
function server_inbounds_enabled_count() {
@@ -890,7 +1034,7 @@ function capability_flags() {
server_inbounds_enabled_count: 0
};
if (command_success_from_args([ "sh", "-c", "command -v sing-box" ])) {
if (file_executable(SING_BOX_BIN_PATH)) {
if (marker_is("extended-compressed")) {
result.sing_box_extended = 1;
result.sing_box_compressed = 1;
@@ -900,31 +1044,20 @@ function capability_flags() {
result.sing_box_extended = 1;
result.sing_box_tailscale = 1;
}
else if (tiny_package_installed() || marker_is("tiny")) {
else if (marker_is("tiny") || tiny_package_installed()) {
result.sing_box_tiny = 1;
}
else if (component_action_running_for("sing_box")) {
result.sing_box_tailscale = 1;
}
else {
let output = command_output_from_args([ "sing-box", "version" ]);
let first = split(output, "\n")[0] || "";
let fields = split(trim(as_string(first)), /[ \t]+/);
let version = length(fields) > 0 ? fields[length(fields) - 1] : "";
if (index(version, "extended") >= 0) {
let info = sing_box_version_info();
if (info != null && index(info.version, "extended") >= 0) {
result.sing_box_extended = 1;
result.sing_box_tailscale = 1;
}
else {
let tags = "";
for (let line in split(output, "\n")) {
line = as_string(line);
if (substr(line, 0, 5) == "Tags:") {
tags = trim(substr(line, 5));
break;
}
}
if (match(tags, /(^|[,: \t])with_tailscale([, \t]|$)/) != null)
else if (info != null) {
if (match(info.tags, /(^|[,: \t])with_tailscale([, \t]|$)/) != null)
result.sing_box_tailscale = 1;
if (result.sing_box_tailscale == 0)
result.sing_box_tiny = 1;
@@ -946,7 +1079,7 @@ function current_ui_state_json() {
let capabilities = capability_flags();
let tachyon_is_running = tachyon_running() ? 1 : 0;
let tachyon_is_enabled = service_enabled() ? 1 : 0;
let sing_box_is_running = sing_box_running() ? 1 : 0;
let sing_box_is_running = tachyon_is_running ? 1 : (sing_box_running() ? 1 : 0);
let sing_box_is_enabled = sing_box_enabled() ? 1 : 0;
let tachyon_status = service_status_text(tachyon_is_running, tachyon_is_enabled);
let sing_box_status = service_status_text(sing_box_is_running, sing_box_is_enabled);
@@ -1081,8 +1214,11 @@ function launch_worker(args) {
TACHYON_UI_COMPONENT_ACTION_DIR: COMPONENT_ACTION_DIR,
TACHYON_UI_SUBSCRIPTION_ACTION_DIR: SUBSCRIPTION_ACTION_DIR,
TACHYON_UI_SING_BOX_VERSION_CACHE_FILE: SING_BOX_VERSION_CACHE_FILE,
TACHYON_UI_SING_BOX_VERSION_STATE_FILE: SING_BOX_VERSION_STATE_FILE,
TACHYON_UI_SING_BOX_VERSION_CACHE_LOCK_DIR: SING_BOX_VERSION_CACHE_LOCK_DIR,
TACHYON_UI_SING_BOX_VARIANT_STATE_FILE: SING_BOX_VARIANT_STATE_FILE,
TACHYON_UI_SING_BOX_BIN_PATH: SING_BOX_BIN_PATH,
TACHYON_UI_SING_BOX_VERSION_PROBE_TIMEOUT_SECONDS: SING_BOX_VERSION_PROBE_TIMEOUT_SECONDS,
TACHYON_UI_SING_BOX_VERSION_PROBE_FAILURE_TTL_SECONDS: SING_BOX_VERSION_PROBE_FAILURE_TTL_SECONDS,
TACHYON_UI_ACTION_FINISHED_TTL_MINUTES: ACTION_FINISHED_TTL_MINUTES,
TACHYON_UI_ACTION_ACKED_TTL_SECONDS: ACTION_ACKED_TTL_SECONDS,
TACHYON_UI_ACTION_STALE_GRACE_SECONDS: ACTION_STALE_GRACE_SECONDS,
@@ -1446,4 +1582,4 @@ else if (mode == "cleanup-action-dir-fixture")
else {
warn("Usage: service/ui.uc <operation> ...\n");
exit(1);
}
}