From d0d56ed8b9854644c2fd60c8346df0cf423d60cd Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sun, 24 May 2026 22:24:14 -0400 Subject: [PATCH] =?UTF-8?q?scheduler:=20orch-god=20toggle=20model=20?= =?UTF-8?q?=E2=80=94=20children=20managed=20via=20master.conf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When orch is ON (god mode): - Children's toggle state is read from master.conf comment status - Toggling a child comments/uncomments its line in the *_SCRIPTS array - Child crons are suppressed in vv_cron_rebuild — orch is the sole trigger - Children not found in any *_SCRIPTS array are shown disabled (read-only) When orch is OFF: - All children flip to off; schedule.json updated immediately - A child with a cron value + enabled toggle gets its own independent cron entry - A child with no cron does nothing when enabled New: vv_conf_script_map() — cached per-request scan of master.conf arrays New: vv_parse_conf_array_full() — includes commented entries (disabled scripts) New: vv_conf_toggle_script() — comments/uncomments a script line in master.conf New: api/conf_toggle.php — endpoint for child toggle → master.conf write --- .../plugins/varaverk/api/conf_toggle.php | 14 ++ .../plugins/varaverk/include/scheduler.php | 123 +++++++++++++++--- .../plugins/varaverk/pages/scheduler.php | 98 ++++++++++++-- 3 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 Plugin/usr/local/emhttp/plugins/varaverk/api/conf_toggle.php diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/api/conf_toggle.php b/Plugin/usr/local/emhttp/plugins/varaverk/api/conf_toggle.php new file mode 100644 index 0000000..5044bfd --- /dev/null +++ b/Plugin/usr/local/emhttp/plugins/varaverk/api/conf_toggle.php @@ -0,0 +1,14 @@ + false, 'error' => 'Invalid id']); + exit; +} + +$ok = vv_conf_toggle_script($id, $enabled); +echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write master.conf']); diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php b/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php index 56b1680..37c2cb9 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php @@ -54,14 +54,30 @@ function vv_cron_rebuild(array $schedule): bool { $lines[] = "# Regenerated: " . date('Y-m-d H:i:s'); $lines[] = ""; + // Build child→orch map so we can suppress a child's independent cron when its orch is enabled. + $childToOrch = []; + $confRaw = file_get_contents(CONF_DIR . '/master.conf') ?: ''; + foreach (glob(SCRIPTS_DIR . '/Orchestrators/*.sh') ?: [] as $orchPath) { + $orchId = 'Orchestrators/' . basename($orchPath); + $content = file_get_contents($orchPath) ?: ''; + preg_match_all('/\$[A-Z_]+\/(?:\.\.\/)?([A-Za-z][A-Za-z0-9_.\-]*\/[A-Za-z0-9_.\-]+\.sh)/', $content, $m1); + foreach ($m1[1] as $rel) $childToOrch[$rel] = $orchId; + preg_match_all('/\$\{([A-Z_]+_SCRIPTS)\[@\]\}/', $content, $refs); + foreach (array_unique($refs[1] ?? []) as $var) { + foreach (vv_parse_conf_array_full($confRaw, $var) as $item) $childToOrch[$item['path']] = $orchId; + } + } + $scriptsDir = SCRIPTS_DIR; foreach ($schedule as $entry) { if (empty($entry['enabled']) || empty($entry['cron']) || empty($entry['id'])) continue; - // Event-triggered jobs (@array_start / @array_stop) are handled by static event scripts, not cron. + // Event-triggered jobs are handled by static event scripts, not cron. if (str_starts_with($entry['cron'], '@array_')) continue; - $script = "$scriptsDir/{$entry['id']}"; + $id = $entry['id']; + // When a child's orch is enabled it is the sole trigger — suppress independent cron. + if (isset($childToOrch[$id]) && !empty($schedule[$childToOrch[$id]]['enabled'])) continue; + $script = "$scriptsDir/$id"; $flags = !empty($entry['log_enabled']) ? ' --log' : ''; - $id = $entry['id']; $lines[] = "{$entry['cron']} bash \"$runner\" \"$id\" \"$script\"$flags"; } $lines[] = ""; @@ -265,6 +281,77 @@ function vv_parse_conf_array(string $conf, string $varName): array { return $scripts; } +// Like vv_parse_conf_array but includes commented entries. +// Returns array of ['path' => string, 'enabled' => bool]. +function vv_parse_conf_array_full(string $conf, string $varName): array { + if (!preg_match('/^\s*' . preg_quote($varName, '/') . '\s*=\s*\((.*?)^\s*\)/ms', $conf, $m)) { + return []; + } + $results = []; + foreach (explode("\n", $m[1]) as $line) { + if (!preg_match('/^\s*(#\s*)?"([^"]+)"/', $line, $e)) continue; + $commented = trim($e[1]) !== ''; + $parts = preg_split('/\s+/', trim($e[2])); + $path = $parts[0] ?? ''; + if (substr($path, -3) !== '.sh') continue; + $results[] = ['path' => $path, 'enabled' => !$commented]; + } + return $results; +} + +// Build a map of script rel-path → conf status by scanning all *_SCRIPTS arrays in master.conf. +// Cached per-request so multiple callers only read the file once. +function vv_conf_script_map(): array { + static $cache = null; + if ($cache !== null) return $cache; + $confPath = CONF_DIR . '/master.conf'; + if (!file_exists($confPath)) return $cache = []; + $lines = file($confPath, FILE_IGNORE_NEW_LINES) ?: []; + $map = []; + $inArray = false; + $arrayVar = ''; + foreach ($lines as $line) { + if (preg_match('/^\s*([A-Z_]+_SCRIPTS)\s*=\s*\(/', $line, $am)) { $inArray = true; $arrayVar = $am[1]; } + if ($inArray && preg_match('/^\s*\)\s*(?:#.*)?$/', $line) && !str_contains($line, '(')) $inArray = false; + if (!$inArray) continue; + if (!preg_match('/^\s*(#\s*)?"([^"]+)"/', $line, $e)) continue; + $commented = trim($e[1]) !== ''; + $parts = preg_split('/\s+/', trim($e[2])); + $path = $parts[0] ?? ''; + if (substr($path, -3) !== '.sh') continue; + if (!isset($map[$path])) $map[$path] = ['array' => $arrayVar, 'enabled' => !$commented, 'managed' => true]; + } + return $cache = $map; +} + +// Comment or uncomment a script's line in the first master.conf array that contains it. +function vv_conf_toggle_script(string $rel, bool $enable): bool { + $confPath = CONF_DIR . '/master.conf'; + $lines = file($confPath, FILE_KEEP_BLANK_LINES); + if (!$lines) return false; + $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 ($inArray && preg_match('/^\s*\)\s*(?:#.*)?$/', $line) && !str_contains($line, '(')) $inArray = false; + if (!$inArray) continue; + if (!preg_match('/^\s*(?:#\s*)?"' . $relEsc . '(?:\s[^"]*)?"/', $line)) continue; + $isCommented = (bool)preg_match('/^\s*#/', $line); + if ($enable && $isCommented) { + $line = preg_replace('/^(\s*)#\s*("' . $relEsc . ')/', '$1$2', $line); + $changed = true; + } elseif (!$enable && !$isCommented) { + $line = preg_replace('/^(\s*)("' . $relEsc . ')/', '$1# $2', $line); + $changed = true; + } + break; + } + unset($line); + if (!$changed) return true; + return file_put_contents($confPath, implode('', $lines)) !== false; +} + // Parse an orchestrator script to find which child scripts it calls. // Two strategies, merged and deduped: // 1. Static paths: $SCRIPT_DIR/../Category/script.sh or $SCRIPTS_ROOT/Category/script.sh @@ -276,19 +363,23 @@ function vv_script_children(string $orchPath, array $schedule): array { $content = file_get_contents($orchPath) ?: ''; $children = []; $seen = []; + $confMap = vv_conf_script_map(); - $addChild = function(string $rel) use ($scriptsDir, $schedule, &$children, &$seen) { + $addChild = function(string $rel) use ($scriptsDir, $schedule, $confMap, &$children, &$seen) { if (isset($seen[$rel]) || !file_exists("$scriptsDir/$rel")) return; - $seen[$rel] = true; - $entry = $schedule[$rel] ?? ['enabled' => false, 'cron' => '']; - $children[] = [ - 'id' => $rel, - 'label' => basename($rel, '.sh'), - 'desc' => vv_script_description("$scriptsDir/$rel"), - 'type' => 'script', - 'enabled' => (bool)($entry['enabled'] ?? false), - 'cron' => $entry['cron'] ?? '', - 'log_enabled' => (bool)($entry['log_enabled'] ?? false), + $seen[$rel] = true; + $entry = $schedule[$rel] ?? ['enabled' => false, 'cron' => '']; + $conf = $confMap[$rel] ?? ['array' => null, 'enabled' => null, 'managed' => false]; + $children[] = [ + 'id' => $rel, + 'label' => basename($rel, '.sh'), + 'desc' => vv_script_description("$scriptsDir/$rel"), + 'type' => 'script', + 'enabled' => (bool)($entry['enabled'] ?? false), + 'cron' => $entry['cron'] ?? '', + 'log_enabled' => (bool)($entry['log_enabled'] ?? false), + 'conf_managed' => $conf['managed'], + 'conf_enabled' => $conf['enabled'], // null if not in any *_SCRIPTS array ]; }; @@ -299,12 +390,12 @@ function vv_script_children(string $orchPath, array $schedule): array { ); foreach ($m[1] as $rel) $addChild($rel); - // Strategy 2: master.conf arrays — find every ${VARNAME_SCRIPTS[@]} the orch iterates + // Strategy 2: master.conf arrays — includes commented (disabled) entries so they appear in the UI preg_match_all('/\$\{([A-Z_]+_SCRIPTS)\[@\]\}/', $content, $refs); if (!empty($refs[1])) { $confRaw = file_get_contents(CONF_DIR . '/master.conf') ?: ''; foreach (array_unique($refs[1]) as $varName) { - foreach (vv_parse_conf_array($confRaw, $varName) as $rel) $addChild($rel); + foreach (vv_parse_conf_array_full($confRaw, $varName) as $item) $addChild($item['path']); } } diff --git a/Plugin/usr/local/emhttp/plugins/varaverk/pages/scheduler.php b/Plugin/usr/local/emhttp/plugins/varaverk/pages/scheduler.php index d970ab8..6318175 100644 --- a/Plugin/usr/local/emhttp/plugins/varaverk/pages/scheduler.php +++ b/Plugin/usr/local/emhttp/plugins/varaverk/pages/scheduler.php @@ -28,11 +28,11 @@ foreach ($tree as $orch) {
-
+
@@ -71,17 +71,19 @@ foreach ($tree as $orch) {