false, 'error' => 'Method not allowed']); exit; } $action = trim($_POST['action'] ?? 'save'); // ── HOST2 pull: pull master.conf from HOST1 via SSH ────────────────────────────────────────── if ($action === 'pull') { $host1Hostname = trim($_POST['host1_hostname'] ?? ''); $mySlot = trim($_POST['my_slot'] ?? 'host2'); $myHostname = trim($_POST['my_hostname'] ?? ''); if (!$host1Hostname) { echo json_encode(['ok' => false, 'error' => 'HOST1 hostname required']); exit; } if (!preg_match('/^host\d+$/', $mySlot)) { echo json_encode(['ok' => false, 'error' => 'Invalid slot']); exit; } $hostId = strtoupper($mySlot); $hostIdLow = strtolower($mySlot); // Derive SSH key path from this server's hostname $sshOwner = strtolower(preg_replace('/^unraid-/i', '', $myHostname ?: vv_get_hostname())); $sshKey = '/root/.ssh/' . $sshOwner . '_rsync_automation'; if (!file_exists($sshKey)) { echo json_encode(['ok' => false, 'error' => "SSH key not found at $sshKey — run Partnership/ssh_setup.sh first"]); exit; } // Resolve HOST1 Tailscale IP $ip = trim(shell_exec('tailscale ip -4 ' . escapeshellarg($host1Hostname) . ' 2>/dev/null') ?: ''); if (!$ip) { echo json_encode(['ok' => false, 'error' => "Cannot resolve Tailscale IP for $host1Hostname — is Tailscale running on both servers?"]); exit; } // Get HOST1's SCRIPTS_DIR from their varaverk.cfg $sshBase = 'ssh -i ' . escapeshellarg($sshKey) . ' -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@' . $ip; $remoteCfg = trim(shell_exec($sshBase . ' "grep SCRIPTS_DIR /boot/config/plugins/varaverk/varaverk.cfg 2>/dev/null"') ?: ''); preg_match('/SCRIPTS_DIR\s*=\s*["\']?([^"\']+)["\']?/', $remoteCfg, $sm); $remoteConf = rtrim($sm[1] ?? '/mnt/user/appdata/Varaverk', '/') . '/Configurations'; // SCP master.conf from HOST1 $localMaster = CONF_DIR . '/master.conf'; $src = escapeshellarg('root@' . $ip . ':' . $remoteConf . '/master.conf'); $cmd = 'scp -i ' . escapeshellarg($sshKey) . ' -o ConnectTimeout=10 -o StrictHostKeyChecking=no' . ' ' . $src . ' ' . escapeshellarg($localMaster) . ' 2>&1'; exec($cmd, $out, $rc); if ($rc !== 0) { echo json_encode(['ok' => false, 'error' => 'SCP failed: ' . implode('; ', $out) . ' — ensure your SSH key is authorised on HOST1 (run Partnership/ssh_setup.sh)']); exit; } // Create host conf from template if it doesn't exist $confFile = $hostIdLow . '.conf'; if (!file_exists(CONF_DIR . '/' . $confFile)) { $template = @file_get_contents(CONF_DIR . '/host.conf.template') ?: ''; if ($template) { $hostname = $myHostname ?: vv_get_hostname(); $sshKeyPath = $sshKey; $conf = str_replace('HOSTN', $hostId, $template); $conf = str_replace('hostn', $hostIdLow, $conf); $conf = preg_replace('/^(\s*' . $hostId . '_SSH_KEY\s*=\s*)""/m', '${1}"' . $sshKeyPath . '"', $conf); vv_write_conf_raw($confFile, $conf); } } echo json_encode(['ok' => true, 'host_id' => $hostId, 'conf_file' => $confFile, 'redirect' => '?tab=scheduler&vv_setup=' . $confFile]); exit; } // ── Default action: save (HOST1 first-run wizard) ──────────────────────────────────────────── $host1 = trim($_POST['host1'] ?? ''); $host2 = trim($_POST['host2'] ?? ''); $mySlot = trim($_POST['my_slot'] ?? 'host1'); $myHostname = trim($_POST['my_hostname'] ?? ''); if (empty($host1)) { echo json_encode(['ok' => false, 'error' => 'HOST1 hostname is required']); exit; } if (!preg_match('/^host\d+$/', $mySlot)) { echo json_encode(['ok' => false, 'error' => 'Invalid slot']); exit; } // Write HOST1 / HOST2 into master.conf $master = vv_read_conf_raw('master.conf'); if ($master === '') { echo json_encode(['ok' => false, 'error' => 'master.conf not found — check SCRIPTS_DIR in varaverk.cfg']); exit; } $master = preg_replace('/^(\s*HOST1\s*=\s*).*$/m', '${1}"' . addslashes($host1) . '"', $master); $master = preg_replace('/^(\s*HOST2\s*=\s*).*$/m', '${1}"' . addslashes($host2) . '"', $master); $slotNum = (int) preg_replace('/\D/', '', $mySlot); if ($slotNum > 2 && !empty($myHostname)) { $hostKey = 'HOST' . $slotNum; if (!preg_match('/^\s*' . $hostKey . '\s*=/m', $master)) { $master = preg_replace('/^(\s*HOST2\s*=.*$)/m', '$1' . "\n {$hostKey}=\"" . addslashes($myHostname) . '"', $master); } else { $master = preg_replace('/^(\s*' . $hostKey . '\s*=\s*).*$/m', '${1}"' . addslashes($myHostname) . '"', $master); } } if (!vv_write_conf_raw('master.conf', $master)) { echo json_encode(['ok' => false, 'error' => 'Failed to write master.conf']); exit; } // Create host*.conf from template $hostId = strtoupper($mySlot); $hostIdLow = strtolower($mySlot); $confFile = $hostIdLow . '.conf'; if (!file_exists(CONF_DIR . '/' . $confFile)) { $template = @file_get_contents(CONF_DIR . '/host.conf.template') ?: ''; if ($template) { $sshOwner = strtolower(preg_replace('/^unraid-/i', '', $myHostname)); $sshKeyPath = '/root/.ssh/' . $sshOwner . '_rsync_automation'; $conf = str_replace('HOSTN', $hostId, $template); $conf = str_replace('hostn', $hostIdLow, $conf); $conf = preg_replace('/^(\s*' . $hostId . '_SSH_KEY\s*=\s*)""/m', '${1}"' . $sshKeyPath . '"', $conf); if (!vv_write_conf_raw($confFile, $conf)) { echo json_encode(['ok' => false, 'error' => "Failed to write $confFile"]); exit; } } } // Write setup state file — lets partner servers know HOST1 is configured vv_setup_state_write(['host1_hostname' => $host1]); echo json_encode([ 'ok' => true, 'host_id' => $hostId, 'redirect' => '?tab=scheduler&vv_setup=master.conf', ]);