Files
Varaverk/Plugin/unraid/include/fallback.php
T
Gmer4Lfe ce0b3fb74b PHP app layer: consolidate common functions, fix critical bugs, standardize patterns
Consolidations (config.php gains 5 shared utilities):
- vv_format_uptime() replaces 4 inline uptime-formatting blocks
- vv_parse_conf_scalar() replaces vv_arr_scalar/vv_wd_scalar/vv_fb_scalar/vv_media_conf_scalar
- vv_known_hosts() replaces vv_arr_known_hosts/vv_fb_known_hosts + inline parser in watchdog
- vv_parse_kv_db() replaces inline key=value parsing in snapshot and monitor
- vv_local_ip() replaces duplicate in docker_folders.php and inline in docker.php
All module-level function names kept as thin aliases so call sites unchanged.

Critical bug fixes:
- api/system.php: added require_once config.php and POST-only guard (no auth on shutdown)
- api/movescript.php + reorderarray.php: use vv_write_conf_raw (atomic) + vv_push_master_conf
- api/snapshot.php: share /tmp/vv_cpu_stat.json with vv_cpu_per_core() instead of own state file

Correctness:
- vv_cpu_per_core() and vv_network_stats(): atomic tmp+rename for state files (concurrent poll safety)
- ext_ip curl cache moved from /tmp/vv_ext_ip.cache to vv_cache_read/write (canonical cache dir)
- monitor_remote.php + board.php + snapshot.php: all use vv_cache_read/write instead of ad-hoc /tmp files

HTTP method guards added to write-only APIs that were missing them:
- api/scheduler.php, conf_toggle.php, flag_toggle.php
2026-06-04 16:44:16 -04:00

172 lines
6.9 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 {
return vv_parse_bash_array($raw, $varname);
}
function vv_fb_scalar(string $raw, string $varname): string {
return vv_parse_conf_scalar($raw, $varname);
}
// ── 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 {
return vv_known_hosts();
}
// ── 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,
];
}