to_array=_SCRIPTS // POST script= 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'; if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['ok' => false, 'error' => 'POST only']); exit; } $script = trim($_POST['script'] ?? ''); $toArray = trim($_POST['to_array'] ?? ''); if (!$script || str_contains($script, '..') || !preg_match('/^[A-Za-z0-9_.\-\/]+\.sh$/', $script)) { echo json_encode(['ok' => false, 'error' => 'Invalid script']); exit; } if ($toArray && !preg_match('/^[A-Z_]+_SCRIPTS$/', $toArray)) { echo json_encode(['ok' => false, 'error' => 'Invalid array name']); exit; } $confPath = CONF_DIR . '/master.conf'; if (!file_exists($confPath)) { echo json_encode(['ok' => false, 'error' => 'master.conf not found']); exit; } $lines = file($confPath, FILE_KEEP_BLANK_LINES); if (!$lines) { echo json_encode(['ok' => false, 'error' => 'Could not read master.conf']); exit; } $scriptEsc = preg_quote($script, '/'); $removedLine = null; $inArray = false; // Step 1: find and remove the script line from whatever array it is currently in. $newLines = []; foreach ($lines as $line) { if (preg_match('/^\s*[A-Z_]+_SCRIPTS\s*=\s*\(/', $line)) $inArray = true; if ($inArray && preg_match('/^\s*\)\s*(?:#.*)?$/', $line) && !str_contains($line, '(')) $inArray = false; if ($inArray && preg_match('/^\s*(?:#\s*)?"' . $scriptEsc . '(?:\s[^"]*)?"/', $line)) { $removedLine = ' "' . $script . '"' . "\n"; // normalise indentation when re-inserting continue; // drop from current location } $newLines[] = $line; } // Step 2: insert into target array (if specified). if ($toArray) { $resultLines = []; $inTarget = false; $inserted = false; foreach ($newLines as $line) { if (preg_match('/^\s*' . preg_quote($toArray, '/') . '\s*=\s*\(/', $line)) $inTarget = true; if ($inTarget && !$inserted && preg_match('/^\s*\)\s*(?:#.*)?$/', $line) && !str_contains($line, '(')) { $resultLines[] = $removedLine ?? (' "' . $script . '"' . "\n"); $inTarget = false; $inserted = true; } $resultLines[] = $line; } if (!$inserted) { echo json_encode(['ok' => false, 'error' => 'Target array "' . $toArray . '" not found in master.conf']); exit; } $newLines = $resultLines; } if (!vv_write_conf_raw('master.conf', implode('', $newLines))) { echo json_encode(['ok' => false, 'error' => 'Write failed']); exit; } // 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]);