Document the PHP api layer and fix what documenting it exposed

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.
This commit is contained in:
Gmer4Lfe
2026-08-02 10:11:39 -04:00
parent 6a959fb5e4
commit 987313e7dc
55 changed files with 3972 additions and 95 deletions
+78 -4
View File
@@ -1,9 +1,83 @@
<?php
// API cache writer — runs every minute via Varaverk scheduler.
// Builds monitor + arrs payloads and writes them to /tmp/vv_cache/ so page
// loads can serve instantly from the file instead of making live HTTP calls.
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Background cache writer. Builds the full monitor payload and the arrs payload once a
// minute and writes them to the cache, so page loads serve from a file instead of paying
// for collection.
//
// Called by api_cache_writer.sh (bash wrapper required by the scheduler).
// OPERATIONAL MODEL
// This is what makes the monitor and arrs tabs fast. api/monitor.php and api/arrs.php read
// the files this process writes and only fall back to collecting for themselves on a miss.
// The expensive work — GraphQL, docker stats, SSH to partners, HTTP to every arr instance —
// happens here, on a schedule, off the request path.
//
// Runs from cron every minute via api_cache_writer.sh, a bash wrapper the scheduler
// requires. Not reachable over HTTP, and refuses to run if it ever is.
//
// The payload assembled here is deliberately identical to api/monitor.php's. The two are
// maintained together: a field added there and not here is a field that is only ever served
// on a cache miss.
//
// DESIGN PRINCIPLES
// One API round trip for the whole payload.
// vv_api_data() is called once up front and static-cached for the life of the process,
// so the API-first collectors below share a single GraphQL query rather than issuing
// one each.
//
// Writes two caches, not one.
// monitor and arrs have different consumers and different costs, so they are written
// under separate keys and either can be served while the other is stale.
//
// Every payload carries its own timestamp, so consumers can render age rather than
// presenting minute-old numbers as current.
//
// Reports its own duration on stdout.
// The elapsed time goes to the job log, which is the only place a slow collection cycle
// becomes visible — this process has no other output and no failure anyone would see.
//
// OPERATIONAL SAFEGUARDS
// Refuses to run under a web server.
// PHP_SAPI is checked first and a non-CLI invocation is answered with a 404 and no
// output. Otherwise a browser hitting this path would trigger the full expensive
// collection — including SSH to every partner — outside any cache or rate limit, once
// per request.
//
// Read-only with respect to the system. Every collector observes; none start, stop, or
// change anything. The only writes are the two cache files.
//
// Cache writes are atomic.
// vv_cache_write() writes a .tmp and renames, so a page load landing mid-write reads
// the previous complete payload rather than a truncated one.
//
// Every collector degrades to empty rather than fatal.
// The library suppresses its filesystem reads and redirects stderr on every shell call,
// so absent hardware yields an empty section. On a payload this wide that property is
// what keeps one missing subsystem from failing the whole cycle and leaving both caches
// to expire.
//
// A failed cycle is survivable by design.
// Nothing here clears the previous cache before building the new one. A run that dies
// partway leaves the last good payload in place, and the readers' own age windows —
// 300s for monitor, 300s for arrs — are several cycles wide, so a single missed minute
// is invisible.
//
// OUTPUT
// VV_CACHE_DIR/monitor.json consumed by api/monitor.php
// VV_CACHE_DIR/arrs.json consumed by api/arrs.php
// stdout one timing line, captured into the job log
//
// DEPENDS ON
// include/monitor.php, include/common.php, include/unraid_api.php,
// include/vms.php, include/docker_folders.php, include/arrs.php
// Tools/api_cache_writer.sh the bash wrapper cron actually invokes
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// Collecting this payload means GraphQL, docker stats and SSH to every partner. It must never
// be triggerable by an HTTP request.
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit(1);
}
$_base = dirname(__DIR__);
require_once $_base . '/include/monitor.php';