fix(components): robust version installation and updates across Docker, APK v3 and OPKG

- fix(updater): support single release JSON object for specific tag queries and unblock prereleases on explicit selection
- fix(action): replace wget --header with -U for uclient-fetch compatibility on OpenWrt 23.05
- fix(action): make package list updates best-effort to prevent failures on snapshot kmod feeds
- fix(action): add offline/corrupt extract fallbacks (apk extract and opkg data.tar.gz) for kernel dependency mismatch
- fix(action): ensure /var/lock and /tmp/run directories exist before opkg/procd operations
- fix(action): fix fallback asset URL pattern for remittor zapret releases
- fix(packages): add binary presence and --version detection fallbacks for zapret, zapret2, byedpi, sing-box
- fix(byedpi): support direct apk list parsing for installed package version
- fix(state): include schedule, profile, and guest_mode in reload state signatures
- fix(imports): fix missing command_status_from_args, write_json, and remove_file imports
- fix(updates): extract meaningful ucode errors and strip trailing caret lines
This commit is contained in:
Dushnilin
2026-09-10 19:38:26 +03:00
parent f3e77e7142
commit a254cfc8b6
15 changed files with 514 additions and 148 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ export const DOMAIN_LIST_OPTIONS = {
russia_outside: 'Russia outside',
ukraine_inside: 'Ukraine',
geoblock: 'Geo Block',
block: 'Block (RKN)',
block: 'Block',
porn: 'Porn',
news: 'News',
anime: 'Anime',
@@ -465,10 +465,27 @@ export function showUpdateProgressModal(
cleanupTimers();
finishLogTracking();
let cleanError = (errorMessage || '').trim();
if (
cleanError.includes('Near here') ||
/^[-=~_^]{3,}$/m.test(cleanError)
) {
cleanError = cleanError
.split('\n')
.filter(
(line) =>
!line.includes('Near here') &&
!/^[\s\-_^]+$/.test(line) &&
line.trim() !== '',
)
.join(' ')
.trim();
}
const bannerEl = E(
'div',
{ class: 'tachyon-update-modal__error-banner' },
[renderXIcon24(), E('span', {}, errorMessage || _('Operation failed'))],
[renderXIcon24(), E('span', {}, cleanError || _('Operation failed'))],
);
const closeBtn = renderButton({
@@ -1232,7 +1232,7 @@ var DOMAIN_LIST_OPTIONS = {
russia_outside: "Russia outside",
ukraine_inside: "Ukraine",
geoblock: "Geo Block",
block: "Block (RKN)",
block: "Block",
porn: "Porn",
news: "News",
anime: "Anime",
@@ -20316,10 +20316,16 @@ function showUpdateProgressModal(options) {
completeError: (errorMessage) => {
cleanupTimers();
finishLogTracking();
let cleanError = (errorMessage || "").trim();
if (cleanError.includes("Near here") || /^[-=~_^]{3,}$/m.test(cleanError)) {
cleanError = cleanError.split("\n").filter(
(line) => !line.includes("Near here") && !/^[\s\-_^]+$/.test(line) && line.trim() !== ""
).join(" ").trim();
}
const bannerEl = E(
"div",
{ class: "tachyon-update-modal__error-banner" },
[renderXIcon24(), E("span", {}, errorMessage || _("Operation failed"))]
[renderXIcon24(), E("span", {}, cleanError || _("Operation failed"))]
);
const closeBtn = renderButton({
classNames: ["cbi-button-remove"],
@@ -7439,12 +7439,7 @@ function loadRulesetValues(option) {
delete option.vallist;
Object.entries(main.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
let displayLabel = _(label);
if (key === "block") {
displayLabel = "Block (РКН / внутри РФ)";
} else if (key === "geoblock") {
displayLabel = "Geo Block (снаружи РФ)";
}
const displayLabel = _(label);
const hint = DOMAIN_LIST_HINTS[key];
option.value(key, hint ? `${displayLabel} — ${hint}` : displayLabel);
});
+144 -75
View File
@@ -35,6 +35,7 @@ let as_string = common.as_string;
let shell_quote = common.shell_quote;
let command_from_args = common.command_from_args;
let command_status = common.command_status;
let command_status_from_args = common.command_status_from_args;
let command_success = common.command_success;
let command_success_from_args = common.command_success_from_args;
let command_output = common.command_output;
@@ -195,6 +196,8 @@ function cleanup_stale_tmp_files() {
}
function init_tmp_dir() {
ensure_dir("/var/lock");
ensure_dir("/tmp/run");
if (tmp_dir != "")
return true;
@@ -452,12 +455,25 @@ function pkg_install_name_downgrade(package_name, package_version) {
// published under the same tag would silently not be applied. apk always writes
// the file it is handed, so its argument list stays untouched.
function pkg_install_files_command(files, force_reinstall) {
let args = is_apk() ? [ "apk", "add", "--allow-untrusted" ] : [ "opkg", "install", "--force-overwrite", "--force-downgrade", "--force-depends" ];
if (!is_apk() && force_reinstall)
if (is_apk()) {
let add_args = [ "apk", "add", "--allow-untrusted" ];
for (let file in files)
push(add_args, file);
let extract_args = [ "apk", "extract", "--allow-untrusted", "--destination", "/" ];
for (let file in files)
push(extract_args, file);
return "(" + command_from_args(add_args) + " </dev/null || " + command_from_args(extract_args) + " </dev/null)";
}
let args = [ "opkg", "install", "--force-overwrite", "--force-downgrade", "--force-depends" ];
if (force_reinstall)
push(args, "--force-reinstall");
for (let file in files)
push(args, file);
return command_from_args(args) + " </dev/null";
let opkg_cmd = command_from_args(args) + " </dev/null";
let fallback_cmds = [];
for (let file in files)
push(fallback_cmds, "(tar -zxOf " + shell_quote(file) + " ./data.tar.gz 2>/dev/null || tar -zxOf " + shell_quote(file) + " data.tar.gz 2>/dev/null) | tar -zx -C /");
return "(" + opkg_cmd + " || (" + join(" && ", fallback_cmds) + "))";
}
function pkg_install_files(files, force_reinstall) {
@@ -607,7 +623,7 @@ function http_get_once(url, output_path, proxy_address, timeout) {
}
if (command_exists("wget")) {
let command = command_from_args([ "wget", "-T", timeout, "-q", "-O", output_path, "--header=User-Agent: Tachyon-OpenWrt", url ]);
let command = command_from_args([ "wget", "-T", timeout, "-q", "-O", output_path, "-U", "Tachyon-OpenWrt", url ]);
if (proxy_address != "")
command = command_env({ http_proxy: "http://" + proxy_address, https_proxy: "http://" + proxy_address }) + " " + command;
return command_success(command);
@@ -769,30 +785,40 @@ function fetch_github_release_tag_fallback(owner, repo) {
let url = "https://github.com/" + as_string(owner) + "/" + as_string(repo) + "/releases/latest";
let url_mod = core_url_module_or_null();
let candidates = url_mod && type(url_mod.download_candidates) == "function" ? url_mod.download_candidates(url) : [ url ];
let proxy_addr = service_proxy_address();
for (let target_url in candidates) {
let args = [ "curl", "-sI", "--connect-timeout", "6", "-m", "12" ];
let proxy_addr = service_proxy_address();
if (proxy_addr != "") {
push(args, "-x");
push(args, "http://" + proxy_addr);
}
push(args, target_url);
let output = command_output_from_args(args);
if (output != "") {
let loc_idx = index(lc(output), "location:");
if (loc_idx >= 0) {
let line = substr(output, loc_idx);
let end_line = index(line, "\r");
if (end_line < 0) end_line = index(line, "\n");
if (end_line >= 0) line = substr(line, 0, end_line);
let tag_idx = rindex(line, "/");
if (tag_idx >= 0) {
let tag = trim(substr(line, tag_idx + 1));
if (tag != "")
return tag;
if (command_exists("curl")) {
let args = [ "curl", "-sI", "--connect-timeout", "6", "-m", "12" ];
if (proxy_addr != "") {
push(args, "-x");
push(args, "http://" + proxy_addr);
}
push(args, target_url);
let output = command_output_from_args(args);
if (output != "") {
let loc_idx = index(lc(output), "location:");
if (loc_idx >= 0) {
let line = substr(output, loc_idx);
let end_line = index(line, "\r");
if (end_line < 0) end_line = index(line, "\n");
if (end_line >= 0) line = substr(line, 0, end_line);
let tag_idx = rindex(line, "/");
if (tag_idx >= 0) {
let tag = trim(substr(line, tag_idx + 1));
if (tag != "")
return tag;
}
}
}
} else if (command_exists("wget")) {
let cmd = command_from_args([ "wget", "-s", "-T", "6", target_url ]);
if (proxy_addr != "")
cmd = command_env({ http_proxy: "http://" + proxy_addr, https_proxy: "http://" + proxy_addr }) + " " + cmd;
let output = command_output("(" + cmd + ") 2>&1");
let m = match(output, /Redirected to [^ \t\r\n]*\/releases\/tag\/([^ \t\r\n]+)/);
if (m && m[1])
return trim(m[1]);
}
}
return "";
@@ -801,21 +827,29 @@ function fetch_github_release_tag_fallback(owner, repo) {
function url_exists(url) {
let url_mod = core_url_module_or_null();
let candidates = url_mod && type(url_mod.download_candidates) == "function" ? url_mod.download_candidates(url) : [ url ];
let proxy_addr = service_proxy_address();
for (let target_url in candidates) {
let args = [ "curl", "-sI", "--connect-timeout", "6", "-m", "12" ];
let proxy_addr = service_proxy_address();
if (proxy_addr != "") {
push(args, "-x");
push(args, "http://" + proxy_addr);
}
push(args, as_string(target_url));
let output = command_output_from_args(args);
if (output != "") {
let first_line = split(output, "\n")[0] || "";
if (index(first_line, " 200 ") > 0 || index(first_line, " 301 ") > 0 || index(first_line, " 302 ") > 0) {
return true;
if (command_exists("curl")) {
let args = [ "curl", "-sI", "--connect-timeout", "6", "-m", "12" ];
if (proxy_addr != "") {
push(args, "-x");
push(args, "http://" + proxy_addr);
}
push(args, as_string(target_url));
let output = command_output_from_args(args);
if (output != "") {
let first_line = split(output, "\n")[0] || "";
if (index(first_line, " 200 ") > 0 || index(first_line, " 301 ") > 0 || index(first_line, " 302 ") > 0) {
return true;
}
}
} else if (command_exists("wget")) {
let cmd = command_from_args([ "wget", "-s", "-T", "6", "-q", as_string(target_url) ]);
if (proxy_addr != "")
cmd = command_env({ http_proxy: "http://" + proxy_addr, https_proxy: "http://" + proxy_addr }) + " " + cmd;
if (command_success(cmd))
return true;
}
}
return false;
@@ -916,8 +950,7 @@ function retry_resolve(description, fn) {
function ensure_package_tool(tool_name, package_name, component, action) {
if (command_exists(tool_name))
return true;
if (!run_logged("Updating package lists before installing " + as_string(package_name), pkg_list_update_command()))
return false;
run_logged("Updating package lists before installing " + as_string(package_name), pkg_list_update_command());
return run_logged("Installing bootstrap package " + as_string(package_name), pkg_install_name_command(package_name));
}
@@ -1199,27 +1232,40 @@ function resolve_zapret_release(arch, tag) {
}
if (tag != null && tag != "") {
let tag_clean = replace(tag, /^v/, "");
let bundle_name = "zapret_" + tag_clean + "_openwrt_" + arch.candidates + ".zip";
let tag_with_v = "v" + tag_clean;
let bundle_name = "zapret_" + tag_with_v + "_" + arch.candidates + ".zip";
return {
arch: arch.candidates,
bundle_name: bundle_name,
bundle_url: "https://github.com/remittor/zapret-openwrt/releases/download/" + tag + "/" + bundle_name,
release_url: "https://github.com/remittor/zapret-openwrt/releases/tag/" + tag,
bundle_url: "https://github.com/remittor/zapret-openwrt/releases/download/" + tag_with_v + "/" + bundle_name,
release_url: "https://github.com/remittor/zapret-openwrt/releases/tag/" + tag_with_v,
version: tag
};
}
return { fetch_failed: true };
}
function resolve_zapret2_release(arch) {
let tag = fetch_github_release_tag_fallback("Dushnilin", "zapret2-openwrt");
if (tag == "")
function resolve_zapret2_release(arch, tag) {
let resolved_tag = (tag != null && tag != "") ? tag : "";
if (resolved_tag == "") {
let releases_json = fetch_github_releases_json("Dushnilin", "zapret2-openwrt", "5");
if (releases_json != "") {
try {
let parsed = json(releases_json);
if (type(parsed) == "array" && length(parsed) > 0)
resolved_tag = trim(as_string(parsed[0].tag_name || ""));
} catch (e) {}
}
}
if (resolved_tag == "")
resolved_tag = fetch_github_release_tag_fallback("Dushnilin", "zapret2-openwrt");
if (resolved_tag == "")
return { fetch_failed: true };
let asset_ext = is_apk() ? "apk" : "ipk";
let base_dl = "https://github.com/Dushnilin/zapret2-openwrt/releases/download/" + tag + "/";
let release_url = "https://github.com/Dushnilin/zapret2-openwrt/releases/tag/" + tag;
let version = replace(tag, /^v/, "");
let base_dl = "https://github.com/Dushnilin/zapret2-openwrt/releases/download/" + resolved_tag + "/";
let release_url = "https://github.com/Dushnilin/zapret2-openwrt/releases/tag/" + resolved_tag;
let version = replace(resolved_tag, /^v/, "");
let candidate_list = split(arch.candidates, " ");
for (let candidate in candidate_list) {
@@ -1227,7 +1273,6 @@ let base_dl = "https://github.com/Dushnilin/zapret2-openwrt/releases/download/"
let pkg_name = "zapret2_" + candidate + "." + asset_ext;
let url = base_dl + pkg_name;
if (url_exists(url)) {
warn("EXISTS: " + url + "\n");
return {
arch: candidate,
package_name: pkg_name,
@@ -1373,7 +1418,7 @@ function install_zapret_like(component, action, runtime_module, resolve_fn, labe
clear_version_caches();
current_version = provider_package_version(runtime_module);
if (current_version == "")
current_version = "unknown";
current_version = pkg.version || "unknown";
action_success(component, action, label + " package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
}
@@ -1421,6 +1466,8 @@ function install_zapret2(action, target_tag) {
if (index(hosts_content, "::1") < 0)
command_success("printf '\\n::1 localhost ip6-localhost ip6-loopback\\n' >> /etc/hosts");
run_logged("Updating package lists before " + label + " package installation", pkg_list_update_command());
if (!run_logged("Installing " + label + " package " + pkg.name, pkg_install_files_command([ pkg.file ])))
action_fail(component, action, "Failed to install " + label + " package", current_version, pkg.version, "", release.release_url || "");
@@ -1440,7 +1487,7 @@ function install_zapret2(action, target_tag) {
clear_version_caches();
current_version = provider_package_version(runtime_module);
if (current_version == "")
current_version = "unknown";
current_version = pkg.version || "unknown";
action_success(component, action, label + " package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
}
@@ -1469,6 +1516,9 @@ function install_byedpi(action, target_tag) {
let pkg = download_direct_package(release);
if (pkg == null)
action_fail("byedpi", action, "Failed to download ByeDPI package");
run_logged("Updating package lists before ByeDPI package installation", pkg_list_update_command());
if (!run_logged("Installing ByeDPI package " + pkg.name, pkg_install_files_command([ pkg.file ])))
action_fail("byedpi", action, "Failed to install ByeDPI package", current_version, pkg.version);
@@ -1477,7 +1527,7 @@ function install_byedpi(action, target_tag) {
clear_version_caches();
current_version = provider_package_version(runtime_module);
if (current_version == "")
current_version = "unknown";
current_version = pkg.version || "unknown";
action_success("byedpi", action, "ByeDPI package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
}
@@ -2017,9 +2067,10 @@ function install_sing_box_extended_package(action, target_tag) {
let latest_version = normalize_sing_box_version(release.tag);
if (action == "check_update") {
if (!sing_box_runtime_success("is-extended", [ current_version ]))
action_fail("sing_box", action, "sing-box-extended is not installed", current_version, latest_version);
check_success("sing_box", normalize_sing_box_version(current_version), normalize_sing_box_version(latest_version), release.release_url);
if (current_version == "" || !sing_box_runtime_success("is-extended", [ current_version ]))
action_success("sing_box", action, "sing-box-extended is not installed", current_version, latest_version, 0, "", release.release_url);
else
check_success("sing_box", normalize_sing_box_version(current_version), normalize_sing_box_version(latest_version), release.release_url);
}
ensure_sing_box_dependencies();
@@ -2028,8 +2079,7 @@ function install_sing_box_extended_package(action, target_tag) {
if (!download_with_retry(release.asset_url, package_file, release.asset_name))
action_fail("sing_box", action, "Failed to download sing-box-extended package", current_version, latest_version);
if (!run_logged("Updating package lists before sing-box-extended package installation", pkg_list_update_command()))
action_fail("sing_box", action, "Failed to update package lists", current_version, latest_version);
run_logged("Updating package lists before sing-box-extended package installation", pkg_list_update_command());
stop_tachyon_before_sing_box_change();
prepare_sing_box_package_service_install();
@@ -2133,11 +2183,10 @@ function install_sing_box_extended(action, compressed, target_tag) {
let latest_version = normalize_sing_box_version(release.tag);
if (action == "check_update") {
if (!sing_box_runtime_success("is-extended", [ current_version ]))
action_fail("sing_box", action, "sing-box-extended is not installed", current_version, latest_version);
if (!sing_box_runtime_success("marker-is", [ "extended-compressed" ]))
action_fail("sing_box", action, "sing-box-extended compressed is not installed", current_version, latest_version);
check_success("sing_box", normalize_sing_box_version(current_version), normalize_sing_box_version(latest_version), release.release_url);
if (current_version == "" || !sing_box_runtime_success("is-extended", [ current_version ]) || !sing_box_runtime_success("marker-is", [ "extended-compressed" ]))
action_success("sing_box", action, label + " is not installed", current_version, latest_version, 0, "", release.release_url);
else
check_success("sing_box", normalize_sing_box_version(current_version), normalize_sing_box_version(latest_version), release.release_url);
}
ensure_sing_box_dependencies();
@@ -2296,11 +2345,10 @@ function install_sing_box_lx(action, target_tag) {
let latest_version = normalize_sing_box_version(release.tag);
if (action == "check_update") {
if (!sing_box_runtime_success("is-lx", [ current_version ]))
action_fail("sing_box", action, "sing-box-lx is not installed", current_version, latest_version);
if (!sing_box_runtime_success("marker-is", [ "lx" ]))
action_fail("sing_box", action, "sing-box-lx is not installed", current_version, latest_version);
check_success("sing_box", normalize_sing_box_version(current_version), normalize_sing_box_version(latest_version), release.release_url);
if (current_version == "" || !sing_box_runtime_success("is-lx", [ current_version ]) || !sing_box_runtime_success("marker-is", [ "lx" ]))
action_success("sing_box", action, label + " is not installed", current_version, latest_version, 0, "", release.release_url);
else
check_success("sing_box", normalize_sing_box_version(current_version), normalize_sing_box_version(latest_version), release.release_url);
}
ensure_sing_box_dependencies();
@@ -2462,17 +2510,24 @@ function install_package_sing_box(action, tiny) {
latest_version = installed_package_version(package_name);
if (action == "check_update") {
if (latest_version == "") {
let proxy_address = service_proxy_address();
run_logged("Refreshing package index", pkg_list_update_command(proxy_address));
latest_version = available_package_version(package_name);
if (latest_version == "")
latest_version = installed_package_version(package_name);
}
if (latest_version == "")
action_fail("sing_box", action, "Failed to resolve " + (tiny ? "tiny" : "stable") + " sing-box package version", current_version);
if (tiny && !sing_box_runtime_success("is-tiny", [ binary_version ]))
action_fail("sing_box", action, "sing-box-tiny is not installed", current_version, latest_version);
check_success("sing_box", current_version, latest_version, "");
if (current_version == "" || (tiny && !sing_box_runtime_success("is-tiny", [ binary_version ])))
action_success("sing_box", action, label + " is not installed", current_version, latest_version, 0, "", "");
else
check_success("sing_box", current_version, latest_version, "");
}
ensure_sing_box_dependencies();
if (!run_logged("Updating package lists before " + package_name + " installation", pkg_list_update_command()))
action_fail("sing_box", action, "Failed to update package lists", current_version, latest_version);
run_logged("Updating package lists before " + package_name + " installation", pkg_list_update_command());
latest_version = available_package_version(package_name);
if (latest_version == "")
latest_version = installed_package_version(package_name);
@@ -3330,12 +3385,26 @@ function component_action(component, action, extra) {
let mode = ARGV[0] || "";
if (mode == "component-action")
component_action(ARGV[1], ARGV[2], ARGV[3]);
if (mode == "component-action") {
try {
component_action(ARGV[1], ARGV[2], ARGV[3]);
} catch (e) {
let err_str = as_string(e);
updates_log("Unhandled component action error: " + err_str, "error");
action_fail(ARGV[1] || "unknown", ARGV[2] || "unknown", "Unexpected error: " + err_str);
}
}
else if (mode == "list-component-releases")
list_component_releases(ARGV[1], ARGV[2]);
else if (mode == "install-component-version")
component_action(ARGV[1], "install_version", ARGV[2]);
else if (mode == "install-component-version") {
try {
component_action(ARGV[1], "install_version", ARGV[2]);
} catch (e) {
let err_str = as_string(e);
updates_log("Unhandled install component version error: " + err_str, "error");
action_fail(ARGV[1] || "unknown", "install_version", "Unexpected error: " + err_str);
}
}
else if (mode == "latest-tachyon-release-json")
print(latest_tachyon_release_json());
else if (mode == "latest-tachyon-version")
+78 -14
View File
@@ -78,6 +78,14 @@ function array_or_empty(value) {
return type(value) == "array" ? value : [];
}
function releases_array_or_wrapped(value) {
if (type(value) == "array")
return value;
if (type(value) == "object" && value != null)
return [ value ];
return [];
}
function str_contains(haystack, needle) {
return index(as_string(haystack), as_string(needle)) >= 0;
}
@@ -915,7 +923,7 @@ function release_asset_matches_arch(name, prefix, arch, ext) {
}
function named_release_select_asset(release_prefix, asset_prefix, asset_ext, arch_candidates) {
let releases = array_or_empty(read_stdin_json());
let releases = releases_array_or_wrapped(read_stdin_json());
let clean_prefix = trim(release_prefix);
for (let release in releases) {
@@ -1006,7 +1014,7 @@ function select_byedpi_asset_from_release(release, asset_ext, arch_candidates) {
}
function byedpi_select_asset(series, asset_ext, arch_candidates) {
let releases = array_or_empty(read_stdin_json());
let releases = releases_array_or_wrapped(read_stdin_json());
for (let pass = 0; pass < 2; pass++) {
if (pass == 0 && as_string(series) == "")
@@ -1015,9 +1023,9 @@ function byedpi_select_asset(series, asset_ext, arch_candidates) {
for (let release in releases) {
if (type(release) != "object")
continue;
if (release.draft === true || release.prerelease === true)
if (release.draft === true || (length(releases) > 1 && release.prerelease === true))
continue;
if (pass == 0) {
if (pass == 0 && length(releases) > 1) {
let tag = as_string(release.tag_name || "");
let name = as_string(release.name || "");
if (!str_contains(tag, series) && !str_contains(name, series))
@@ -1030,7 +1038,7 @@ function byedpi_select_asset(series, asset_ext, arch_candidates) {
}
function sing_box_extended_release_tag() {
for (let release in array_or_empty(read_stdin_json())) {
for (let release in releases_array_or_wrapped(read_stdin_json())) {
if (type(release) != "object")
continue;
if (release.draft === true || release.prerelease === true)
@@ -1045,7 +1053,7 @@ function sing_box_extended_release_tag() {
}
function sing_box_lx_release_tag() {
for (let release in array_or_empty(read_stdin_json())) {
for (let release in releases_array_or_wrapped(read_stdin_json())) {
if (type(release) != "object")
continue;
if (release.draft === true || release.prerelease === true)
@@ -1067,17 +1075,73 @@ function text_first_chars(value, max_chars) {
function file_last_nonblank_line(path, fallback, max_chars) {
let data = fs.readfile(path);
let result = "";
if (data != null) {
for (let line in split(as_string(data), "\n"))
if (match(line, /^[[:space:]]*$/) == null)
result = line;
if (data == null || trim(as_string(data)) == "") {
print(text_first_chars(fallback, max_chars), "\n");
return;
}
if (result == "")
result = as_string(fallback);
let raw_lines = split(as_string(data), /\r?\n/);
let lines = [];
for (let l in raw_lines) {
let t = trim(l);
if (t != "")
push(lines, t);
}
if (length(lines) == 0) {
print(text_first_chars(fallback, max_chars), "\n");
return;
}
// 1. Detect ucode runtime/syntax exception
let first_line = lines[0];
let lower_first = lc(first_line);
if (index(lower_first, "type error:") == 0 ||
index(lower_first, "syntax error:") == 0 ||
index(lower_first, "reference error:") == 0 ||
index(lower_first, "runtime error:") == 0 ||
index(lower_first, "unhandled exception:") == 0 ||
index(lower_first, "error:") == 0) {
let loc = "";
if (length(lines) > 1 && (index(lines[1], "In ") == 0 || index(lines[1], "in ") == 0)) {
let m = match(lines[1], /([a-zA-Z0-9_.-]+),[ \t]*line[ \t]*([0-9]+)/);
if (m)
loc = " (" + m[1] + ":" + m[2] + ")";
}
let msg = first_line + loc;
print(text_first_chars(msg, max_chars), "\n");
return;
}
// 2. Discard trailing pointer / caret / delimiter lines
while (length(lines) > 0) {
let last = lines[length(lines) - 1];
if (index(last, "Near here") >= 0 || match(last, /^[ \t\-_^]*$/) || match(last, /^[-=~_]{4,}$/)) {
pop(lines);
} else {
break;
}
}
if (length(lines) == 0) {
print(text_first_chars(fallback, max_chars), "\n");
return;
}
// 3. Check if any line has an explicit error prefix
for (let l in lines) {
let lower_l = lc(l);
if (index(lower_l, "error:") >= 0 ||
index(lower_l, "fatal:") >= 0 ||
index(lower_l, "failed:") >= 0 ||
index(lower_l, "failure:") >= 0) {
print(text_first_chars(l, max_chars), "\n");
return;
}
}
// 4. Fallback to the last non-empty line
let result = lines[length(lines) - 1];
print(text_first_chars(result, max_chars), "\n");
}
+55 -7
View File
@@ -230,17 +230,65 @@ function text_first_chars(value, max_chars) {
function file_last_nonblank_line_value(path, fallback, max_chars) {
let data = fs.readfile(path);
let result = "";
if (data == null || trim(as_string(data)) == "")
return text_first_chars(fallback, max_chars);
if (data != null) {
for (let line in split(as_string(data), "\n"))
if (match(line, /^[[:space:]]*$/) == null)
result = line;
let raw_lines = split(as_string(data), /\r?\n/);
let lines = [];
for (let l in raw_lines) {
let t = trim(l);
if (t != "")
push(lines, t);
}
if (result == "")
result = as_string(fallback);
if (length(lines) == 0)
return text_first_chars(fallback, max_chars);
// 1. Detect ucode runtime/syntax exception
let first_line = lines[0];
let lower_first = lc(first_line);
if (index(lower_first, "type error:") == 0 ||
index(lower_first, "syntax error:") == 0 ||
index(lower_first, "reference error:") == 0 ||
index(lower_first, "runtime error:") == 0 ||
index(lower_first, "unhandled exception:") == 0 ||
index(lower_first, "error:") == 0) {
let loc = "";
if (length(lines) > 1 && (index(lines[1], "In ") == 0 || index(lines[1], "in ") == 0)) {
let m = match(lines[1], /([a-zA-Z0-9_.-]+),[ \t]*line[ \t]*([0-9]+)/);
if (m)
loc = " (" + m[1] + ":" + m[2] + ")";
}
let msg = first_line + loc;
return text_first_chars(msg, max_chars);
}
// 2. Discard trailing pointer / caret / delimiter lines
while (length(lines) > 0) {
let last = lines[length(lines) - 1];
if (index(last, "Near here") >= 0 || match(last, /^[ \t\-_^]*$/) || match(last, /^[-=~_]{4,}$/)) {
pop(lines);
} else {
break;
}
}
if (length(lines) == 0)
return text_first_chars(fallback, max_chars);
// 3. Check if any line has an explicit error prefix
for (let l in lines) {
let lower_l = lc(l);
if (index(lower_l, "error:") >= 0 ||
index(lower_l, "fatal:") >= 0 ||
index(lower_l, "failed:") >= 0 ||
index(lower_l, "failure:") >= 0) {
return text_first_chars(l, max_chars);
}
}
// 4. Fallback to the last non-empty line
let result = lines[length(lines) - 1];
return text_first_chars(result, max_chars);
}
+5 -6
View File
@@ -6,6 +6,7 @@ let uci_core = require("core.uci");
let as_string = common.as_string;
let object_or_empty = common.object_or_empty;
let bool_value_common = common.bool_value;
let remove_file = common.remove_file;
function bool_value(value, fallback) {
if (value == null || value == "")
@@ -1372,12 +1373,10 @@ function cascade_delete_section(cursor, config_name, parent_name) {
// 6. Delete section cache files
try {
command_success_from_args([ "rm", "-f",
"/var/run/tachyon/section-cache/" + parent_name + ".json",
"/etc/tachyon/subscription-cache/" + parent_name + ".json",
"/etc/tachyon/subscription-cache/" + parent_name + ".yaml",
"/etc/tachyon/subscription-cache/" + parent_name + ".txt"
]);
remove_file("/var/run/tachyon/section-cache/" + parent_name + ".json");
remove_file("/etc/tachyon/subscription-cache/" + parent_name + ".json");
remove_file("/etc/tachyon/subscription-cache/" + parent_name + ".yaml");
remove_file("/etc/tachyon/subscription-cache/" + parent_name + ".txt");
} catch (e) {}
return removed;
+69 -20
View File
@@ -20,22 +20,36 @@ function command_exists(name) {
return system(command_from_args([ "command", "-v", name ]) + " >/dev/null 2>&1") == 0;
}
function binary_installed(package_name) {
if (package_name == "zapret2")
return fs.stat("/opt/zapret2/nfq2/nfqws2") != null || fs.stat("/opt/zapret2/nfqws2") != null || fs.stat("/usr/bin/nfqws2") != null;
if (package_name == "zapret")
return fs.stat("/opt/zapret/nfq/nfqws") != null || fs.stat("/opt/zapret/nfqws") != null;
if (package_name == "byedpi")
return fs.stat("/usr/bin/ciadpi") != null;
if (package_name == "sing-box" || package_name == "sing-box-extended" || package_name == "sing-box-lx")
return fs.stat("/usr/bin/sing-box") != null;
return false;
}
function apk_installed(package_name) {
return command_exists("apk") && command_success([ "apk", "info", "-e", as_string(package_name) ]);
package_name = as_string(package_name);
if (command_exists("apk") && command_success([ "apk", "info", "-e", package_name ]))
return true;
return binary_installed(package_name);
}
function opkg_installed(package_name) {
package_name = as_string(package_name);
if (!command_exists("opkg"))
return false;
let prefix = package_name + " - ";
for (let line in split(command_output_args([ "opkg", "list-installed" ]), "\n")) {
line = trim(as_string(line));
if (substr(line, 0, length(prefix)) == prefix)
return true;
if (command_exists("opkg")) {
let prefix = package_name + " - ";
for (let line in split(command_output_args([ "opkg", "list-installed" ]), "\n")) {
line = trim(as_string(line));
if (substr(line, 0, length(prefix)) == prefix)
return true;
}
}
return false;
return binary_installed(package_name);
}
function installed(package_name) {
@@ -113,6 +127,38 @@ function apk_query_version(package_name, output) {
return "";
}
function binary_version(package_name) {
if (package_name == "zapret2") {
for (let p in [ "/opt/zapret2/nfq2/nfqws2", "/opt/zapret2/nfqws2", "/usr/bin/nfqws2" ]) {
if (fs.stat(p) != null) {
let out = command_output_args([ p, "--version" ]);
let m = match(out, /version[ \t]*([0-9a-zA-Z._-]+)/i);
if (m) return m[1];
}
}
}
if (package_name == "zapret") {
for (let p in [ "/opt/zapret/nfq/nfqws", "/opt/zapret/nfqws" ]) {
if (fs.stat(p) != null) {
let out = command_output_args([ p, "--version" ]);
let m = match(out, /version[ \t]*([0-9a-zA-Z._-]+)/i);
if (m) return m[1];
}
}
}
if (package_name == "byedpi" && fs.stat("/usr/bin/ciadpi") != null) {
let out = command_output_args([ "/usr/bin/ciadpi", "--version" ]);
let m = match(out, /([0-9a-zA-Z._-]+)/);
if (m) return m[1];
}
if ((package_name == "sing-box" || package_name == "sing-box-extended" || package_name == "sing-box-lx") && fs.stat("/usr/bin/sing-box") != null) {
let out = command_output_args([ "/usr/bin/sing-box", "version" ]);
let m = match(out, /version[ \t]*([0-9a-zA-Z._-]+)/i);
if (m) return m[1];
}
return "";
}
function apk_version(package_name) {
package_name = as_string(package_name);
if (!apk_installed(package_name))
@@ -130,7 +176,11 @@ function apk_version(package_name) {
if (version != "")
return version;
return apk_manifest_version(package_name, command_output_args([ "apk", "list", "--installed", "--manifest" ]));
version = apk_manifest_version(package_name, command_output_args([ "apk", "list", "--installed", "--manifest" ]));
if (version != "")
return version;
return binary_version(package_name);
}
function apk_available_version(package_name) {
@@ -145,16 +195,15 @@ function apk_available_version(package_name) {
function opkg_version(package_name) {
package_name = as_string(package_name);
if (!command_exists("opkg"))
return "";
let prefix = package_name + " - ";
for (let line in split(command_output_args([ "opkg", "list-installed" ]), "\n")) {
line = trim(as_string(line));
if (substr(line, 0, length(prefix)) == prefix)
return substr(line, length(prefix));
if (command_exists("opkg")) {
let prefix = package_name + " - ";
for (let line in split(command_output_args([ "opkg", "list-installed" ]), "\n")) {
line = trim(as_string(line));
if (substr(line, 0, length(prefix)) == prefix)
return substr(line, length(prefix));
}
}
return "";
return binary_version(package_name);
}
function version(package_name) {
+1 -1
View File
@@ -1969,7 +1969,7 @@ function global_check(arg1, arg2) {
print_global("❌ Failed to get DNS info");
print_global("━━━━━━━━━━━━━━━━━━━━━━━━━━━");
print_global("ЁЯУж Sing-box status");
print_global("═══ Sing-box status");
let singbox_check_json = command_capture(command_from_args(module_args(LIB_DIR + "/diagnostics/runtime.uc", [ "check-sing-box" ]))).output;
if (singbox_check_json != "")
render_or_fail([ "global-sing-box-check" ], singbox_check_json, "❌ Failed to parse sing-box info", [ 0 ]);
+6 -2
View File
@@ -2359,7 +2359,9 @@ function nft_rule_signature_body(body, section) {
function nft_schedule_signature_body(body, schedule) {
let name = as_string(schedule[".name"]);
body = signature_add_value(body, "schedule." + name + ".enabled", bool_option(schedule, "enabled", true) ? "1" : "0");
body = signature_add_value(body, "schedule." + name + ".device_ip", option(schedule, "device_ip", ""));
let dev_ips = join(",", list_option(schedule, "device_ip"));
if (dev_ips == "") dev_ips = option(schedule, "device_ip", "");
body = signature_add_value(body, "schedule." + name + ".device_ip", dev_ips);
body = signature_add_value(body, "schedule." + name + ".profile", join(",", list_option(schedule, "profile")));
body = signature_add_value(body, "schedule." + name + ".target", option(schedule, "target", "all"));
body = signature_add_value(body, "schedule." + name + ".sections", join(",", list_option(schedule, "sections")));
@@ -2375,7 +2377,9 @@ function nft_schedule_signature_body(body, schedule) {
function nft_profile_signature_body(body, profile) {
let name = as_string(profile[".name"]);
body = signature_add_value(body, "profile." + name + ".enabled", bool_option(profile, "enabled", true) ? "1" : "0");
body = signature_add_value(body, "profile." + name + ".device_ip", join(",", list_option(profile, "device_ip")));
let dev_ips = join(",", list_option(profile, "device_ip"));
if (dev_ips == "") dev_ips = option(profile, "device_ip", "");
body = signature_add_value(body, "profile." + name + ".device_ip", dev_ips);
body = signature_add_value(body, "profile." + name + ".safe_search", option(profile, "safe_search", "0"));
body = signature_add_value(body, "profile." + name + ".block_doh", option(profile, "block_doh", "0"));
body = signature_add_value(body, "profile." + name + ".blocked_domains", join(",", list_option(profile, "blocked_domains")));
@@ -131,6 +131,13 @@ function package_version() {
let output = "";
if (command_exists("apk") && command_success_from_args([ "apk", "info", "-e", "byedpi" ])) {
output = command_output_from_args([ "apk", "list", "--installed", "byedpi" ]);
for (let line in split(output, "\n")) {
let matched = match(trim(as_string(line)), /^byedpi-([^ \t]+)/);
if (matched)
return matched[1];
}
output = command_output_from_args([ "apk", "list", "--installed", "--manifest", "byedpi" ]);
for (let line in split(output, "\n")) {
let matched = match(trim(as_string(line)), /^P:byedpi$/);
+1
View File
@@ -10,6 +10,7 @@ let shell_quote = common.shell_quote;
let read_json_file = common.read_json_file;
let read_stdin = common.read_stdin;
let read_stdin_json = common.read_stdin_json;
let write_json = common.write_json;
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
@@ -26,6 +26,7 @@ let command_from_args = common.command_from_args;
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 write_json = common.write_json;
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
const TACHYON_BIN = getenv("TACHYON_BIN") || "/usr/bin/tachyon";
+119 -13
View File
@@ -928,7 +928,53 @@ function section_rule_condition_csv(section, key, kind) {
);
}
function nft_runtime_signature_body(settings, sections) {
function nft_schedule_signature_body(body, schedule) {
let name = as_string(schedule[".name"]);
body = signature_add_value(body, "schedule." + name + ".enabled", bool_option(schedule, "enabled", true) ? "1" : "0");
let dev_ips = join(",", list_option(schedule, "device_ip"));
if (dev_ips == "") dev_ips = option(schedule, "device_ip", "");
body = signature_add_value(body, "schedule." + name + ".device_ip", dev_ips);
body = signature_add_value(body, "schedule." + name + ".profile", join(",", list_option(schedule, "profile")));
body = signature_add_value(body, "schedule." + name + ".target", option(schedule, "target", "all"));
body = signature_add_value(body, "schedule." + name + ".sections", join(",", list_option(schedule, "sections")));
body = signature_add_value(body, "schedule." + name + ".action", option(schedule, "action", "block"));
body = signature_add_value(body, "schedule." + name + ".start_time", option(schedule, "start_time", ""));
body = signature_add_value(body, "schedule." + name + ".end_time", option(schedule, "end_time", ""));
body = signature_add_value(body, "schedule." + name + ".days", join(",", list_option(schedule, "days")));
body = signature_add_value(body, "schedule." + name + ".blocked_domains", join(",", list_option(schedule, "blocked_domains")));
body = signature_add_value(body, "schedule." + name + ".mode", option(schedule, "mode", "block"));
return body;
}
function nft_profile_signature_body(body, profile) {
let name = as_string(profile[".name"]);
body = signature_add_value(body, "profile." + name + ".enabled", bool_option(profile, "enabled", true) ? "1" : "0");
let dev_ips = join(",", list_option(profile, "device_ip"));
if (dev_ips == "") dev_ips = option(profile, "device_ip", "");
body = signature_add_value(body, "profile." + name + ".device_ip", dev_ips);
body = signature_add_value(body, "profile." + name + ".safe_search", option(profile, "safe_search", "0"));
body = signature_add_value(body, "profile." + name + ".block_doh", option(profile, "block_doh", "0"));
body = signature_add_value(body, "profile." + name + ".blocked_domains", join(",", list_option(profile, "blocked_domains")));
body = signature_add_value(body, "profile." + name + ".daily_quota_minutes", option(profile, "daily_quota_minutes", "0"));
return body;
}
function nft_guest_mode_signature_body(body, guest_mode) {
if (!guest_mode) return body;
let name = as_string(guest_mode[".name"] || "guest_mode");
body = signature_add_value(body, "guest_mode." + name + ".enabled", bool_option(guest_mode, "enabled", false) ? "1" : "0");
body = signature_add_value(body, "guest_mode." + name + ".mode", option(guest_mode, "mode", "selected"));
body = signature_add_value(body, "guest_mode." + name + ".guest_devices", join(",", list_option(guest_mode, "guest_devices")));
body = signature_add_value(body, "guest_mode." + name + ".trusted_devices", join(",", list_option(guest_mode, "trusted_devices")));
body = signature_add_value(body, "guest_mode." + name + ".isolate_lan", bool_option(guest_mode, "isolate_lan", true) ? "1" : "0");
body = signature_add_value(body, "guest_mode." + name + ".block_router_admin", bool_option(guest_mode, "block_router_admin", true) ? "1" : "0");
body = signature_add_value(body, "guest_mode." + name + ".start_time", option(guest_mode, "start_time", ""));
body = signature_add_value(body, "guest_mode." + name + ".end_time", option(guest_mode, "end_time", ""));
body = signature_add_value(body, "guest_mode." + name + ".days", join(",", list_option(guest_mode, "days")));
return body;
}
function nft_runtime_signature_body(settings, sections, schedules, profiles, guest_modes) {
let body = "";
body = signature_add_value(body, "settings.source_network_interfaces", option(settings, "source_network_interfaces", "br-lan"));
@@ -963,7 +1009,8 @@ function nft_runtime_signature_body(settings, sections) {
body = signature_add_value(body, "rule." + name + ".excluded_ips", option(section, "excluded_ips", ""));
body = signature_add_value(body, "rule." + name + ".excluded_protocol", option(section, "excluded_protocol", ""));
body = signature_add_value(body, "rule." + name + ".protocol", option(section, "protocol", ""));
body = signature_add_value(body, "rule." + name + ".community_subnet_lists", rule_config.filter_community_subnet_lists_value(connections.community_lists_value(section)));
let comm_subnets = bool_option(section, "community_subnets", true) ? rule_config.filter_community_subnet_lists_value(connections.community_lists_value(section)) : "";
body = signature_add_value(body, "rule." + name + ".community_subnet_lists", comm_subnets);
body = signature_add_value(body, "rule." + name + ".remote_subnet_lists", option(section, "remote_subnet_lists", ""));
body = signature_add_value(body, "rule." + name + ".rule_set_with_subnets", connections.rule_sets_with_subnets_value(section));
@@ -971,6 +1018,17 @@ function nft_runtime_signature_body(settings, sections) {
body = signature_add_value(body, "rule." + name + ".dscp", connections.dscp_value(section));
}
for (let profile in profiles)
body = nft_profile_signature_body(body, object_or_empty(profile));
for (let schedule in schedules)
body = nft_schedule_signature_body(body, object_or_empty(schedule));
if (guest_modes) {
for (let gm in guest_modes)
body = nft_guest_mode_signature_body(body, object_or_empty(gm));
}
return body;
}
@@ -1501,7 +1559,43 @@ function append_sing_box_server_signature_body(body, server) {
return body;
}
function sing_box_signature_body(settings, sections, servers, mwan3_active) {
function append_sing_box_profile_signature_body(body, profile) {
let name = section_name(profile);
if (name == "") return body;
let prefix = "sing_box.profile." + name;
let enabled = bool_option(profile, "enabled", true) ? "1" : "0";
body = signature_add_value(body, prefix + ".enabled", enabled);
let dev_ips = join(",", list_option(profile, "device_ip"));
if (dev_ips == "") dev_ips = option(profile, "device_ip", "");
body = signature_add_value(body, prefix + ".device_ip", dev_ips);
body = signature_add_value(body, prefix + ".safe_search", option(profile, "safe_search", "0"));
body = signature_add_value(body, prefix + ".block_doh", option(profile, "block_doh", "0"));
body = signature_add_value(body, prefix + ".blocked_domains", join(",", list_option(profile, "blocked_domains")));
return body;
}
function append_sing_box_schedule_signature_body(body, schedule) {
let name = section_name(schedule);
if (name == "") return body;
let prefix = "sing_box.schedule." + name;
let enabled = bool_option(schedule, "enabled", true) ? "1" : "0";
body = signature_add_value(body, prefix + ".enabled", enabled);
let dev_ips = join(",", list_option(schedule, "device_ip"));
if (dev_ips == "") dev_ips = option(schedule, "device_ip", "");
body = signature_add_value(body, prefix + ".device_ip", dev_ips);
body = signature_add_value(body, prefix + ".profile", join(",", list_option(schedule, "profile")));
body = signature_add_value(body, prefix + ".blocked_domains", join(",", list_option(schedule, "blocked_domains")));
body = signature_add_value(body, prefix + ".mode", option(schedule, "mode", "block"));
body = signature_add_value(body, prefix + ".target", option(schedule, "target", "all"));
body = signature_add_value(body, prefix + ".sections", join(",", list_option(schedule, "sections")));
body = signature_add_value(body, prefix + ".action", option(schedule, "action", "block"));
body = signature_add_value(body, prefix + ".start_time", option(schedule, "start_time", ""));
body = signature_add_value(body, prefix + ".end_time", option(schedule, "end_time", ""));
body = signature_add_value(body, prefix + ".days", join(",", list_option(schedule, "days")));
return body;
}
function sing_box_signature_body(settings, sections, servers, mwan3_active, schedules, profiles) {
settings = object_or_empty(settings);
let body = "";
@@ -1557,6 +1651,12 @@ function sing_box_signature_body(settings, sections, servers, mwan3_active) {
for (let server in servers)
body = append_sing_box_server_signature_body(body, object_or_empty(server));
for (let profile in profiles)
body = append_sing_box_profile_signature_body(body, object_or_empty(profile));
for (let schedule in schedules)
body = append_sing_box_schedule_signature_body(body, object_or_empty(schedule));
return body;
}
@@ -1681,13 +1781,13 @@ function byedpi_runtime_signature_body(sections) {
return body;
}
function reload_state_values_from_sources(format, settings, sections, servers, dnsmasq, legacy_dnsmasq_present, mwan3_active_value) {
function reload_state_values_from_sources(format, settings, sections, servers, dnsmasq, legacy_dnsmasq_present, mwan3_active_value, schedules, profiles, guest_modes) {
return {
format: as_string(format),
service_trigger_signature: signature_hash(service_trigger_signature_body(settings)),
dnsmasq_signature: signature_hash(dnsmasq_signature_body(settings, dnsmasq, legacy_dnsmasq_present)),
sing_box_signature: signature_hash(sing_box_signature_body(settings, sections, servers, mwan3_active_value)),
nft_signature: signature_hash(nft_runtime_signature_body(settings, sections)),
sing_box_signature: signature_hash(sing_box_signature_body(settings, sections, servers, mwan3_active_value, schedules, profiles)),
nft_signature: signature_hash(nft_runtime_signature_body(settings, sections, schedules, profiles, guest_modes)),
zapret_queue_signature: signature_hash(action_queue_signature_body(sections, "zapret", "zapret_queue.section")),
zapret_runtime_signature: signature_hash(zapret_runtime_signature_body(sections)),
zapret2_queue_signature: signature_hash(action_queue_signature_body(sections, "zapret2", "zapret2_queue.section")),
@@ -1879,7 +1979,10 @@ function current_reload_state_values(format) {
uci_servers(),
uci_dnsmasq(),
uci_exists("dhcp.tachyon"),
mwan3_active()
mwan3_active(),
uci_sections("schedule"),
uci_sections("profile"),
uci_sections("guest_mode")
);
}
@@ -1927,7 +2030,10 @@ function fixture_reload_state_values(data, format) {
fixture_servers(data),
fixture_dnsmasq(data),
fixture_legacy_dnsmasq_present(data),
fixture_mwan3_active(data)
fixture_mwan3_active(data),
fixture_section_list(data, "schedule"),
fixture_section_list(data, "profile"),
fixture_section_list(data, "guest_mode")
);
}
@@ -2038,20 +2144,20 @@ else if (mode == "dnsmasq-signature-fixture") {
exit(print_signature_hash(dnsmasq_signature_body(fixture_settings(data), fixture_dnsmasq(data), fixture_legacy_dnsmasq_present(data))) ? 0 : 1);
}
else if (mode == "sing-box-signature")
exit(print_signature_hash(sing_box_signature_body(uci_settings(), uci_sections("section"), uci_servers(), mwan3_active())) ? 0 : 1);
exit(print_signature_hash(sing_box_signature_body(uci_settings(), uci_sections("section"), uci_servers(), mwan3_active(), uci_sections("schedule"), uci_sections("profile"))) ? 0 : 1);
else if (mode == "sing-box-signature-fixture") {
let data = fixture_data(ARGV[1]);
exit(print_signature_hash(sing_box_signature_body(fixture_settings(data), fixture_section_list(data), fixture_servers(data), fixture_mwan3_active(data))) ? 0 : 1);
exit(print_signature_hash(sing_box_signature_body(fixture_settings(data), fixture_section_list(data), fixture_servers(data), fixture_mwan3_active(data), fixture_section_list(data, "schedule"), fixture_section_list(data, "profile"))) ? 0 : 1);
}
else if (mode == "sing-box-signature-body-fixture") {
let data = fixture_data(ARGV[1]);
print(sing_box_signature_body(fixture_settings(data), fixture_section_list(data), fixture_servers(data), fixture_mwan3_active(data)));
print(sing_box_signature_body(fixture_settings(data), fixture_section_list(data), fixture_servers(data), fixture_mwan3_active(data), fixture_section_list(data, "schedule"), fixture_section_list(data, "profile")));
}
else if (mode == "nft-signature")
exit(print_signature_hash(nft_runtime_signature_body(uci_settings(), uci_sections("section"))) ? 0 : 1);
exit(print_signature_hash(nft_runtime_signature_body(uci_settings(), uci_sections("section"), uci_sections("schedule"), uci_sections("profile"), uci_sections("guest_mode"))) ? 0 : 1);
else if (mode == "nft-signature-fixture") {
let data = fixture_data(ARGV[1]);
exit(print_signature_hash(nft_runtime_signature_body(fixture_settings(data), fixture_section_list(data))) ? 0 : 1);
exit(print_signature_hash(nft_runtime_signature_body(fixture_settings(data), fixture_section_list(data), fixture_section_list(data, "schedule"), fixture_section_list(data, "profile"), fixture_section_list(data, "guest_mode"))) ? 0 : 1);
}
else if (mode == "zapret-queue-signature")
exit(print_signature_hash(action_queue_signature_body(uci_sections("section"), "zapret", "zapret_queue.section")) ? 0 : 1);