fix(config): ложное предупреждение про AWG, сироты section_interface, дубль cron
- has_outbound_section: туннельные секции (awg/warp/vpn/openvpn/masque/ anytls/snell/mieru/sudoku) теперь считаются исходящей конфигурацией - одинокая AWG-секция больше не получает вводящее в заблуждение «No proxy outbound sections found» (жалобы пользователей #39/#42) - новая миграция orphan_section_interface_cleanup: удаление секции маршрутизации раньше оставляло после себя дочерние section_interface, ссылающиеся на несуществующего родителя (у пользователя - запись на секцию '2' при живой только '1'); сироты удаляются через новую операцию delete_section; функция объявлена до массива MIGRATIONS - ucode не поднимает объявления - strip_unmarked_tachyon_cron_lines: старые версии писали cron-строки без маркер-комментария, они переживали фильтрацию по маркерам, копились при обновлениях и crond запускал component_updates_if_due дважды за тик; немаркированные строки с нашими командами теперь вычищаются при каждом refresh и remove
This commit is contained in:
@@ -710,6 +710,36 @@ function cron_refresh_plan(settings, sections, bin, list_marker, subscription_ma
|
||||
print_cron_refresh_plan(cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker, component_marker));
|
||||
}
|
||||
|
||||
// Older releases wrote tachyon cron jobs WITHOUT the marker comment; those
|
||||
// lines survive marker-based filtering, stack up across upgrades and make
|
||||
// crond run the same job twice per tick. Drop unmarked lines that invoke a
|
||||
// tachyon-owned cron command via our binary.
|
||||
function strip_unmarked_tachyon_cron_lines(data, bin, markers) {
|
||||
data = as_string(data);
|
||||
if (data == "")
|
||||
return "";
|
||||
|
||||
let escaped_bin = replace(as_string(bin), /([^0-9A-Za-z_])/g, "\\$1");
|
||||
let pattern = "^[^#\\n]*" + escaped_bin + "[ \\t]+(list_update|subscription_update|component_updates_if_due)([ \\t]|$)";
|
||||
|
||||
let lines = split(data, "\n");
|
||||
let has_trailing_newline = substr(data, length(data) - 1) == "\n";
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < length(lines); i++) {
|
||||
let line = as_string(lines[i]);
|
||||
if (i == length(lines) - 1 && has_trailing_newline && line == "")
|
||||
continue;
|
||||
if (line_contains_any_marker(line, markers))
|
||||
continue;
|
||||
if (match(line, pattern) != null)
|
||||
continue;
|
||||
result += line + "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function cron_refresh_apply_result(settings, sections, existing_crontab, bin, list_marker, subscription_marker, component_marker) {
|
||||
let plan = cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker, component_marker);
|
||||
let filtered_crontab = filter_cron_markers_text(existing_crontab, [ list_marker, subscription_marker, component_marker ]);
|
||||
@@ -754,7 +784,9 @@ function cron_refresh_apply_result(settings, sections, existing_crontab, bin, li
|
||||
|
||||
return {
|
||||
status: int(plan.status || 0),
|
||||
crontab: filtered_crontab + (int(plan.status || 0) == 0 ? cron_jobs : ""),
|
||||
crontab: strip_unmarked_tachyon_cron_lines(
|
||||
filtered_crontab, bin, [ list_marker, subscription_marker, component_marker ]
|
||||
) + (int(plan.status || 0) == 0 ? cron_jobs : ""),
|
||||
logs
|
||||
};
|
||||
}
|
||||
@@ -774,6 +806,11 @@ function write_crontab_text(text) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Older releases wrote tachyon cron jobs WITHOUT the marker comment; those
|
||||
// lines survive marker-based filtering, stack up across upgrades and make
|
||||
// crond run the same job twice per tick. Drop unmarked lines that invoke a
|
||||
// tachyon-owned cron command via our binary.
|
||||
|
||||
function log_cron_apply_result(result) {
|
||||
for (let item in array_or_empty(object_or_empty(result).logs))
|
||||
log_message(item.message, item.level);
|
||||
@@ -781,8 +818,10 @@ function log_cron_apply_result(result) {
|
||||
|
||||
function remove_cron_jobs(list_marker, subscription_marker, component_marker) {
|
||||
let crontab = command_output_from_args([ "crontab", "-l" ]);
|
||||
let markers = [ list_marker, subscription_marker, component_marker ];
|
||||
let result = {
|
||||
crontab: filter_cron_markers_text(crontab, [ list_marker, subscription_marker, component_marker ]),
|
||||
crontab: strip_unmarked_tachyon_cron_lines(
|
||||
filter_cron_markers_text(crontab, markers), BIN_PATH, markers),
|
||||
logs: [ { level: "info", message: "The cron job removed" } ]
|
||||
};
|
||||
|
||||
|
||||
@@ -1374,14 +1374,40 @@ function migrate_global_hosts_to_section(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
function migrate_orphan_section_interfaces(ctx) {
|
||||
// Deleting a routing section used to leave its section_interface child
|
||||
// records behind. A parent-less record never matches any section during
|
||||
// config generation - dead weight that only confuses config readers.
|
||||
let owners = {};
|
||||
for (let section in ctx.model.sections)
|
||||
owners[section_name(section)] = true;
|
||||
|
||||
let kept = [];
|
||||
let changed = false;
|
||||
for (let child in ctx.model.section_interface || []) {
|
||||
let owner = option(child, "section", "");
|
||||
if (owner != "" && !owners[owner]) {
|
||||
record_operation(ctx, { op: "delete_section", section: section_name(child) });
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
push(kept, child);
|
||||
}
|
||||
|
||||
if (changed)
|
||||
ctx.model.section_interface = kept;
|
||||
}
|
||||
const MIGRATIONS = [
|
||||
{ id: "interface_sections", run: migrate_interface_sections },
|
||||
{ id: "enable_component_checks", run: migrate_enable_component_checks },
|
||||
{ id: "http_connection_urls", run: migrate_http_connection_urls },
|
||||
{ id: "dns_hosts_to_option", run: migrate_dns_hosts_to_option },
|
||||
{ id: "global_hosts_to_section", run: migrate_global_hosts_to_section }
|
||||
{ id: "global_hosts_to_section", run: migrate_global_hosts_to_section },
|
||||
{ id: "orphan_section_interface_cleanup", run: migrate_orphan_section_interfaces }
|
||||
];
|
||||
|
||||
|
||||
|
||||
function apply_migrations(ctx) {
|
||||
ctx.source_release = option(ctx.model.settings, CONFIG_VERSION_OPTION, "");
|
||||
|
||||
@@ -1551,6 +1577,8 @@ function apply_operations(cursor, operations) {
|
||||
cursor.set(CONFIG_NAME, section_ref(op.section), op.option, op.value);
|
||||
else if (op.op == "delete")
|
||||
cursor.delete(CONFIG_NAME, section_ref(op.section), op.option);
|
||||
else if (op.op == "delete_section")
|
||||
cursor.delete(CONFIG_NAME, section_ref(op.section));
|
||||
else if (op.op == "add_list")
|
||||
cursor.set(CONFIG_NAME, section_ref(op.section), op.option, op.values);
|
||||
else if (op.op == "set_list")
|
||||
|
||||
@@ -2054,6 +2054,13 @@ function has_outbound_section(ctx) {
|
||||
if (action == "zapret2" && ctx.zapret2_installed)
|
||||
return true;
|
||||
|
||||
// Tunnel-style actions generate real sing-box endpoints/outbounds;
|
||||
// without this check a lone AWG/WARP/VPN section triggered the
|
||||
// misleading "No proxy outbound sections found" warning.
|
||||
if (contains([ "awg", "warp", "vpn", "openvpn", "masque",
|
||||
"anytls", "snell", "mieru", "sudoku" ], as_string(action)))
|
||||
return true;
|
||||
|
||||
if (length(connections.connection_urls(section)) > 0 ||
|
||||
length(connections.subscription_urls(section)) > 0 ||
|
||||
length(connections.outbound_jsons(section)) > 0 ||
|
||||
|
||||
Reference in New Issue
Block a user