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:
@@ -1,5 +1,101 @@
|
||||
<?php
|
||||
// Live board data: locks, recent errors, partner reachability.
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
// PURPOSE
|
||||
// Attention board. The three things worth knowing without opening a tab: locks nobody is
|
||||
// holding, scripts whose last run ended badly, and whether the partner is reachable.
|
||||
//
|
||||
// OPERATIONAL MODEL
|
||||
// A dashboard of exceptions, not of state. Everything here is designed to be empty on a
|
||||
// healthy host — an empty board is the expected reading, which is what makes a non-empty
|
||||
// one worth looking at.
|
||||
//
|
||||
// Each section answers a question no single other endpoint does. Locks come from the
|
||||
// filesystem, errors from correlating logs against their stat files, reachability from an
|
||||
// actual ping. They are collected together because they are read together.
|
||||
//
|
||||
// DESIGN PRINCIPLES
|
||||
// Only stale locks are reported.
|
||||
// A lock whose owning pid is still in /proc is a job legitimately running and is
|
||||
// skipped. What remains is the set a person might need to clear — which is exactly what
|
||||
// clearlock.php exists for.
|
||||
//
|
||||
// Errors are gated on the stat file, not on the log text.
|
||||
// A script is only reported when its last recorded run exited warn or error. Scanning
|
||||
// logs for the word "error" produced two persistent false positives: dry runs, which
|
||||
// print failures they did not cause, and success summaries containing lines like
|
||||
// "Failed: 0". The stat file is the run's own verdict, and it is authoritative.
|
||||
//
|
||||
// A script with no stat file is skipped entirely.
|
||||
// No stat file means it never ran through run_job.sh, so there is no verdict to trust
|
||||
// and no basis for reporting it.
|
||||
//
|
||||
// The error line is the last matching one, searched backwards.
|
||||
// Logs are appended, so the most recent failure is at the end. The search walks up from
|
||||
// there and stops at the first hit rather than reading forward and reporting the oldest.
|
||||
//
|
||||
// There is a fallback when nothing matches.
|
||||
// A run that failed without printing a recognisable error still reports its last
|
||||
// non-blank line. A script marked error with no explanation is worse than an imperfect
|
||||
// one.
|
||||
//
|
||||
// The partner is discovered, not configured.
|
||||
// master.conf is scanned for HOST<n> entries and the first non-self one is used, so this
|
||||
// works for any number of hosts without a second list to maintain.
|
||||
//
|
||||
// Tailscale resolves the address, never local DNS.
|
||||
// vv_resolve_tailscale_ip() mirrors common.sh, so the board tests the same path the
|
||||
// rest of the system uses and survives the partner's IP changing.
|
||||
//
|
||||
// OPERATIONAL SAFEGUARDS
|
||||
// Read-only. Nothing here clears a lock, truncates a log, or restarts anything — the board
|
||||
// reports; clearlock.php and stop.php act.
|
||||
//
|
||||
// The lock scan is bounded to a hardcoded directory.
|
||||
// glob over /tmp/unraid_locks/*.lock — a literal, not a config value, so no conf edit
|
||||
// can point this scan somewhere else.
|
||||
//
|
||||
// The log walk is wrapped in a try/catch.
|
||||
// RecursiveDirectoryIterator throws when LOG_DIR is absent or a subdirectory is
|
||||
// unreadable — the normal state on a fresh install. The catch yields an empty error
|
||||
// list rather than a 500.
|
||||
//
|
||||
// Every file read is independently suppressed and defaulted.
|
||||
// @file_get_contents, @file, and ?: fallbacks throughout. One unreadable log or stat
|
||||
// file costs its own row, not the response.
|
||||
//
|
||||
// The scan window and the output are both bounded.
|
||||
// Logs older than 7 days are skipped, only the last 200 lines of each are read, each
|
||||
// line is truncated at 220 characters, and the result is capped at 20 errors. This runs
|
||||
// against a directory that grows without limit.
|
||||
//
|
||||
// ANSI escapes are stripped before matching and before returning.
|
||||
// Logs are written with colour. Without stripping, the patterns would miss coloured
|
||||
// error markers and the JSON would carry terminal control codes into the page.
|
||||
//
|
||||
// The ping target is escaped, and time-boxed by ping itself.
|
||||
// escapeshellarg() on a value that came from conf, and -c1 -W2 so an unreachable
|
||||
// partner costs two seconds. The result is cached 30s so the board's poll does not ping
|
||||
// on every request.
|
||||
//
|
||||
// Reachability falls back to the hostname when Tailscale cannot resolve.
|
||||
// Better to test something and report the result than to report nothing because the
|
||||
// preferred resolution path failed.
|
||||
//
|
||||
// REQUEST
|
||||
// GET, no parameters
|
||||
//
|
||||
// RESPONSE
|
||||
// {"ok":true,
|
||||
// "locks":[{"name","file","age"}], stale locks only — empty is healthy
|
||||
// "errors":[{"script","line","ts"}], newest first, max 20
|
||||
// "partner":{"host","reachable","latency"}} null when no partner is configured
|
||||
//
|
||||
// DEPENDS ON
|
||||
// include/config.php LOG_DIR, vv_conf_vars(), vv_get_hostname(),
|
||||
// vv_resolve_tailscale_ip(), vv_cache_read(), vv_cache_write()
|
||||
// /tmp/unraid_locks lock files written by common.sh's locking helper
|
||||
// LOG_DIR/** .log files and their .json stat files, written by run_job.sh
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user