Varaverk: FallBack + Watchdog tabs; plugin path restructure to Plugin/unraid/
- FallBack tab: per-node tier inventory + active fallback card with duration, tier, handback strikes, running container status - Watchdog tab: live system health (RAM bar + thresholds, load, uptime, daemon), docker watchdog strikes + skip list + restart history, stability strikes + reboot log, resource pressure alert card, config inventory (mem limits, required, pause/stop lists) - Swapped partnership/arrs tab order; FallBack between partnership and watchdog - Plugin source tree moved from Plugin/usr/local/emhttp/plugins/varaverk/ to Plugin/unraid/ - Deployment/ conf templates added
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
// confform.php — script→conf-section mapping, field parsing, and write-back.
|
||||
|
||||
// Map: script relative id → subsection names (must match # ━━━ Name ━━━ or # ── Name ── headers).
|
||||
const VV_SCRIPT_CONF_SECTIONS = [
|
||||
// Orchestrators
|
||||
'Orchestrators/array_started.sh' => ['Array Start'],
|
||||
'Orchestrators/array_stopping.sh' => ['Array Stop'],
|
||||
'Orchestrators/watchdog_orchestrator.sh' => ['Watchdog Orchestrator', 'System Watchdog'],
|
||||
'Orchestrators/critical_sync_maintenance.sh' => ['Critical Sync Maintenance', 'Critical Sync Shares'],
|
||||
'Orchestrators/intermediate_sync_maintenance.sh' => ['Intermediate Sync Maintenance', 'Intermediate Sync Shares'],
|
||||
'Orchestrators/daily_sync_maintenance.sh' => ['Daily Sync Maintenance', 'Daily Sync Shares'],
|
||||
'Orchestrators/weekly_sync_maintenance.sh' => ['Weekly Sync Maintenance', 'Weekly Sync Shares'],
|
||||
'Orchestrators/monthly_maintenance.sh' => ['Monthly Maintenance'],
|
||||
'Orchestrators/transcode_management.sh' => ['Transcode Manager', 'Transcode Server Array', 'Transcodes'],
|
||||
// Docker Essentials
|
||||
'Docker_Essentials/docker_daily_restart.sh' => ['Docker Daily Restart'],
|
||||
'Docker_Essentials/docker_weekly_restart.sh' => ['Docker Weekly Restart'],
|
||||
'Docker_Essentials/docker_network_connect.sh' => ['Docker Network Connect'],
|
||||
'Docker_Essentials/downloaders_reset.sh' => ['Downloaders Reset', 'Downloaders'],
|
||||
// Watchdogs
|
||||
'Watchdogs/docker_watchdog.sh' => ['Docker Watchdog'],
|
||||
'Watchdogs/resource_watchdog.sh' => ['Pressure Levels'],
|
||||
'Watchdogs/System/network_watchdog.sh' => ['Network Watchdog'],
|
||||
'Watchdogs/System/webgui_watchdog.sh' => ['WebGUI Watchdog'],
|
||||
// Media
|
||||
'Media/media_cleaner.sh' => ['Media Cleaner'],
|
||||
'Media/media_shares_permissions.sh' => ['Media Permissions'],
|
||||
'Media/arrs_failed_stalled_recovery.sh' => ['Arr Failed/Stalled Recovery'],
|
||||
'Media/radarr_cleanup.sh' => ['Arr Cleanup'],
|
||||
'Media/lidarr_cleanup.sh' => ['Arr Cleanup'],
|
||||
'Media/sonarr_cleanup.sh' => ['Arr Cleanup'],
|
||||
// Monitors
|
||||
'Monitors/cert_monitor.sh' => ['Certificate Monitor'],
|
||||
'Monitors/backup_verify.sh' => ['Backup Verify'],
|
||||
'Monitors/smart_health.sh' => ['SMART Health'],
|
||||
'Monitors/bandwidth_monitor.sh' => ['Bandwidth Monitor'],
|
||||
'Monitors/emby_session_report.sh' => ['Emby Session Report'],
|
||||
'Monitors/zfs_memory_snapshot.sh' => ['ZFS Report'],
|
||||
];
|
||||
|
||||
function vv_conf_has_sections(string $id): bool {
|
||||
return !empty(VV_SCRIPT_CONF_SECTIONS[$id] ?? []);
|
||||
}
|
||||
|
||||
// Parse fields from a named subsection in raw conf content.
|
||||
// Headers accepted: # ━━━ Name ━━━ OR # ── Name ── (any mix of ━ ─ chars).
|
||||
// Returns array of field defs, or null if subsection not found.
|
||||
function vv_conf_parse_subsection(string $raw, string $subName, string $filename): ?array {
|
||||
$lines = explode("\n", $raw);
|
||||
$n = count($lines);
|
||||
$needle = mb_strtolower(trim(preg_replace('/\s+/', ' ', $subName)));
|
||||
$start = -1;
|
||||
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
if (!preg_match('/^#\s*[━─]{2,}\s+([A-Za-z].+?)\s+[━─]{2,}/', $lines[$i], $m)) continue;
|
||||
$t = mb_strtolower(trim(preg_replace('/\s+/', ' ', $m[1])));
|
||||
if ($t === $needle) { $start = $i + 1; break; }
|
||||
}
|
||||
if ($start === -1) return null;
|
||||
|
||||
// End at next section/subsection line (3+ consecutive divider chars after #)
|
||||
$end = $n;
|
||||
for ($i = $start; $i < $n; $i++) {
|
||||
if (preg_match('/^#\s*[━─═=]{3,}/', $lines[$i])) { $end = $i; break; }
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
$pendingDesc = [];
|
||||
|
||||
for ($i = $start; $i < $end; $i++) {
|
||||
$line = rtrim($lines[$i]);
|
||||
|
||||
if ($line === '' || $line === '#') { $pendingDesc = []; continue; }
|
||||
|
||||
// Pure comment line
|
||||
if (preg_match('/^#\s*(.*)$/', $line, $cm)) {
|
||||
$inner = trim($cm[1]);
|
||||
if ($inner !== '' && !preg_match('/^[━─═=\-\s]+$/', $inner)) {
|
||||
$pendingDesc[] = $inner;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$desc = implode(' ', $pendingDesc);
|
||||
$pendingDesc = [];
|
||||
|
||||
// declare -A KEY=(
|
||||
if (preg_match('/^(\s*)declare\s+-A\s+([A-Z_][A-Z0-9_]*)\s*=\s*\(/', $line, $m)) {
|
||||
$indent = $m[1]; $key = $m[2];
|
||||
$blockLines = [];
|
||||
$j = $i + 1;
|
||||
while ($j < $end && !preg_match('/^\s*\)\s*$/', $lines[$j])) {
|
||||
$blockLines[] = rtrim($lines[$j]);
|
||||
$j++;
|
||||
}
|
||||
$fields[] = ['key' => $key, 'value' => implode("\n", $blockLines),
|
||||
'type' => 'assoc_array', 'desc' => $desc, 'file' => $filename, 'indent' => $indent];
|
||||
$i = $j;
|
||||
continue;
|
||||
}
|
||||
|
||||
// KEY=( (array)
|
||||
if (preg_match('/^(\s*)([A-Z_][A-Z0-9_]*)\s*=\s*\(/', $line, $m)) {
|
||||
$indent = $m[1]; $key = $m[2];
|
||||
// Single-line: KEY=( ... )
|
||||
if (preg_match('/^[^(]*\(([^)]*)\)/', $line, $sm)) {
|
||||
$fields[] = ['key' => $key, 'value' => $sm[1],
|
||||
'type' => 'array_single', 'desc' => $desc, 'file' => $filename, 'indent' => $indent];
|
||||
continue;
|
||||
}
|
||||
// Multi-line
|
||||
$blockLines = [];
|
||||
$j = $i + 1;
|
||||
while ($j < $end && !preg_match('/^\s*\)\s*$/', $lines[$j])) {
|
||||
$blockLines[] = rtrim($lines[$j]);
|
||||
$j++;
|
||||
}
|
||||
$fields[] = ['key' => $key, 'value' => implode("\n", $blockLines),
|
||||
'type' => 'array', 'desc' => $desc, 'file' => $filename, 'indent' => $indent];
|
||||
$i = $j;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scalar: KEY="value" or KEY=value
|
||||
if (preg_match('/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*"([^"]*)"(?:\s+#\s*(.+))?$/', $line, $m)) {
|
||||
$fields[] = ['key' => $m[1], 'value' => $m[2],
|
||||
'type' => 'scalar', 'desc' => ($m[3] ?? '') ?: $desc, 'file' => $filename];
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*([^(\n#]*?)(?:\s+#\s*(.+))?$/', $line, $m)) {
|
||||
$val = trim($m[2]);
|
||||
if ($val === '') continue;
|
||||
$fields[] = ['key' => $m[1], 'value' => $val,
|
||||
'type' => 'scalar', 'desc' => ($m[3] ?? '') ?: $desc, 'file' => $filename];
|
||||
}
|
||||
}
|
||||
|
||||
return $fields ?: null;
|
||||
}
|
||||
|
||||
// Return all conf groups (subsection + fields) for a script on the current host.
|
||||
function vv_conf_fields_for_script(string $id): array {
|
||||
$sectionNames = VV_SCRIPT_CONF_SECTIONS[$id] ?? [];
|
||||
if (!$sectionNames) return [];
|
||||
|
||||
$groups = [];
|
||||
foreach ($sectionNames as $name) {
|
||||
foreach (vv_get_conf_files() as $filename) {
|
||||
$fields = vv_conf_parse_subsection(vv_read_conf_raw($filename), $name, $filename);
|
||||
if ($fields !== null) {
|
||||
$groups[] = ['subsection' => $name, 'file' => $filename, 'fields' => $fields];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
// Write a batch of field changes back to their respective conf files.
|
||||
// Each change: {file, key, value, type}
|
||||
function vv_conf_write_changes(array $changes): array {
|
||||
$byFile = [];
|
||||
foreach ($changes as $c) {
|
||||
if (!empty($c['file']) && !empty($c['key'])) $byFile[$c['file']][] = $c;
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($byFile as $file => $fileChanges) {
|
||||
$raw = vv_read_conf_raw($file);
|
||||
if ($raw === '') { $results[$file] = false; continue; }
|
||||
|
||||
foreach ($fileChanges as $c) {
|
||||
$qKey = preg_quote($c['key'], '/');
|
||||
$value = $c['value'];
|
||||
$type = $c['type'] ?? 'scalar';
|
||||
|
||||
if ($type === 'scalar') {
|
||||
$raw = preg_replace_callback(
|
||||
'/^(\s*' . $qKey . '\s*=\s*)("(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\'|[^#\n]*?)(\s*(?:#[^\n]*)?)$/m',
|
||||
fn($m) => $m[1] . '"' . str_replace(['"', '\\'], ['\\"', '\\\\'], $value) . '"' . $m[3],
|
||||
$raw
|
||||
) ?? $raw;
|
||||
|
||||
} elseif ($type === 'array_single') {
|
||||
$raw = preg_replace_callback(
|
||||
'/^(\s*' . $qKey . '\s*=\s*\()([^)]*)(\)(?:\s*(?:#[^\n]*)?)?)$/m',
|
||||
fn($m) => $m[1] . $value . $m[3],
|
||||
$raw
|
||||
) ?? $raw;
|
||||
|
||||
} elseif ($type === 'array') {
|
||||
$raw = preg_replace_callback(
|
||||
'/^(\s*)(' . $qKey . '\s*=\s*\()[^)]*\)/ms',
|
||||
fn($m) => $m[1] . $m[2] . "\n" . $value . "\n" . $m[1] . ")",
|
||||
$raw
|
||||
) ?? $raw;
|
||||
|
||||
} elseif ($type === 'assoc_array') {
|
||||
$raw = preg_replace_callback(
|
||||
'/^(\s*)(declare\s+-A\s+' . $qKey . '\s*=\s*\()[^)]*\)/ms',
|
||||
fn($m) => $m[1] . $m[2] . "\n" . $value . "\n" . $m[1] . ")",
|
||||
$raw
|
||||
) ?? $raw;
|
||||
}
|
||||
}
|
||||
$results[$file] = vv_write_conf_raw($file, $raw);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
Reference in New Issue
Block a user