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,6 +1,84 @@
|
||||
<?php
|
||||
// Move a script between *_SCRIPTS arrays in master.conf.
|
||||
// POST: script (rel path), to_array (var name, or '' to remove from all arrays).
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
// PURPOSE
|
||||
// Moves a script between orchestrators. Removes its line from whichever *_SCRIPTS array in
|
||||
// master.conf currently holds it and inserts it into the named one — or into none, which
|
||||
// removes it from every orchestrator.
|
||||
//
|
||||
// OPERATIONAL MODEL
|
||||
// Line surgery on master.conf, not a re-serialisation. The file is read as lines, one line
|
||||
// is relocated, and the rest is written back byte for byte. master.conf is hand-maintained
|
||||
// and full of comments, grouping and deliberate ordering that no round trip through a
|
||||
// parser would preserve.
|
||||
//
|
||||
// Two passes, in order: remove first, then insert. Doing it in one pass would need to know
|
||||
// whether the target array comes before or after the source, and getting that wrong would
|
||||
// either duplicate the entry or drop it.
|
||||
//
|
||||
// Position within the target array is the end, immediately before its closing paren.
|
||||
// Orchestrators run their arrays in order, so appending is the only placement that does not
|
||||
// silently reorder someone else's work.
|
||||
//
|
||||
// DESIGN PRINCIPLES
|
||||
// Move is remove-plus-insert, and remove alone is a valid operation.
|
||||
// An empty to_array performs only the removal pass, which is how a script is taken out
|
||||
// of every orchestrator. That is a distinct intent from conf_toggle.php's commenting
|
||||
// out — this removes the line, that disables it in place.
|
||||
//
|
||||
// Indentation is normalised on re-insertion.
|
||||
// The moved line is rewritten as two spaces and the quoted path, so a script does not
|
||||
// carry its old array's formatting into its new one.
|
||||
//
|
||||
// A move that finds nothing to move still succeeds.
|
||||
// The removal pass is best-effort; only a missing *target* is an error. A script that
|
||||
// was in no array is simply added to the one requested.
|
||||
//
|
||||
// OPERATIONAL SAFEGUARDS
|
||||
// POST only, checked before any parameter is read.
|
||||
//
|
||||
// Both inputs are pattern-matched, and neither is used as a path.
|
||||
// The script must match ^[A-Za-z0-9_.\-/]+\.sh$ with an explicit '..' check, and the
|
||||
// array name ^[A-Z_]+_SCRIPTS$. Both then reach the matcher only through preg_quote(),
|
||||
// so they are needles matched against existing lines — nothing is opened or executed
|
||||
// from either.
|
||||
//
|
||||
// The scope of both passes is bounded to array bodies.
|
||||
// Each pass tracks whether it is inside a *_SCRIPTS=( block and ignores every line
|
||||
// outside one, so a matching string in a comment or an unrelated variable is never
|
||||
// moved or displaced.
|
||||
//
|
||||
// A missing target array aborts before the write.
|
||||
// If the insert pass never finds the target, the endpoint returns an error and writes
|
||||
// nothing — the removal is discarded with it. Without that check a typo'd array name
|
||||
// would silently delete the script from the orchestrator it was in.
|
||||
//
|
||||
// master.conf is confirmed present and readable before either pass.
|
||||
// Both file_exists() and the file() result are checked, so a missing or unreadable conf
|
||||
// returns a named error rather than writing a file built from an empty line list.
|
||||
//
|
||||
// The write is atomic.
|
||||
// vv_write_conf_raw() writes .vv.tmp and rename()s. Every script sources master.conf,
|
||||
// so a truncated write here would be a system-wide outage rather than a lost move.
|
||||
//
|
||||
// The push happens only after a confirmed write, and its result is returned.
|
||||
// A partner that did not receive the move is exactly the state that makes one host run
|
||||
// a script the other does not, so the per-host outcome is reported rather than
|
||||
// discarded.
|
||||
//
|
||||
// REQUEST
|
||||
// POST script=<Category/name.sh> to_array=<NAME>_SCRIPTS
|
||||
// POST script=<Category/name.sh> to_array= remove from all arrays
|
||||
//
|
||||
// RESPONSE
|
||||
// {"ok":true,"push":[{"host","ok","ready","error"}, …]}
|
||||
// {"ok":false,"error":"POST only"|"Invalid script"|"Invalid array name"
|
||||
// |"master.conf not found"|"Could not read master.conf"
|
||||
// |"Target array \"…\" not found in master.conf"|"Write failed"}
|
||||
//
|
||||
// DEPENDS ON
|
||||
// include/config.php CONF_DIR, vv_write_conf_raw(), vv_push_master_conf(),
|
||||
// vv_push_setup_state()
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
@@ -75,5 +153,8 @@ if (!vv_write_conf_raw('master.conf', implode('', $newLines))) {
|
||||
exit;
|
||||
}
|
||||
|
||||
vv_push_master_conf();
|
||||
echo json_encode(['ok' => true]);
|
||||
// Reported rather than discarded — a partner that did not receive the move is exactly the
|
||||
// state that makes one host run a script the other does not. Mirrors rawconf/confform.
|
||||
$push = vv_push_master_conf();
|
||||
vv_push_setup_state();
|
||||
echo json_encode(['ok' => true, 'push' => $push]);
|
||||
|
||||
Reference in New Issue
Block a user