Eleven call sites wrote master.conf with tmp+rename and nothing else — no backup, no parse check, no audit — including the two toggles the UI uses most and the raw editor that installs a whole hand-edited file.
108 lines
5.7 KiB
PHP
108 lines
5.7 KiB
PHP
<?php
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
// PURPOSE
|
|
// Rsync tier toggle. Flips one *_RSYNC_ENABLED boolean in master.conf and propagates the
|
|
// changed file to every partner host — the enable switches on the rsync tab.
|
|
//
|
|
// OPERATIONAL MODEL
|
|
// master.conf is shared, not per-host. A tier flag has to mean the same thing on both sides
|
|
// of the partnership or a sync will run from one end and not the other, so the write is
|
|
// always followed by a push. The push is a no-op on a non-owner: vv_push_master_conf()
|
|
// returns empty when this host has no SSH key, so a partner flipping a flag locally does
|
|
// not overwrite the owner's file.
|
|
//
|
|
// Two flags exist at different levels — RSYNC_ENABLED is the global gate and the tier flags
|
|
// (CRITICAL_, INTERMEDIATE_, DAILY_, WEEKLY_, FALLBACK_) sit under it. This endpoint treats
|
|
// them identically; the precedence lives in the shell layer.
|
|
//
|
|
// DESIGN PRINCIPLES
|
|
// Toggles existing flags, never creates them.
|
|
// vv_conf_flag_set() rewrites a line that already matches NAME=true|false and returns
|
|
// false when nothing matched. A typo'd flag name fails loudly rather than appending a
|
|
// key no script reads.
|
|
//
|
|
// Push results are reported, not swallowed.
|
|
// The per-host push outcome is returned in the response so the page can show that a
|
|
// partner did not receive the change. A flag that is set on one host and not the other
|
|
// is exactly the state that produces a one-sided sync.
|
|
//
|
|
// Setup state is pushed alongside the conf.
|
|
// vv_push_setup_state() runs after the conf push so the partner's onboarding view
|
|
// reflects the same reality — the two are written together because they are read
|
|
// together.
|
|
//
|
|
// OPERATIONAL SAFEGUARDS
|
|
// POST only, checked before any parameter is read.
|
|
//
|
|
// The flag name is constrained to the rsync namespace, with the tier prefix optional.
|
|
// ^([A-Z][A-Z_]*_)?RSYNC_ENABLED$ — this endpoint cannot be used to flip an unrelated
|
|
// boolean in master.conf. Every other conf edit goes through confform.php or config.php,
|
|
// which have their own rules; a general-purpose flag setter would bypass all of them.
|
|
//
|
|
// The prefix must be optional because RSYNC_ENABLED is itself the global gate, and the
|
|
// page renders it alongside the tier flags. Requiring a prefix rejected it as an invalid
|
|
// flag name, so the master switch could not be turned on from the UI at all.
|
|
//
|
|
// Anything other than the literal "1" is treated as false.
|
|
// ($_POST['enabled'] ?? '0') === '1' — strict comparison against one value, so a
|
|
// missing, malformed, or unexpected parameter disables rather than enables. Failing
|
|
// toward off is the safe direction for a flag that starts data movement.
|
|
//
|
|
// The conf write is atomic, backed up, verified and logged.
|
|
// vv_conf_flag_set() goes through vv_conf_edit(), the one guarded conf write path: an
|
|
// exclusive lock, a timestamped copy into CONF_BACKUP_DIR, bash -n on the candidate,
|
|
// tmp + rename to install it, then the file is sourced and the flag read back — a value
|
|
// that does not come back as asked restores the backup. Every script sources master.conf,
|
|
// so a truncated or unparseable write would be a system-wide outage.
|
|
//
|
|
// The push only happens after a confirmed local write.
|
|
// Guarded on $ok, so a failed edit cannot distribute a stale or partly-written conf to
|
|
// partners.
|
|
//
|
|
// Related risk, not guarded here: turning RSYNC_ENABLED on for HOST2 onboarding requires
|
|
// re-reading the --merge-run / --delete interlock in Rsync/rsync.sh first. That is a
|
|
// property of the sync, not of this switch, and this endpoint does not enforce it.
|
|
//
|
|
// REQUEST
|
|
// POST name=<TIER>_RSYNC_ENABLED enabled=0|1
|
|
//
|
|
// RESPONSE
|
|
// {"ok":true,"error":null,"push":[{"host","ok","ready","error"}, …]}
|
|
// {"ok":false,"error":"POST only"|"Invalid flag name"|"Failed to write master.conf",
|
|
// "push":[]}
|
|
//
|
|
// DEPENDS ON
|
|
// include/scheduler.php vv_conf_flag_set() → vv_conf_edit() → vv_write_conf_raw()
|
|
// include/config.php vv_push_master_conf(), vv_push_setup_state()
|
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
header('Content-Type: application/json');
|
|
require_once dirname(__DIR__) . '/include/scheduler.php';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
echo json_encode(['ok' => false, 'error' => 'POST only']);
|
|
exit;
|
|
}
|
|
|
|
$name = trim($_POST['name'] ?? '');
|
|
$enabled = ($_POST['enabled'] ?? '0') === '1';
|
|
|
|
// The tier prefix is optional. RSYNC_ENABLED itself is the global gate the page also renders,
|
|
// and requiring a prefix silently rejected it — the toggle reverted to off with no way to turn
|
|
// syncing on from the UI at all. Masked until now because every POST to this endpoint was
|
|
// being swallowed by the multipart bug.
|
|
if (!$name || !preg_match('/^([A-Z][A-Z_]*_)?RSYNC_ENABLED$/', $name)) {
|
|
echo json_encode(['ok' => false, 'error' => 'Invalid flag name']);
|
|
exit;
|
|
}
|
|
|
|
$ok = vv_conf_flag_set($name, $enabled);
|
|
|
|
// master.conf is shared — propagate the change to partner hosts (no-op on non-owner).
|
|
$push = [];
|
|
if ($ok) {
|
|
$push = vv_push_master_conf();
|
|
vv_push_setup_state();
|
|
}
|
|
|
|
echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write master.conf', 'push' => $push]);
|