Writing down what each endpoint actually guarantees made the places it didn't obvious — shell arguments reaching a crontab or a bash -c unescaped, master.conf written without tmp+rename, and conf edits that could be saved without ever being parsed.
67 lines
3.5 KiB
PHP
67 lines
3.5 KiB
PHP
<?php
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
// PURPOSE
|
|
// Remote node metrics endpoint. The monitor page's partner cards — CPU, memory, storage,
|
|
// uptime and container counts for every host other than this one.
|
|
//
|
|
// OPERATIONAL MODEL
|
|
// Split from monitor.php on cost, not on subject. Local metrics are cheap file reads;
|
|
// remote metrics are SSH round trips to every partner. Keeping them on separate URLs lets
|
|
// the page poll local stats often and remote stats rarely, and lets a dark partner slow
|
|
// only its own request. Served from a 1-hour cache by default; ?live bypasses it for the
|
|
// page's explicit refresh button.
|
|
//
|
|
// DESIGN PRINCIPLES
|
|
// The cache is the default and the live call is the exception.
|
|
// An hour is deliberately long. Partner hardware stats do not move fast enough to
|
|
// justify paying SSH latency on every page load, and the refresh button exists for the
|
|
// moment someone actually needs current numbers.
|
|
//
|
|
// Every payload carries its own timestamp.
|
|
// ts is written into the cached document, so the page can render the age rather than
|
|
// presenting hour-old numbers as current.
|
|
//
|
|
// The live path writes the cache too.
|
|
// A manual refresh benefits every subsequent visitor instead of being discarded.
|
|
//
|
|
// OPERATIONAL SAFEGUARDS
|
|
// Cache miss is distinguished from empty payload.
|
|
// vv_cache_read() returns null on a miss, expiry, or unparseable file, and the check is
|
|
// an explicit !== null. A legitimately empty result — the single-host case, where there
|
|
// are no remote hosts at all — is served from cache rather than being mistaken for a
|
|
// miss and forced onto the SSH path on every single poll.
|
|
//
|
|
// Read-only over SSH. The remote commands are stat collection only; nothing is started,
|
|
// stopped, or written on a partner.
|
|
//
|
|
// Unreachable partners degrade per node inside vv_remote_hosts_stats(), so one dark host
|
|
// cannot empty the other cards.
|
|
//
|
|
// HTTP caching is disabled even though the payload is cached server-side.
|
|
// The two are not the same lever. The server-side cache has an age the page can see and
|
|
// a bypass it can trigger; a browser or proxy cache has neither, and would defeat ?live
|
|
// entirely.
|
|
//
|
|
// REQUEST
|
|
// GET served from the 3600s cache when one is present
|
|
// GET ?live bypass the cache, collect fresh, and rewrite it
|
|
//
|
|
// RESPONSE
|
|
// {"remote_hosts":{…},"ts":epoch}
|
|
//
|
|
// DEPENDS ON
|
|
// include/monitor.php vv_remote_hosts_stats(), vv_cache_read(), vv_cache_write()
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
header('Content-Type: application/json');
|
|
header('Cache-Control: no-cache, no-store');
|
|
require_once dirname(__DIR__) . '/include/monitor.php';
|
|
|
|
if (!isset($_GET['live'])) {
|
|
$cached = vv_cache_read('monitor_remote', 3600);
|
|
if ($cached !== null) { echo json_encode($cached); exit; }
|
|
}
|
|
|
|
$data = ['remote_hosts' => vv_remote_hosts_stats(), 'ts' => time()];
|
|
vv_cache_write('monitor_remote', $data);
|
|
echo json_encode($data);
|