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
+90 -2
View File
@@ -1,7 +1,73 @@
<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Relocates the plugin. Points SCRIPTS_DIR in varaverk.cfg at a different Varaverk checkout
// — the manual equivalent of what the storage-migration flow does automatically.
//
// OPERATIONAL MODEL
// varaverk.cfg is the one file whose location is fixed. Everything else the plugin reads —
// scripts, Configurations/, Deployment/, State_Files/, data/ — is resolved relative to the
// SCRIPTS_DIR it names, which is what makes internal-vs-appdata storage mode possible at
// all. Rewriting that one value moves the entire plugin.
//
// Currently unreferenced by the UI. The settings tab drives relocation through the storage
// migration flow, which also copies the data. This endpoint changes the pointer alone, and
// is kept as the recovery path for a cfg that points somewhere that no longer exists.
//
// DESIGN PRINCIPLES
// Changes the pointer, never moves the data.
// There is deliberately no copy step. Migration is a separate, longer operation with
// its own confirmation; conflating the two would make a one-field edit capable of
// deleting a directory.
//
// Merges into the existing cfg rather than authoring it.
// varaverk.cfg carries GITEA_CONTAINER, GITEA_REPO_PATH, GITEA_SSH_KEY, SSH_PORT and
// CUSTOM_SCRIPTS_DIR alongside SCRIPTS_DIR. Only the key being changed is touched.
//
// OPERATIONAL SAFEGUARDS
// POST only, checked before anything is read.
//
// The target must actually be a Varaverk checkout.
// is_dir() alone is not enough — it would happily accept /mnt/user or /tmp and leave
// every subsequent page resolving conf and script paths under a directory that contains
// none of them, with no single obvious symptom. common.sh and load_config.sh must both
// be present, because those are the two files every script in the repo sources.
//
// Unrelated keys survive the write.
// The previous version emitted a file containing SCRIPTS_DIR and nothing else, silently
// discarding the Gitea coordinates that git_pull_execute.sh needs — so a relocation
// would have broken both the daily pull and the UI pull button, and only at the next
// scheduled run. The cfg is now read, one key replaced, and all keys written back.
//
// The write is atomic.
// tmp + rename, so a page render or a script reading varaverk.cfg mid-save sees the old
// file or the new one. A truncated varaverk.cfg would leave SCRIPTS_DIR undefined and
// every path in the plugin falling back to the compiled-in default.
//
// Values are escaped for the ini quoting parse_ini_file() expects.
// addslashes() on every value written, not just the new one, so a path containing a
// quote cannot terminate the string early and corrupt the keys that follow it.
//
// REQUEST
// POST scripts_dir=<absolute path to a Varaverk checkout>
//
// RESPONSE
// {"ok":true,"error":null}
// {"ok":false,"error":"POST only"|"scripts_dir is required"|"Directory does not exist"
// |"Not a Varaverk checkout — common.sh and load_config.sh not found"
// |"Failed to write cfg file"}
//
// DEPENDS ON
// include/config.php PLUGIN_CFG
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['ok' => false, 'error' => 'POST only']);
exit;
}
$scriptsDir = trim($_POST['scripts_dir'] ?? '');
if (!$scriptsDir) {
@@ -14,11 +80,33 @@ if (!is_dir($scriptsDir)) {
exit;
}
// Pointing SCRIPTS_DIR at an arbitrary directory leaves every conf and script path in the
// plugin resolving under something that holds none of them. These two files are sourced by
// every script in the repo, so their presence is what makes a directory a checkout.
if (!file_exists($scriptsDir . '/common.sh') || !file_exists($scriptsDir . '/load_config.sh')) {
echo json_encode([
'ok' => false,
'error' => 'Not a Varaverk checkout — common.sh and load_config.sh not found',
]);
exit;
}
$cfgFile = PLUGIN_CFG;
$cfgDir = dirname($cfgFile);
if (!is_dir($cfgDir)) mkdir($cfgDir, 0755, true);
$content = 'SCRIPTS_DIR="' . addslashes($scriptsDir) . '"' . "\n";
$ok = file_put_contents($cfgFile, $content) !== false;
// Merge, do not author. varaverk.cfg also carries the Gitea coordinates git_pull_execute.sh
// needs — rewriting the file with SCRIPTS_DIR alone would silently break the daily pull.
$cfg = @parse_ini_file($cfgFile) ?: [];
$cfg['SCRIPTS_DIR'] = $scriptsDir;
$content = '';
foreach ($cfg as $k => $v) {
$content .= $k . '="' . addslashes((string)$v) . '"' . "\n";
}
$tmp = $cfgFile . '.vv.tmp';
$ok = file_put_contents($tmp, $content) !== false && rename($tmp, $cfgFile);
if (!$ok) @unlink($tmp);
echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write cfg file']);