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 = `
`; } else if (item.action === 'pull_master') { act = `
`; + } else if (item.action === 'ssh_setup') { + act = `
` + + `
`; } } return `
@@ -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', {