Files
Gmer4Lfe 1231cd69a8 A host with the VM service off is not a host with a broken API key
The API answered and simply had no vms node, but that was recorded as an API fallback, and the monitor reads any fallback with a key present as 'Unraid API unreachable — check API key in host conf.'
2026-08-17 13:47:19 -04:00

114 lines
5.9 KiB
PHP

<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// VM inventory for the monitor page — name, state, and assigned resources for each libvirt
// domain on this host.
//
// DESIGN PRINCIPLES
// Prefer the Unraid API, fall back to virsh.
// The API path returns the full set in one call; virsh is queried per domain only when
// the API is unavailable.
//
// Reports VM state; never changes it. Nothing here starts, stops, or reconfigures a domain.
//
// OPERATIONAL SAFEGUARDS
// Domain names are shell-escaped.
// Every virsh invocation passes the name through escapeshellarg(), so a domain named
// with shell metacharacters cannot become a command.
//
// A host with no VMs, or no libvirt at all, returns an empty list.
// The VM card simply does not render. This is the expected state on a host that does
// not run VMs, not a failure.
//
// Unknown state is reported as 'unknown' rather than assumed stopped.
//
// EXPORTS
// vv_get_vms() every libvirt domain with state and assigned resources
//
// CONFIGURATION
// None. Reads libvirt through the API or virsh; no Varaverk conf variables involved.
// ═══════════════════════════════════════════════════════════════════════════════════════════════
require_once __DIR__ . '/unraid_api.php';
function vv_get_vms(): array {
// ── API path ──────────────────────────────────────────────────────────────
$api = vv_api_data();
if ($api && isset($api['vms']['domains'])) {
$vms = [];
foreach ($api['vms']['domains'] as $d) {
$name = $d['name'] ?? '';
// VmState enum values are RUNNING, PAUSED, SHUT_OFF, etc. — normalise to lowercase
$state = strtolower(str_replace('_', ' ', $d['state'] ?? 'unknown'));
$nl = strtolower($name);
$os = 'linux';
if (str_contains($nl, 'win')) $os = 'windows';
elseif (str_contains($nl, 'mac') || str_contains($nl, 'osx')) $os = 'macos';
elseif (str_contains($nl, 'bsd') || str_contains($nl, 'freebsd')) $os = 'bsd';
// vcpus/mem_mb not available via API — will show null (virsh fallback provides them)
$vms[] = ['name' => $name, 'state' => $state, 'os' => $os, 'vcpus' => null, 'mem_mb' => null];
}
return ['available' => true, 'vms' => $vms];
}
// ── VM service off is not an API failure ──────────────────────────────────
// The API answered — there is simply no vms node in the reply, which is what a host with
// the VM service disabled returns. Recording a fallback here put 'vms' on the API-status
// list, and the monitor banner reads any non-empty list with a key present as
// "⚠ Unraid API unreachable — using local reads. Check API key in host conf." So a healthy
// host with VMs switched off accused its own API key of being broken, and the only clue to
// the contrary was the quiet "(vms)" after the message.
//
// Falling through to virsh would be wrong too: libvirt is not running, so it can only fail,
// and the header of this file already says the absence of VMs is the expected state.
if ($api !== null) return ['available' => false, 'vms' => []];
// ── Local fallback ────────────────────────────────────────────────────────
// Reached only when the API itself gave us nothing, which IS worth reporting.
vv_api_record_fallback('vms');
if (!file_exists('/usr/bin/virsh')) return ['available' => false, 'vms' => []];
exec('virsh list --all --name 2>/dev/null', $names, $rc);
if ($rc !== 0) return ['available' => false, 'vms' => []];
$vms = [];
foreach ($names as $raw) {
$name = trim($raw);
if ($name === '') continue;
$state = trim(shell_exec('virsh domstate ' . escapeshellarg($name) . ' 2>/dev/null') ?? 'unknown');
$vcpus = null;
$memMb = null;
if ($state === 'running') {
$info = shell_exec('virsh dominfo ' . escapeshellarg($name) . ' 2>/dev/null') ?? '';
if (preg_match('/CPU\(s\)\s*:\s*(\d+)/i', $info, $m)) $vcpus = (int)$m[1];
if (preg_match('/Used memory\s*:\s*(\d+)/i', $info, $m)) $memMb = (int)round((int)$m[1] / 1024);
}
$os = 'linux';
$xmlPath = '/etc/libvirt/qemu/' . $name . '.xml';
if (file_exists($xmlPath)) {
$xml = @file_get_contents($xmlPath) ?: '';
if (stripos($xml, 'windows') !== false || stripos($xml, 'win10') !== false || stripos($xml, 'win11') !== false) $os = 'windows';
elseif (stripos($xml, 'darwin') !== false || stripos($xml, 'macos') !== false) $os = 'macos';
}
$nl = strtolower($name);
if (str_contains($nl, 'win')) $os = 'windows';
elseif (str_contains($nl, 'mac') || str_contains($nl, 'osx')) $os = 'macos';
elseif (str_contains($nl, 'bsd') || str_contains($nl, 'freebsd')) $os = 'bsd';
$vms[] = [
'name' => $name,
'state' => $state,
'os' => $os,
'vcpus' => $vcpus,
'mem_mb' => $memMb,
];
}
return ['available' => true, 'vms' => $vms];
}