- 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
185 lines
7.5 KiB
PHP
185 lines
7.5 KiB
PHP
<?php
|
|
// Fallback tab data helpers
|
|
|
|
require_once __DIR__ . '/config.php';
|
|
require_once __DIR__ . '/partnership.php'; // vv_pt_ssh(), vv_pt_ts_peers()
|
|
|
|
// ── Conf parsers ──────────────────────────────────────────────────────────────
|
|
|
|
function vv_fb_bash_array(string $raw, string $varname): array {
|
|
if (!preg_match('/^\s*' . preg_quote($varname, '/') . '\s*=\s*\(\s*(.*?)\s*\)/ms', $raw, $m))
|
|
return [];
|
|
preg_match_all('/"([^"]*)"/', $m[1], $items);
|
|
return array_values(array_filter($items[1]));
|
|
}
|
|
|
|
function vv_fb_scalar(string $raw, string $varname): string {
|
|
return preg_match('/^\s*' . preg_quote($varname, '/') . '\s*=\s*"?([^"\n]*)"?/m', $raw, $m)
|
|
? trim($m[1]) : '';
|
|
}
|
|
|
|
// ── State file ────────────────────────────────────────────────────────────────
|
|
|
|
function vv_fb_parse_state(string $text): array {
|
|
$out = [
|
|
'state' => 'UNKNOWN',
|
|
'fallback_start' => 0,
|
|
'handback_strikes' => 0,
|
|
'tier2_started' => false,
|
|
'tier3_started' => false,
|
|
'tier4_started' => false,
|
|
];
|
|
foreach (explode("\n", $text) as $line) {
|
|
$line = trim($line);
|
|
if (!$line || !str_contains($line, '=')) continue;
|
|
[$k, $v] = array_pad(explode('=', $line, 2), 2, '');
|
|
$k = trim($k); $v = trim($v, '"\'');
|
|
switch ($k) {
|
|
case 'state': $out['state'] = $v; break;
|
|
case 'fallback_start': $out['fallback_start'] = (int)$v; break;
|
|
case 'handback_strikes': $out['handback_strikes'] = (int)$v; break;
|
|
case 'tier2_started': $out['tier2_started'] = $v === 'true'; break;
|
|
case 'tier3_started': $out['tier3_started'] = $v === 'true'; break;
|
|
case 'tier4_started': $out['tier4_started'] = $v === 'true'; break;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
function vv_fb_local_state(): array {
|
|
$path = '/boot/config/fallback_state.db';
|
|
return vv_fb_parse_state(file_exists($path) ? file_get_contents($path) : '');
|
|
}
|
|
|
|
function vv_fb_remote_state(string $ip, string $sshKey): array {
|
|
$out = vv_pt_ssh($ip, $sshKey, 'cat /boot/config/fallback_state.db 2>/dev/null');
|
|
return vv_fb_parse_state($out);
|
|
}
|
|
|
|
// ── Running containers ────────────────────────────────────────────────────────
|
|
|
|
function vv_fb_local_running(): array {
|
|
$out = shell_exec("docker ps --format '{{.Names}}' 2>/dev/null") ?: '';
|
|
return array_values(array_filter(explode("\n", trim($out))));
|
|
}
|
|
|
|
function vv_fb_remote_running(string $ip, string $sshKey): array {
|
|
$out = vv_pt_ssh($ip, $sshKey, "docker ps --format '{{.Names}}' 2>/dev/null");
|
|
return array_values(array_filter(explode("\n", trim($out))));
|
|
}
|
|
|
|
// ── Covers — what a node runs for the other when it's down ───────────────────
|
|
|
|
function vv_fb_covers(string $covering, string $remote, string $coveringRaw, string $remoteRaw): array {
|
|
$cu = strtoupper($covering); // HOST1
|
|
$ru = strtoupper($remote); // HOST2
|
|
$tiers = [];
|
|
for ($t = 1; $t <= 4; $t++) {
|
|
$tiers["tier$t"] = vv_fb_bash_array($coveringRaw, "FALLBACK_{$cu}_COVERS_{$ru}_TIER{$t}");
|
|
}
|
|
// Delays: how long the remote (covered) host must be down before each tier fires.
|
|
// Stored in the *remote* host's conf as REMOTE_TIER*_DELAY.
|
|
$tiers['delays'] = [
|
|
'tier2' => (int)(vv_fb_scalar($remoteRaw, "{$ru}_TIER2_DELAY") ?: 240),
|
|
'tier3' => (int)(vv_fb_scalar($remoteRaw, "{$ru}_TIER3_DELAY") ?: 720),
|
|
'tier4' => (int)(vv_fb_scalar($remoteRaw, "{$ru}_TIER4_DELAY") ?: 1440),
|
|
];
|
|
return $tiers;
|
|
}
|
|
|
|
// ── Known hosts ───────────────────────────────────────────────────────────────
|
|
|
|
function vv_fb_known_hosts(): array {
|
|
$master = vv_read_conf_raw('master.conf');
|
|
preg_match_all('/^\s*(HOST(\d+))(?:_NAME)?\s*=\s*["\']?(\S+?)["\']?\s*$/m', $master, $m);
|
|
$hosts = [];
|
|
foreach ($m[1] as $i => $key) {
|
|
$num = $m[2][$i];
|
|
$name = trim($m[3][$i]);
|
|
$hosts['host' . $num] = $name;
|
|
}
|
|
ksort($hosts);
|
|
return $hosts ?: ['host1' => 'HOST1'];
|
|
}
|
|
|
|
// ── Main data builder ─────────────────────────────────────────────────────────
|
|
|
|
function vv_fb_all(): array {
|
|
$currentHost = vv_detect_host();
|
|
$hosts = vv_fb_known_hosts();
|
|
$tsPeers = vv_pt_ts_peers();
|
|
$handbackReq = (int)(vv_fb_scalar(vv_read_conf_raw('master.conf'), 'FALLBACK_HANDBACK_STRIKES') ?: 3);
|
|
$fbEnabled = vv_fb_scalar(vv_read_conf_raw('master.conf'), 'FALLBACK_ENABLED') === 'true';
|
|
|
|
// Read all host conf raws upfront
|
|
$raws = [];
|
|
foreach (array_keys($hosts) as $slot) {
|
|
$raws[$slot] = vv_read_conf_raw($slot . '.conf');
|
|
}
|
|
|
|
// SSH key — from local host conf
|
|
$myId = strtoupper($currentHost);
|
|
$myRaw = $raws[$currentHost] ?? '';
|
|
$mySshKey = vv_fb_scalar($myRaw, $myId . '_SSH_KEY');
|
|
|
|
$nodes = [];
|
|
foreach ($hosts as $slot => $hostname) {
|
|
$isMe = ($slot === $currentHost || $currentHost === 'unknown');
|
|
$tsLabel = strtolower($hostname);
|
|
$ts = $tsPeers[$tsLabel] ?? ['online' => null, 'active' => false, 'ip' => null];
|
|
$ip = $ts['ip'] ?? null;
|
|
|
|
// State
|
|
if ($isMe) {
|
|
$state = vv_fb_local_state();
|
|
} elseif ($ip && $mySshKey) {
|
|
$state = vv_fb_remote_state($ip, $mySshKey);
|
|
} else {
|
|
$state = vv_fb_parse_state('');
|
|
$state['state'] = $ts['online'] === false ? 'OFFLINE' : 'UNREACHABLE';
|
|
}
|
|
|
|
// Running containers
|
|
if ($isMe) {
|
|
$running = vv_fb_local_running();
|
|
} elseif ($ip && $mySshKey && $ts['online']) {
|
|
$running = vv_fb_remote_running($ip, $mySshKey);
|
|
} else {
|
|
$running = [];
|
|
}
|
|
|
|
// Covers: for a 2-node setup, each covers the other
|
|
// For N nodes this would need a different approach — for now, assume 2-node
|
|
$covers = null;
|
|
foreach ($hosts as $otherSlot => $otherHostname) {
|
|
if ($otherSlot === $slot) continue;
|
|
$coveringRaw = $raws[$slot] ?? '';
|
|
$remoteRaw = $raws[$otherSlot] ?? '';
|
|
$covers = [
|
|
'slot' => $otherSlot,
|
|
'id' => strtoupper($otherSlot),
|
|
'hostname' => $otherHostname,
|
|
] + vv_fb_covers($slot, $otherSlot, $coveringRaw, $remoteRaw);
|
|
break; // 2-node only
|
|
}
|
|
|
|
$nodes[] = [
|
|
'slot' => $slot,
|
|
'id' => strtoupper($slot),
|
|
'hostname' => $hostname,
|
|
'is_me' => $isMe,
|
|
'ts_online' => $ts['online'],
|
|
'state' => $state,
|
|
'running' => $running,
|
|
'covers' => $covers,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ts' => time(),
|
|
'fb_enabled' => $fbEnabled,
|
|
'handback_req' => $handbackReq,
|
|
'nodes' => $nodes,
|
|
];
|
|
}
|