Files
Varaverk/Plugin/usr/local/emhttp/plugins/varaverk/include/scheduler.php
T
Gmer4Lfe 0197eab7a5 Read scheduler children from master.conf arrays
Orchestrators using dynamic dispatch (array_started, watchdog_orchestrator,
daily/weekly/etc maintenance) iterate \${VARNAME_SCRIPTS[@]} arrays defined
in master.conf. The previous regex only caught static hardcoded paths.

Two-strategy approach, merged and deduped:
1. Static: \$VAR/../Category/script.sh and \$VAR/Category/script.sh patterns
2. Dynamic: detect \${VARNAME[@]} references in the orch, parse that array
   from master.conf — handles inline args and skips commented entries

Changes to master.conf arrays are reflected immediately on next page load.
2026-05-23 17:08:52 -04:00

141 lines
5.3 KiB
PHP

<?php
// Scheduler — manages schedule.json and /etc/cron.d/varaverk.
// schedule.json is per-host, never synced.
require_once __DIR__ . '/config.php';
define('SCHEDULE_FILE', '/boot/config/plugins/varaverk/schedule.json');
define('CRON_FILE', '/etc/cron.d/varaverk');
define('CRON_USER', 'root');
function vv_schedule_load(): array {
if (!file_exists(SCHEDULE_FILE)) return [];
$data = json_decode(file_get_contents(SCHEDULE_FILE), true);
return is_array($data) ? $data : [];
}
function vv_schedule_save(array $schedule): bool {
$dir = dirname(SCHEDULE_FILE);
if (!is_dir($dir)) mkdir($dir, 0755, true);
return file_put_contents(SCHEDULE_FILE, json_encode($schedule, JSON_PRETTY_PRINT)) !== false;
}
function vv_schedule_update(string $id, bool $enabled, string $cron): bool {
$schedule = vv_schedule_load();
$schedule[$id] = [
'id' => $id,
'enabled' => $enabled,
'cron' => $cron,
'updated' => date('c'),
];
if (!vv_schedule_save($schedule)) return false;
return vv_cron_rebuild($schedule);
}
define('LOG_DIR', '/var/log/varaverk');
function vv_cron_rebuild(array $schedule): bool {
if (!is_dir(LOG_DIR)) mkdir(LOG_DIR, 0755, true);
$lines = ["# Varaverk — managed by plugin, do not edit manually"];
$lines[] = "# Regenerated: " . date('Y-m-d H:i:s');
$lines[] = "";
$scriptsDir = SCRIPTS_DIR;
foreach ($schedule as $entry) {
if (empty($entry['enabled']) || empty($entry['cron']) || empty($entry['id'])) continue;
$script = "$scriptsDir/{$entry['id']}";
$cron = $entry['cron'];
$lines[] = "$cron " . CRON_USER . " bash \"$script\" >> " . LOG_DIR . "/jobs.log 2>&1";
}
$lines[] = "";
return file_put_contents(CRON_FILE, implode("\n", $lines)) !== false;
}
// Walk the scripts repo and return the job tree:
// orchestrators as top-level, individual scripts as children
function vv_job_tree(): array {
$scriptsDir = SCRIPTS_DIR;
$schedule = vv_schedule_load();
// Orchestrators are in Orchestrators/ and their children are all scripts they call
// For now: walk top-level folders, treat *_management.sh or *_maintenance.sh as orchs
$orchPattern = "$scriptsDir/Orchestrators/*.sh";
$orchs = [];
foreach (glob($orchPattern) ?: [] as $path) {
$id = 'Orchestrators/' . basename($path);
$entry = $schedule[$id] ?? ['enabled' => false, 'cron' => ''];
$orchs[] = [
'id' => $id,
'label' => basename($path, '.sh'),
'type' => 'orchestrator',
'enabled' => (bool)($entry['enabled'] ?? false),
'cron' => $entry['cron'] ?? '',
'children' => vv_script_children($path, $schedule),
];
}
return $orchs;
}
// Parse a bash array from master.conf content and return its script paths.
// Handles entries with inline args ("script.sh --flag") and skips commented lines (#"...").
function vv_parse_conf_array(string $conf, string $varName): array {
if (!preg_match('/^\s*' . preg_quote($varName, '/') . '\s*=\s*\((.*?)\)/ms', $conf, $m)) {
return [];
}
$scripts = [];
preg_match_all('/^\s*(?!#)"([^"]+)"/m', $m[1], $entries);
foreach ($entries[1] as $entry) {
$parts = preg_split('/\s+/', trim($entry));
$path = $parts[0] ?? '';
if (substr($path, -3) === '.sh') $scripts[] = $path;
}
return $scripts;
}
// 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
// 2. master.conf arrays: detects ${VARNAME[@]} iteration and reads the array from master.conf
// Root-level files (load_config.sh etc.) excluded — must be in a subdirectory.
// Each candidate validated against the filesystem.
function vv_script_children(string $orchPath, array $schedule): array {
$scriptsDir = SCRIPTS_DIR;
$content = file_get_contents($orchPath) ?: '';
$children = [];
$seen = [];
$addChild = function(string $rel) use ($scriptsDir, $schedule, &$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'),
'type' => 'script',
'enabled' => (bool)($entry['enabled'] ?? false),
'cron' => $entry['cron'] ?? '',
];
};
// Strategy 1: static variable paths ($VAR/../Category/script.sh or $VAR/Category/script.sh)
preg_match_all(
'/\$[A-Z_]+\/(?:\.\.\/)?([A-Za-z][A-Za-z0-9_.\-]*\/[A-Za-z0-9_.\-]+\.sh)/',
$content, $m
);
foreach ($m[1] as $rel) $addChild($rel);
// Strategy 2: master.conf arrays — find every ${VARNAME_SCRIPTS[@]} the orch iterates
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);
}
}
return $children;
}