rsync_opts, bw_limit, retry_count, sleep, // critical_containers, delayed_containers, container_delay, // exclude_dirs, remote_restart (all optional, default empty) // POST action=delete name= // // RESPONSE // list {"ok":true,"profiles":{"":{"":"", …}, …}} // save {"ok":bool,"results":{"master.conf":bool}} // delete {"ok":bool} // {"ok":false,"error":"Invalid profile name — …"|"No profile arrays found in master.conf" // |"Profile not found"|"Unknown action"} // // DEPENDS ON // include/confform.php vv_conf_write_changes() // include/config.php vv_read_conf_raw(), vv_push_master_conf(), vv_push_setup_state() // Rsync/rsync.sh consumer of every PROFILE_* array this writes // ═══════════════════════════════════════════════════════════════════════════════════════════════ header('Content-Type: application/json'); header('Cache-Control: no-store, no-cache'); require_once dirname(__DIR__) . '/include/config.php'; require_once dirname(__DIR__) . '/include/confform.php'; // All PROFILE_* assoc arrays and their UI field keys const RP_ARRAYS = [ 'PROFILE_RSYNC_OPTS' => 'rsync_opts', 'PROFILE_BW_LIMIT' => 'bw_limit', 'PROFILE_RETRY_COUNT' => 'retry_count', 'PROFILE_SLEEP' => 'sleep', 'PROFILE_CRITICAL_CONTAINER_NAMES' => 'critical_containers', 'PROFILE_DELAYED_CONTAINERS' => 'delayed_containers', 'PROFILE_CONTAINER_DELAY' => 'container_delay', 'PROFILE_EXCLUDE_DIRS' => 'exclude_dirs', 'PROFILE_REMOTE_RESTART_CONTAINERS' => 'remote_restart', ]; // Parse [key]="value" or [key]=bare entries from an assoc_array body function _rp_parse_assoc(string $body): array { $result = []; preg_match_all('/\[([^\]]+)\]\s*=\s*(?:"([^"]*)"|([^\s#\n]*))/', $body, $m, PREG_SET_ORDER); foreach ($m as $match) { $key = $match[1]; $val = $match[2] !== '' ? $match[2] : ($match[3] ?? ''); $result[$key] = $val; } return $result; } // Rebuild assoc_array body from entries map function _rp_build_assoc(array $entries): string { $lines = []; foreach ($entries as $k => $v) { // Quote if empty, has spaces or special shell chars if ($v === '' || preg_match('/[\s\$\!\[\]\(\)\|\'`\\\\]/', $v)) { $lines[] = ' [' . $k . ']="' . str_replace('"', '\\"', $v) . '"'; } else { $lines[] = ' [' . $k . ']=' . $v; } } return implode("\n", $lines); } // Read all PROFILE_* arrays from master.conf → [profile_name => [field => value]] function _rp_read_all(): array { $raw = vv_read_conf_raw('master.conf'); $result = []; foreach (RP_ARRAYS as $varName => $fieldKey) { if (!preg_match('/declare\s+-A\s+' . preg_quote($varName, '/') . '\s*=\s*\((.*?)\)/s', $raw, $m)) continue; foreach (_rp_parse_assoc($m[1]) as $profile => $value) { $result[$profile][$fieldKey] = $value; } } return $result; } $action = trim($_GET['action'] ?? $_POST['action'] ?? ''); // ── list ────────────────────────────────────────────────────────────────────── if ($action === 'list') { echo json_encode(['ok' => true, 'profiles' => _rp_read_all()]); exit; } // ── save (create or update) ─────────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'save') { $name = trim($_POST['name'] ?? ''); if (!$name || !preg_match('/^[a-zA-Z0-9_\-]+$/', $name)) { echo json_encode(['ok' => false, 'error' => 'Invalid profile name — use letters, numbers, hyphens, underscores']); exit; } $raw = vv_read_conf_raw('master.conf'); $changes = []; foreach (RP_ARRAYS as $varName => $fieldKey) { $value = trim($_POST[$fieldKey] ?? ''); // Find and parse current array body if (!preg_match('/declare\s+-A\s+' . preg_quote($varName, '/') . '\s*=\s*\((.*?)\)/s', $raw, $m)) continue; $entries = _rp_parse_assoc($m[1]); $entries[$name] = $value; $changes[] = [ 'file' => 'master.conf', 'key' => $varName, 'type' => 'assoc_array', 'value' => _rp_build_assoc($entries), ]; } if (!$changes) { echo json_encode(['ok' => false, 'error' => 'No profile arrays found in master.conf']); exit; } $results = vv_conf_write_changes($changes); $ok = !in_array(false, $results, true); if ($ok) { vv_push_master_conf(); vv_push_setup_state(); } echo json_encode(['ok' => $ok, 'results' => $results]); exit; } // ── delete ──────────────────────────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'delete') { $name = trim($_POST['name'] ?? ''); if (!$name || !preg_match('/^[a-zA-Z0-9_\-]+$/', $name)) { echo json_encode(['ok' => false, 'error' => 'Invalid profile name']); exit; } $raw = vv_read_conf_raw('master.conf'); $changes = []; foreach (RP_ARRAYS as $varName => $fieldKey) { if (!preg_match('/declare\s+-A\s+' . preg_quote($varName, '/') . '\s*=\s*\((.*?)\)/s', $raw, $m)) continue; $entries = _rp_parse_assoc($m[1]); if (!array_key_exists($name, $entries)) continue; unset($entries[$name]); $changes[] = [ 'file' => 'master.conf', 'key' => $varName, 'type' => 'assoc_array', 'value' => _rp_build_assoc($entries), ]; } if (!$changes) { echo json_encode(['ok' => false, 'error' => 'Profile not found']); exit; } $results = vv_conf_write_changes($changes); $ok = !in_array(false, $results, true); if ($ok) { vv_push_master_conf(); vv_push_setup_state(); } echo json_encode(['ok' => $ok]); exit; } echo json_encode(['ok' => false, 'error' => 'Unknown action']);