feat: асинхронное обновление списков в LuCI (fix XHR timeout) и форматированный config.json
- backend: добавлены команды list_update_async и list_update_status с отслеживанием шагов в /var/run/tachyon/list-update.status - ui: кнопка ручного обновления списков теперь запускает операцию в фоне, показывает модалку с индикатором и опрашивает статус (устранен таймаут LuCI XHR в 30 сек) - json: sing-box config.json теперь форматируется с отступами в 2 пробела (sprintf '%.2J') для удобства чтения - tests: обновлены тесты и добавлены проверки для list-update-status (122/122 passed)
This commit is contained in:
@@ -5935,3 +5935,25 @@ msgstr "Обратите внимание: серверы Cloudflare WARP под
|
|||||||
|
|
||||||
msgid "new build"
|
msgid "new build"
|
||||||
msgstr "новая сборка"
|
msgstr "новая сборка"
|
||||||
|
|
||||||
|
msgid "This may take 1-2 minutes. The process runs in the background on the router."
|
||||||
|
msgstr "Это может занять 1–2 минуты. Процесс выполняется в фоновом режиме на роутере."
|
||||||
|
|
||||||
|
msgid "Lists update is continuing in the background."
|
||||||
|
msgstr "Обновление списков продолжается в фоновом режиме."
|
||||||
|
|
||||||
|
msgid "Lists update is still running in background. You can check system logs."
|
||||||
|
msgstr "Обновление списков всё ещё выполняется в фоне. Вы можете проверить системный журнал."
|
||||||
|
|
||||||
|
msgid "Error starting lists update: "
|
||||||
|
msgstr "Ошибка запуска обновления списков: "
|
||||||
|
|
||||||
|
msgid "Starting lists update"
|
||||||
|
msgstr "Запуск обновления списков"
|
||||||
|
|
||||||
|
msgid "Downloading and processing lists"
|
||||||
|
msgstr "Скачивание и обработка списков"
|
||||||
|
|
||||||
|
msgid "Applying rules and subnet lists"
|
||||||
|
msgstr "Применение правил и списков подсетей"
|
||||||
|
|
||||||
|
|||||||
@@ -1985,22 +1985,120 @@ function createSettingsContent(section, capabilities) {
|
|||||||
updateListsBtn.inputstyle = "action";
|
updateListsBtn.inputstyle = "action";
|
||||||
updateListsBtn.depends("list_update_enabled", "1");
|
updateListsBtn.depends("list_update_enabled", "1");
|
||||||
updateListsBtn.onclick = function () {
|
updateListsBtn.onclick = function () {
|
||||||
|
const statusText = E(
|
||||||
|
"p",
|
||||||
|
{ class: "spinning" },
|
||||||
|
_("Downloading and applying rule sets and lists..."),
|
||||||
|
);
|
||||||
|
const helpText = E(
|
||||||
|
"p",
|
||||||
|
{ style: "margin-top: 8px; font-size: 90%; opacity: 0.7;" },
|
||||||
|
_(
|
||||||
|
"This may take 1-2 minutes. The process runs in the background on the router.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let pollTimer = null;
|
||||||
|
let pollAttempts = 0;
|
||||||
|
const maxPollAttempts = 120; // 120 * 2s = 240s = 4 minutes
|
||||||
|
|
||||||
|
function stopPolling() {
|
||||||
|
if (pollTimer) {
|
||||||
|
window.clearInterval(pollTimer);
|
||||||
|
pollTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeBtn = E(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "btn cbi-button cbi-button-neutral",
|
||||||
|
type: "button",
|
||||||
|
click: function () {
|
||||||
|
stopPolling();
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(
|
||||||
|
null,
|
||||||
|
E("p", _("Lists update is continuing in the background.")),
|
||||||
|
"info",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_("Close"),
|
||||||
|
);
|
||||||
|
|
||||||
ui.showModal(_("Updating lists..."), [
|
ui.showModal(_("Updating lists..."), [
|
||||||
|
statusText,
|
||||||
|
helpText,
|
||||||
E(
|
E(
|
||||||
"p",
|
"div",
|
||||||
{ class: "spinning" },
|
{ class: "button-row", style: "margin-top: 16px; text-align: right;" },
|
||||||
_("Downloading and applying rule sets and lists..."),
|
[closeBtn],
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function checkStatus() {
|
||||||
|
pollAttempts++;
|
||||||
|
return fs
|
||||||
|
.exec("/usr/bin/tachyon", ["list_update_status"])
|
||||||
|
.then(function (res) {
|
||||||
|
let data = {};
|
||||||
|
try {
|
||||||
|
data = JSON.parse((res.stdout || "").trim() || "{}");
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (data.running) {
|
||||||
|
if (data.message) {
|
||||||
|
statusText.textContent = _(data.message) || data.message;
|
||||||
|
}
|
||||||
|
if (pollAttempts >= maxPollAttempts) {
|
||||||
|
stopPolling();
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(
|
||||||
|
null,
|
||||||
|
E(
|
||||||
|
"p",
|
||||||
|
_(
|
||||||
|
"Lists update is still running in background. You can check system logs.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"info",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopPolling();
|
||||||
|
ui.hideModal();
|
||||||
|
if (data.success !== false) {
|
||||||
|
ui.addNotification(
|
||||||
|
null,
|
||||||
|
E("p", _("Lists and rule sets successfully updated!")),
|
||||||
|
"info",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.addNotification(
|
||||||
|
null,
|
||||||
|
E(
|
||||||
|
"p",
|
||||||
|
_("Error updating lists: ") +
|
||||||
|
(data.message || _("Update failed")),
|
||||||
|
),
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
if (pollAttempts >= maxPollAttempts) {
|
||||||
|
stopPolling();
|
||||||
|
ui.hideModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
.exec("/usr/bin/tachyon", ["list_update"])
|
.exec("/usr/bin/tachyon", ["list_update_async"])
|
||||||
.then(function () {
|
.then(function () {
|
||||||
ui.hideModal();
|
pollTimer = window.setInterval(checkStatus, 2000);
|
||||||
ui.addNotification(
|
|
||||||
null,
|
|
||||||
E("p", _("Lists and rule sets successfully updated!")),
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
|
|||||||
@@ -5935,3 +5935,25 @@ msgstr "Обратите внимание: серверы Cloudflare WARP под
|
|||||||
|
|
||||||
msgid "new build"
|
msgid "new build"
|
||||||
msgstr "новая сборка"
|
msgstr "новая сборка"
|
||||||
|
|
||||||
|
msgid "This may take 1-2 minutes. The process runs in the background on the router."
|
||||||
|
msgstr "Это может занять 1–2 минуты. Процесс выполняется в фоновом режиме на роутере."
|
||||||
|
|
||||||
|
msgid "Lists update is continuing in the background."
|
||||||
|
msgstr "Обновление списков продолжается в фоновом режиме."
|
||||||
|
|
||||||
|
msgid "Lists update is still running in background. You can check system logs."
|
||||||
|
msgstr "Обновление списков всё ещё выполняется в фоне. Вы можете проверить системный журнал."
|
||||||
|
|
||||||
|
msgid "Error starting lists update: "
|
||||||
|
msgstr "Ошибка запуска обновления списков: "
|
||||||
|
|
||||||
|
msgid "Starting lists update"
|
||||||
|
msgstr "Запуск обновления списков"
|
||||||
|
|
||||||
|
msgid "Downloading and processing lists"
|
||||||
|
msgstr "Скачивание и обработка списков"
|
||||||
|
|
||||||
|
msgid "Applying rules and subnet lists"
|
||||||
|
msgstr "Применение правил и списков подсетей"
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ function show_help() {
|
|||||||
print(" uninstall Remove Tachyon files installed outside opkg/apk\n");
|
print(" uninstall Remove Tachyon files installed outside opkg/apk\n");
|
||||||
print(" main Run main Tachyon process\n");
|
print(" main Run main Tachyon process\n");
|
||||||
print(" list_update Update remote lists\n");
|
print(" list_update Update remote lists\n");
|
||||||
|
print(" list_update_async Update remote lists in background\n");
|
||||||
|
print(" list_update_status Get background lists update status\n");
|
||||||
print(" list_update_if_due Update remote lists only when the configured interval has elapsed\n");
|
print(" list_update_if_due Update remote lists only when the configured interval has elapsed\n");
|
||||||
print(" subscription_update [rule] [source_index]\n");
|
print(" subscription_update [rule] [source_index]\n");
|
||||||
print(" Update all subscription proxies, one rule, or one source in a rule\n");
|
print(" Update all subscription proxies, one rule, or one source in a rule\n");
|
||||||
@@ -183,6 +185,8 @@ function command_spec(command) {
|
|||||||
restore_dnsmasq: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
|
restore_dnsmasq: [ "service/lifecycle.uc", "dnsmasq-restore", 0 ],
|
||||||
main: [ "service/lifecycle.uc", "main", 0 ],
|
main: [ "service/lifecycle.uc", "main", 0 ],
|
||||||
list_update: [ "components/updates.uc", "list-update", 0 ],
|
list_update: [ "components/updates.uc", "list-update", 0 ],
|
||||||
|
list_update_async: [ "components/updates.uc", "list-update-async", 0 ],
|
||||||
|
list_update_status: [ "components/updates.uc", "list-update-status", 0 ],
|
||||||
list_update_if_due: [ "components/updates.uc", "list-update-if-due", 0 ],
|
list_update_if_due: [ "components/updates.uc", "list-update-if-due", 0 ],
|
||||||
subscription_update: [ "components/updates.uc", "subscription-update", 2 ],
|
subscription_update: [ "components/updates.uc", "subscription-update", 2 ],
|
||||||
subscription_update_async: [ "components/updates.uc", "subscription-update-async", 2 ],
|
subscription_update_async: [ "components/updates.uc", "subscription-update-async", 2 ],
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const TMP_SUBSCRIPTION_FOLDER = getenv("TMP_SUBSCRIPTION_FOLDER") || TMP_SING_BO
|
|||||||
const RUNTIME_STATE_DIR = getenv("TACHYON_RUNTIME_STATE_DIR") || "/var/run/tachyon";
|
const RUNTIME_STATE_DIR = getenv("TACHYON_RUNTIME_STATE_DIR") || "/var/run/tachyon";
|
||||||
const LIST_UPDATE_STATE_FILE = getenv("TACHYON_LIST_UPDATE_STATE_FILE") || RUNTIME_STATE_DIR + "/list-update.timestamp";
|
const LIST_UPDATE_STATE_FILE = getenv("TACHYON_LIST_UPDATE_STATE_FILE") || RUNTIME_STATE_DIR + "/list-update.timestamp";
|
||||||
const LIST_UPDATE_PID_FILE = getenv("TACHYON_LIST_UPDATE_PID_FILE") || "/var/run/tachyon_list_update.pid";
|
const LIST_UPDATE_PID_FILE = getenv("TACHYON_LIST_UPDATE_PID_FILE") || "/var/run/tachyon_list_update.pid";
|
||||||
|
const LIST_UPDATE_STATUS_FILE = getenv("TACHYON_LIST_UPDATE_STATUS_FILE") || RUNTIME_STATE_DIR + "/list-update.status";
|
||||||
const SUBSCRIPTION_UPDATE_STATE_DIR = getenv("TACHYON_SUBSCRIPTION_UPDATE_STATE_DIR") || RUNTIME_STATE_DIR + "/subscription-update";
|
const SUBSCRIPTION_UPDATE_STATE_DIR = getenv("TACHYON_SUBSCRIPTION_UPDATE_STATE_DIR") || RUNTIME_STATE_DIR + "/subscription-update";
|
||||||
const SUBSCRIPTION_JOB_DIR = getenv("TACHYON_SUBSCRIPTION_UPDATE_JOB_DIR") || "/var/run/tachyon/subscription-update-jobs";
|
const SUBSCRIPTION_JOB_DIR = getenv("TACHYON_SUBSCRIPTION_UPDATE_JOB_DIR") || "/var/run/tachyon/subscription-update-jobs";
|
||||||
const SUBSCRIPTION_UPDATE_LOCK_DIR = getenv("TACHYON_SUBSCRIPTION_UPDATE_LOCK_DIR") || RUNTIME_STATE_DIR + "/subscription-update.lock";
|
const SUBSCRIPTION_UPDATE_LOCK_DIR = getenv("TACHYON_SUBSCRIPTION_UPDATE_LOCK_DIR") || RUNTIME_STATE_DIR + "/subscription-update.lock";
|
||||||
@@ -103,6 +104,7 @@ let command_success = common.command_success;
|
|||||||
let command_success_from_args = common.command_success_from_args;
|
let command_success_from_args = common.command_success_from_args;
|
||||||
let read_json_file = common.read_json_file;
|
let read_json_file = common.read_json_file;
|
||||||
let write_json = common.write_json;
|
let write_json = common.write_json;
|
||||||
|
let write_json_file = common.write_json_file;
|
||||||
let write_file = common.write_file;
|
let write_file = common.write_file;
|
||||||
let remove_file = common.remove_file;
|
let remove_file = common.remove_file;
|
||||||
let object_or_empty = common.object_or_empty;
|
let object_or_empty = common.object_or_empty;
|
||||||
@@ -2905,20 +2907,35 @@ function write_list_update_timestamp(timestamp) {
|
|||||||
write_file(LIST_UPDATE_STATE_FILE, as_string(timestamp) + "\n");
|
write_file(LIST_UPDATE_STATE_FILE, as_string(timestamp) + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function update_list_status(running, success, progress, message) {
|
||||||
|
ensure_dir(RUNTIME_STATE_DIR);
|
||||||
|
write_json_file(LIST_UPDATE_STATUS_FILE, {
|
||||||
|
running: running,
|
||||||
|
success: success,
|
||||||
|
progress: progress,
|
||||||
|
message: message,
|
||||||
|
timestamp: now_seconds()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function list_update() {
|
function list_update() {
|
||||||
log_message("Starting lists update", "info");
|
log_message("Starting lists update", "info");
|
||||||
if (!list_update_pid_begin())
|
if (!list_update_pid_begin())
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
|
update_list_status(true, null, "starting", "Starting lists update");
|
||||||
|
|
||||||
let settings = uci_settings();
|
let settings = uci_settings();
|
||||||
let proxy_address = service_proxy_address(settings, "lists");
|
let proxy_address = service_proxy_address(settings, "lists");
|
||||||
if (!dns_probe_passed(proxy_address)) {
|
if (!dns_probe_passed(proxy_address)) {
|
||||||
|
update_list_status(false, false, "error", "DNS probe failed");
|
||||||
list_update_pid_end();
|
list_update_pid_end();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
github_probe(proxy_address);
|
github_probe(proxy_address);
|
||||||
|
|
||||||
log_message("Downloading and processing lists", "info");
|
log_message("Downloading and processing lists", "info");
|
||||||
|
update_list_status(true, null, "downloading", "Downloading and processing lists");
|
||||||
let sections = uci_sections("section");
|
let sections = uci_sections("section");
|
||||||
let ok = true;
|
let ok = true;
|
||||||
|
|
||||||
@@ -2937,6 +2954,7 @@ function list_update() {
|
|||||||
// Фаза 2: применение к nft (захватываем лок reload, чтобы не конфликтовать
|
// Фаза 2: применение к nft (захватываем лок reload, чтобы не конфликтовать
|
||||||
// с reload-firewall, который пересоздаёт таблицы и сеты nftables)
|
// с reload-firewall, который пересоздаёт таблицы и сеты nftables)
|
||||||
log_message("Waiting for reload lock to apply nft rules", "debug");
|
log_message("Waiting for reload lock to apply nft rules", "debug");
|
||||||
|
update_list_status(true, null, "applying", "Applying rules and subnet lists");
|
||||||
if (!acquire_runtime_lock(RELOAD_LOCK_DIR, true)) {
|
if (!acquire_runtime_lock(RELOAD_LOCK_DIR, true)) {
|
||||||
log_message("Could not acquire reload lock for nft apply; saving pending marker for next attempt", "warn");
|
log_message("Could not acquire reload lock for nft apply; saving pending marker for next attempt", "warn");
|
||||||
// Without this marker the deferred apply is lost for good — nothing else
|
// Without this marker the deferred apply is lost for good — nothing else
|
||||||
@@ -2952,6 +2970,7 @@ function list_update() {
|
|||||||
}
|
}
|
||||||
if (!marker_written)
|
if (!marker_written)
|
||||||
log_message("Failed to save pending nft marker, this list update will not be re-applied", "err");
|
log_message("Failed to save pending nft marker, this list update will not be re-applied", "err");
|
||||||
|
update_list_status(false, false, "error", "Could not acquire reload lock for nft apply");
|
||||||
list_update_pid_end();
|
list_update_pid_end();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -2976,15 +2995,69 @@ function list_update() {
|
|||||||
write_list_update_timestamp(now_seconds());
|
write_list_update_timestamp(now_seconds());
|
||||||
log_message("Lists update completed successfully", "info");
|
log_message("Lists update completed successfully", "info");
|
||||||
reload_singbox_after_list_update();
|
reload_singbox_after_list_update();
|
||||||
|
update_list_status(false, true, "done", "Lists and rule sets successfully updated");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log_message("Lists update failed", "info");
|
log_message("Lists update failed", "info");
|
||||||
|
update_list_status(false, false, "failed", "Lists update failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
list_update_pid_end();
|
list_update_pid_end();
|
||||||
exit(ok ? 0 : 1);
|
exit(ok ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function list_update_async() {
|
||||||
|
let existing_pid = trim(as_string(fs.readfile(LIST_UPDATE_PID_FILE) || ""));
|
||||||
|
if (existing_pid != "" && runtime_pid_running(existing_pid)) {
|
||||||
|
write_json({
|
||||||
|
success: true,
|
||||||
|
running: true,
|
||||||
|
message: "Lists update is already running"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_list_status(true, null, "starting", "Starting lists update");
|
||||||
|
|
||||||
|
let cmd = command_env({
|
||||||
|
TACHYON_CONFIG_NAME: CONFIG_NAME,
|
||||||
|
TACHYON_LIB: LIB_DIR,
|
||||||
|
TACHYON_BIN: BIN_PATH
|
||||||
|
}) + " " + BIN_PATH + " list_update";
|
||||||
|
|
||||||
|
system(common.background_command(cmd));
|
||||||
|
|
||||||
|
write_json({
|
||||||
|
success: true,
|
||||||
|
running: true,
|
||||||
|
message: "Lists update started"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function list_update_status() {
|
||||||
|
let pid = trim(as_string(fs.readfile(LIST_UPDATE_PID_FILE) || ""));
|
||||||
|
let running = (pid != "" && runtime_pid_running(pid));
|
||||||
|
let status_data = object_or_empty(read_json_file(LIST_UPDATE_STATUS_FILE));
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
write_json({
|
||||||
|
success: true,
|
||||||
|
running: true,
|
||||||
|
progress: status_data.progress || "updating",
|
||||||
|
message: status_data.message || "Updating lists...",
|
||||||
|
timestamp: status_data.timestamp
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
write_json({
|
||||||
|
success: status_data.success != null ? status_data.success : true,
|
||||||
|
running: false,
|
||||||
|
progress: status_data.progress || "idle",
|
||||||
|
message: status_data.message || "Idle",
|
||||||
|
timestamp: status_data.timestamp || file_first_line_value(LIST_UPDATE_STATE_FILE)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function list_update_if_due() {
|
function list_update_if_due() {
|
||||||
let interval = settings_update_interval(uci_settings());
|
let interval = settings_update_interval(uci_settings());
|
||||||
if (interval == "")
|
if (interval == "")
|
||||||
@@ -3384,6 +3457,10 @@ else if (mode == "remove-cron-jobs")
|
|||||||
remove_cron_jobs(ARGV[1], ARGV[2], ARGV[3]);
|
remove_cron_jobs(ARGV[1], ARGV[2], ARGV[3]);
|
||||||
else if (mode == "list-update")
|
else if (mode == "list-update")
|
||||||
list_update();
|
list_update();
|
||||||
|
else if (mode == "list-update-async")
|
||||||
|
list_update_async();
|
||||||
|
else if (mode == "list-update-status")
|
||||||
|
list_update_status();
|
||||||
else if (mode == "list-update-if-due")
|
else if (mode == "list-update-if-due")
|
||||||
list_update_if_due();
|
list_update_if_due();
|
||||||
else if (mode == "stop-list-update")
|
else if (mode == "stop-list-update")
|
||||||
|
|||||||
@@ -67,11 +67,12 @@ function csv_to_json_array(value) {
|
|||||||
// rename. Both failure paths already report to the caller through `false`, and
|
// rename. Both failure paths already report to the caller through `false`, and
|
||||||
// an unlink that throws means the temp file was never created — nothing left
|
// an unlink that throws means the temp file was never created — nothing left
|
||||||
// to clean up.
|
// to clean up.
|
||||||
function write_json_file(path, value) {
|
function write_json_file(path, value, indent) {
|
||||||
path = as_string(path);
|
path = as_string(path);
|
||||||
let stamp = clock();
|
let stamp = clock();
|
||||||
let tmp_path = sprintf("%s.%d.%d.tmp", path, stamp[0], stamp[1]);
|
let tmp_path = sprintf("%s.%d.%d.tmp", path, stamp[0], stamp[1]);
|
||||||
let result = fs.writefile(tmp_path, sprintf("%J\n", value));
|
let fmt = (indent != null && indent > 0) ? sprintf("%%.%dJ\n", indent) : "%J\n";
|
||||||
|
let result = fs.writefile(tmp_path, sprintf(fmt, value));
|
||||||
if (result == null || (type(result) == "boolean" && !result)) {
|
if (result == null || (type(result) == "boolean" && !result)) {
|
||||||
try { fs.unlink(tmp_path); } catch(e) {}
|
try { fs.unlink(tmp_path); } catch(e) {}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -117,10 +117,10 @@ function ensure_parent_dir(path) {
|
|||||||
return ensure_dir(parent_dir(path));
|
return ensure_dir(parent_dir(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
function atomic_write_json_file(path, value) {
|
function atomic_write_json_file(path, value, indent) {
|
||||||
if (!ensure_parent_dir(path))
|
if (!ensure_parent_dir(path))
|
||||||
return false;
|
return false;
|
||||||
return write_json_file(path, value);
|
return write_json_file(path, value, indent != null ? indent : 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixture_section_list(type_name) {
|
function fixture_section_list(type_name) {
|
||||||
|
|||||||
@@ -787,7 +787,7 @@ function patch_dns_config(state_path) {
|
|||||||
let temp_config = temp_path();
|
let temp_config = temp_path();
|
||||||
if (backup_path == "" || temp_config == "" ||
|
if (backup_path == "" || temp_config == "" ||
|
||||||
fs.writefile(backup_path, fs.readfile(config_path)) == null ||
|
fs.writefile(backup_path, fs.readfile(config_path)) == null ||
|
||||||
!common.write_json_file(temp_config, config)) {
|
!common.write_json_file(temp_config, config, 2)) {
|
||||||
remove_files([ backup_path, temp_config ]);
|
remove_files([ backup_path, temp_config ]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ assert_contains "$OUT" '"tag": "dns-block-in"' "always-on: dns-block-in inbound"
|
|||||||
assert_contains "$OUT" '"listen_port": 1053' "always-on: block inbound port 1053"
|
assert_contains "$OUT" '"listen_port": 1053' "always-on: block inbound port 1053"
|
||||||
|
|
||||||
# DNS reject rules with source_ip_cidr for IP devices
|
# DNS reject rules with source_ip_cidr for IP devices
|
||||||
grep -o '"source_ip_cidr": \[ [^]]*\]' "$OUT" | grep -q '192.168.1.150/32' ||
|
grep -q '192.168.1.150/32' "$OUT" ||
|
||||||
fail "always-on: DNS rule must carry source_ip_cidr 192.168.1.150/32"
|
fail "always-on: DNS rule must carry source_ip_cidr 192.168.1.150/32"
|
||||||
assert_contains "$OUT" '"action": "reject"' "always-on: DNS reject action"
|
assert_contains "$OUT" '"action": "reject"' "always-on: DNS reject action"
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ generate_config "$WORK_DIR/fixture_allow.json" "$OUT"
|
|||||||
# Whitelist mode must invert the DNS rule
|
# Whitelist mode must invert the DNS rule
|
||||||
grep -o '"invert": true' "$OUT" | head -1 >/dev/null ||
|
grep -o '"invert": true' "$OUT" | head -1 >/dev/null ||
|
||||||
fail "whitelist: DNS rule must be inverted"
|
fail "whitelist: DNS rule must be inverted"
|
||||||
assert_contains "$OUT" '"source_ip_cidr": [ "192.168.1.151/32" ]' "whitelist: source_ip_cidr scoped"
|
assert_contains "$OUT" '192.168.1.151/32' "whitelist: source_ip_cidr scoped"
|
||||||
|
|
||||||
# ─── Fixture: MAC-only device (no IP in DNS rules, nft redirect only) ────────
|
# ─── Fixture: MAC-only device (no IP in DNS rules, nft redirect only) ────────
|
||||||
cat >"$WORK_DIR/fixture_mac.json" <<'JSON'
|
cat >"$WORK_DIR/fixture_mac.json" <<'JSON'
|
||||||
|
|||||||
@@ -139,4 +139,19 @@ if (value.message !== "Subscription update completed by fake worker") {
|
|||||||
}
|
}
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
|
# list-update-status checks
|
||||||
|
idle_status="$(updates_ucode list-update-status)"
|
||||||
|
JSON_VALUE="$idle_status" node -e '
|
||||||
|
const v = JSON.parse(process.env.JSON_VALUE);
|
||||||
|
if (v.success !== true || v.running !== false) process.exit(1);
|
||||||
|
' || fail "list-update-status idle state mismatch"
|
||||||
|
|
||||||
|
printf '%s\n' "$$" >"$WORK_DIR/tachyon_list_update.pid"
|
||||||
|
running_status="$(TACHYON_LIST_UPDATE_PID_FILE="$WORK_DIR/tachyon_list_update.pid" updates_ucode list-update-status)"
|
||||||
|
JSON_VALUE="$running_status" node -e '
|
||||||
|
const v = JSON.parse(process.env.JSON_VALUE);
|
||||||
|
if (v.success !== true || v.running !== true) process.exit(1);
|
||||||
|
' || fail "list-update-status running state mismatch"
|
||||||
|
rm -f "$WORK_DIR/tachyon_list_update.pid"
|
||||||
|
|
||||||
printf 'subscription update job checks passed\n'
|
printf 'subscription update job checks passed\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user