Files
Varaverk/Plugin/unraid/api/flag_toggle.php
T
Gmer4Lfe 7625e923e5 Accept the global RSYNC_ENABLED flag, not only prefixed tiers
The pattern required a tier prefix, so the master gate the rsync page renders
alongside the tier toggles was rejected as an invalid flag name and sprang
back to off. There was no way to enable syncing from the UI. Masked until now
because every POST to this endpoint was being swallowed as multipart.
2026-08-02 18:49:00 -04:00

105 lines
5.4 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.
// vv_conf_flag_set() writes through vv_write_conf_raw() (tmp + rename). Every script
// sources master.conf, so a truncated 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_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]);