scheduler: orch-god toggle model — children managed via master.conf
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
This commit is contained in:
@@ -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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user