diff --git a/Plugin/unraid/pages/setup.php b/Plugin/unraid/pages/setup.php
index 94b0de0..c8846f4 100644
--- a/Plugin/unraid/pages/setup.php
+++ b/Plugin/unraid/pages/setup.php
@@ -322,13 +322,17 @@ function vvRunPopulate() {
// ── Checklist ─────────────────────────────────────────────────────────────────
const vvActionLabels = {
create_key: 'Create API key',
- ssh_setup: 'SSH guide →',
+ ssh_setup: 'Generate SSH key',
run_populate: 'Run now',
pull_master: 'Pull from HOST1',
onboard: 'Partnership tab →',
};
+// ssh_setup used to be a link to the Partnership tab — a guide, on the one row where the reader
+// has no key and therefore cannot do the thing the guide describes. It generates the key here
+// now, through api/setup.php?action=ssh_generate, which already existed and had never been
+// called from anywhere. The partner path needs this: 'Pull from HOST1' authenticates with the
+// key this row creates, so sending someone away to read about it left the next row unusable.
const vvActionHref = {
- ssh_setup: '?tab=partnership',
onboard: '?tab=partnership',
};
@@ -352,6 +356,9 @@ function vvLoadChecklist() {
act = `
@@ -366,6 +373,46 @@ function vvLoadChecklist() {
}).catch(() => {});
}
+// ── SSH key ───────────────────────────────────────────────────────────────────
+// Generates this node's keypair and shows the public half. Generating it is the easy part; the
+// key is useless until the other host trusts it, and that is a step no button here can take —
+// so the public key is put on screen, in something selectable, rather than described.
+//
+// A readonly textarea rather than a copy button: the clipboard API needs a secure context and
+// Unraid is routinely reached over plain http on the LAN, where it is simply undefined.
+function vvSshGenerate(btn) {
+ btn.disabled = true; btn.textContent = '⟳ Generating…';
+ const out = document.getElementById('vv-ssh-out');
+ if (out) { out.textContent = ''; out.style.color = ''; }
+ fetch('/plugins/varaverk/api/setup.php', {
+ method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
+ body: new URLSearchParams({action: 'ssh_generate'})
+ }).then(r => r.json()).then(d => {
+ if (d.ok && d.pubkey) {
+ btn.textContent = '✓ Created';
+ if (out) {
+ out.style.color = '#8a8a8a';
+ // Built as nodes, not innerHTML — the key and its comment come off the filesystem and
+ // land in the page either way, so there is no reason to send them through a parser.
+ const p = document.createElement('div');
+ p.textContent = 'Key created. It is not trusted until this public key is on the other '
+ + 'host — append it to /root/.ssh/authorized_keys there:';
+ p.style.cssText = 'margin-bottom:6px;line-height:1.5;';
+ const ta = document.createElement('textarea');
+ ta.readOnly = true; ta.value = d.pubkey; ta.rows = 3;
+ ta.style.cssText = 'width:100%;font-family:monospace;font-size:10px;background:#111;'
+ + 'color:#8a8a8a;border:1px solid #2a2a2a;border-radius:3px;padding:6px;';
+ ta.onclick = () => ta.select();
+ out.appendChild(p); out.appendChild(ta);
+ }
+ setTimeout(vvLoadChecklist, 800);
+ } else {
+ if (out) { out.style.color = '#ef5350'; out.textContent = d.error || 'Key generation failed'; }
+ btn.disabled = false; btn.textContent = 'Retry';
+ }
+ }).catch(() => { btn.disabled = false; btn.textContent = 'Retry'; });
+}
+
function vvRunPopulateBtn(btn) {
btn.disabled = true; btn.textContent = '…';
fetch('/plugins/varaverk/api/setup.php', {