diff --git a/Plugin/unraid/api/conf_toggle.php b/Plugin/unraid/api/conf_toggle.php index d246570..2391db1 100644 --- a/Plugin/unraid/api/conf_toggle.php +++ b/Plugin/unraid/api/conf_toggle.php @@ -76,11 +76,24 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $id = trim($_POST['id'] ?? ''); $enabled = ($_POST['enabled'] ?? '0') === '1'; +// Which orchestrator's list this click came from. Optional for callers that have only one, but +// the scheduler always sends it: without it the toggle acts on whichever array declares the +// script first, which for a script listed in three is right by luck at best. +$array = trim($_POST['array'] ?? ''); if (!$id || !preg_match('/^[a-zA-Z0-9_.\/\-]+\.sh$/', $id) || str_contains($id, '..')) { echo json_encode(['ok' => false, 'error' => 'Invalid id']); exit; } -$ok = vv_conf_toggle_script($id, $enabled); +// Shaped like the arrays it may name and nothing else. It is compared against array names read +// out of master.conf rather than used to build a pattern, but a value that cannot be an array +// name has no legitimate target and is refused rather than quietly ignored — silently falling +// back to first-match is how this went wrong in the first place. +if ($array !== '' && !preg_match('/^[A-Z][A-Z0-9_]*_SCRIPTS$/', $array)) { + echo json_encode(['ok' => false, 'error' => 'Invalid array']); + exit; +} + +$ok = vv_conf_toggle_script($id, $enabled, $array !== '' ? $array : null); echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write master.conf']); diff --git a/Plugin/unraid/include/scheduler.php b/Plugin/unraid/include/scheduler.php index 64250a9..296f842 100644 --- a/Plugin/unraid/include/scheduler.php +++ b/Plugin/unraid/include/scheduler.php @@ -618,18 +618,31 @@ function vv_conf_flag_set(string $name, bool $value): bool { }, [$name => $val]); } -// Comment or uncomment a script's line in the first master.conf array that contains it. +// Comment or uncomment a script's line in one master.conf array. +// +// $array names which one. It is optional only so existing single-array callers keep working; +// without it this toggles the FIRST array containing the script, which is wrong whenever a script +// is listed in more than one — and several are, deliberately. docker_update.sh runs bare in daily, +// --weekly in weekly and --remainder in monthly, so turning it off in monthly silently disabled +// the daily run instead and left monthly on: the exact inverse of what was asked for, with a +// success reported. Callers that know their array must pass it. +// // Goes through vv_conf_edit() for the lock, the pre-write backup, the syntax check and the audit // line — see vv_conf_flag_set() above for what that replaced. There is no key to verify here, // so the audit subject is the script id and a clean source is the whole assertion. -function vv_conf_toggle_script(string $rel, bool $enable): bool { - return vv_conf_edit('master.conf', function (string $content) use ($rel, $enable): ?string { +function vv_conf_toggle_script(string $rel, bool $enable, ?string $array = null): bool { + return vv_conf_edit('master.conf', function (string $content) use ($rel, $enable, $array): ?string { $lines = preg_split('/(?<=\n)/', $content) ?: []; $changed = false; $inArray = false; $relEsc = preg_quote($rel, '/'); foreach ($lines as &$line) { - if (preg_match('/^\s*[A-Z_]+_SCRIPTS\s*=\s*\(/', $line)) $inArray = true; + if (preg_match('/^\s*([A-Z_]+_SCRIPTS)\s*=\s*\(/', $line, $am)) { + // Only the named array is entered when one was named. Everything else is skipped + // wholesale rather than matched and rejected per line, so a script that appears in + // three arrays cannot be reached in the two it was not clicked in. + $inArray = ($array === null || $am[1] === $array); + } if ($inArray && preg_match('/^\s*\)\s*(?:#.*)?$/', $line) && !str_contains($line, '(')) $inArray = false; if (!$inArray) continue; if (!preg_match('/^\s*(?:#\s*)?"' . $relEsc . '(?:\s[^"]*)?"/', $line)) continue; @@ -669,7 +682,14 @@ function vv_script_children(string $orchPath, array $schedule): array { $rsyncFlagName = $rm[1] . '_RSYNC_ENABLED'; } - $addChild = function(string $rel) use ($scriptsDir, $schedule, $confMap, &$children, &$seen) { + // $fromArray is the array this orchestrator actually iterates. It matters because a script + // may be listed in several with different arguments — docker_update.sh runs bare in daily, + // --weekly in weekly and --remainder in monthly — and vv_conf_script_map() keeps only the + // first it meets, so the map would label the monthly child DAILY_MAINTENANCE_SCRIPTS. Every + // consumer of conf_array then acts on the wrong line: the toggle disabled daily when monthly + // was clicked, and the drag reorder read the wrong list. + $addChild = function(string $rel, ?string $fromArray = null) + use ($scriptsDir, $schedule, $confMap, &$children, &$seen) { if (isset($seen[$rel]) || !file_exists("$scriptsDir/$rel")) return; $seen[$rel] = true; $entry = $schedule[$rel] ?? ['enabled' => false, 'cron' => '']; @@ -685,7 +705,9 @@ function vv_script_children(string $orchPath, array $schedule): array { 'log_enabled' => (bool)($entry['log_enabled'] ?? false), 'conf_managed' => $conf['managed'], 'conf_enabled' => $conf['enabled'], // null if not in any *_SCRIPTS array - 'conf_array' => $conf['array'], + // The array this orchestrator reads, when we know it. Falling back to the map is only + // for children found as static paths, which are not array members at all. + 'conf_array' => $fromArray ?? $conf['array'], 'suggested_cron' => $suggested['cron'], 'suggested_label' => $suggested['label'], ]; @@ -703,7 +725,7 @@ function vv_script_children(string $orchPath, array $schedule): array { if (!empty($refs[1])) { $confRaw = file_get_contents(CONF_DIR . '/master.conf') ?: ''; foreach (array_unique($refs[1]) as $varName) { - foreach (vv_parse_conf_array_full($confRaw, $varName) as $item) $addChild($item['path']); + foreach (vv_parse_conf_array_full($confRaw, $varName) as $item) $addChild($item['path'], $varName); } } diff --git a/Plugin/unraid/pages/scheduler.php b/Plugin/unraid/pages/scheduler.php index bf059b0..f9b6bf3 100644 --- a/Plugin/unraid/pages/scheduler.php +++ b/Plugin/unraid/pages/scheduler.php @@ -1782,7 +1782,11 @@ function vvSaveChild(el) { } if (orchOn) { - vvPost('/plugins/varaverk/api/conf_toggle.php', {id, enabled: enabled ? '1' : '0'}) + // The array this row was rendered from, so the write lands in the list that was clicked. A + // script may sit in several with different arguments, and without this the endpoint acts on + // whichever one master.conf declares first. + vvPost('/plugins/varaverk/api/conf_toggle.php', + {id, enabled: enabled ? '1' : '0', array: child.dataset.confArray || ''}) .then(d => { if (d.ok) vvFlashSaved(child); }); } else { const cron = child.querySelector('input.vv-cron')?.value.trim() ?? '';