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
+48 -2
View File
@@ -1,6 +1,52 @@
<?php
// Connectivity test — SSH echo to a partner host with round-trip latency.
// GET ?id=HOST2 (GET avoids the bodyless-POST issue on this nginx setup).
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Partner connectivity test. Runs one SSH `echo ok` against a named host slot and reports
// success plus round-trip latency — the "Test" button on the partnership tab.
//
// OPERATIONAL MODEL
// Deliberately a GET, despite being an action. php -S and this nginx setup drop POST
// bodies on Unraid PHP 8.4, so a POST would arrive with no id at all. The operation is
// safe to repeat and changes nothing, which is what makes GET acceptable here rather than
// merely convenient.
//
// DESIGN PRINCIPLES
// Tests the real path, not a substitute for it.
// The probe is SSH over the Tailscale IP with the same key every partnership operation
// uses. An ICMP ping would answer a question nobody is asking — what matters is whether
// this host can actually drive the partner.
//
// Distinguishes why it failed.
// vv_pt_ping() returns separate errors for an unknown slot, a missing key, and an
// unresolvable Tailscale name, because those need three different fixes.
//
// OPERATIONAL SAFEGUARDS
// The host id is pattern-matched before it is used.
// ^host\d+$ (case-insensitive) is enforced here, and the value is then only used as a
// conf-key lookup — it never reaches a path or a shell. The remote command is the fixed
// literal `echo ok`; nothing from the request composes it.
//
// The probe is time-boxed at 8 seconds.
// vv_pt_ping() passes an explicit timeout to vv_pt_ssh(), which also sets BatchMode=yes
// so it can never sit waiting for a password. A powered-off partner costs 8s, not a
// stuck request.
//
// Read-only on the partner. `echo ok` is the entire remote payload.
//
// Never cached.
// Cache-Control: no-store, no-cache — a connectivity test served from cache is worse
// than no test, because it reports a partner reachable after it has gone dark.
//
// REQUEST
// GET ?id=host<n> (case-insensitive; the slot name, not the hostname)
//
// RESPONSE
// {"ok":true,"ms":int,…} on success
// {"ok":false,"error":string} invalid id, no key, unresolvable, or no reply
//
// DEPENDS ON
// include/partnership.php vv_pt_ping() → vv_pt_ssh(), vv_resolve_tailscale_ip()
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
header('Cache-Control: no-store, no-cache');
require_once dirname(__DIR__) . '/include/partnership.php';