API keys: Varaverk_HOST1/HOST2 named keys, cross-host SSH setup, keys in master.conf for direct GraphQL access
This commit is contained in:
@@ -95,35 +95,56 @@ if ($action === 'detect' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Unraid API key status ─────────────────────────────────────────────────────
|
||||
// ── Unraid API key status — all hosts ────────────────────────────────────────
|
||||
if ($action === 'api_status') {
|
||||
require_once dirname(__DIR__) . '/include/unraid_api.php';
|
||||
$data = vv_api_data();
|
||||
$status = vv_api_get_status();
|
||||
$vars = vv_conf_vars();
|
||||
$myHost = vv_detect_host();
|
||||
$keyVar = strtoupper($myHost) . '_UNRAID_API_KEY';
|
||||
$key = $vars[$keyVar] ?? '';
|
||||
$localStatus = vv_api_get_status();
|
||||
$vars = vv_conf_vars();
|
||||
$myHost = vv_detect_host();
|
||||
$myId = strtoupper($myHost);
|
||||
|
||||
// Collect status for every configured host
|
||||
$hosts = [];
|
||||
foreach ($vars as $k => $v) {
|
||||
if (!preg_match('/^HOST(\d+)$/', $k, $m) || !$v) continue;
|
||||
$id = 'HOST' . $m[1];
|
||||
$keyVar = $id . '_UNRAID_API_KEY';
|
||||
$key = $vars[$keyVar] ?? '';
|
||||
$isLocal = ($id === $myId);
|
||||
$hosts[] = [
|
||||
'host_id' => $id,
|
||||
'hostname' => $v,
|
||||
'is_local' => $isLocal,
|
||||
'key_var' => $keyVar,
|
||||
'key_present' => !empty($key),
|
||||
'key_preview' => $key ? substr($key, 0, 8) . '...' . substr($key, -4) : null,
|
||||
'api_ok' => $isLocal
|
||||
? (!$localStatus['key_missing'] && $localStatus['available'])
|
||||
: !empty($key),
|
||||
];
|
||||
}
|
||||
usort($hosts, fn($a,$b) => strcmp($a['host_id'], $b['host_id']));
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'key_present' => !empty($key),
|
||||
'key_preview' => $key ? substr($key, 0, 8) . '...' . substr($key, -4) : null,
|
||||
'api_ok' => !empty($key) && !$status['key_missing'] && $status['available'],
|
||||
'key_missing' => $status['key_missing'],
|
||||
'fallbacks' => $status['fallbacks'],
|
||||
'ok' => true,
|
||||
'my_id' => $myId,
|
||||
'hosts' => $hosts,
|
||||
'fallbacks' => $localStatus['fallbacks'],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Renew Unraid API key ──────────────────────────────────────────────────────
|
||||
if ($action === 'renew_apikey' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// ── Setup/renew API keys (local + all partners via SSH) ───────────────────────
|
||||
if ($action === 'setup_apikeys' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$script = SCRIPTS_DIR . '/unRAID_Essentials/unraid_api_key_renew.sh';
|
||||
if (!file_exists($script)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'unraid_api_key_renew.sh not found']); exit;
|
||||
}
|
||||
set_time_limit(30);
|
||||
$allHosts = ($_POST['all_hosts'] ?? '0') === '1';
|
||||
$flags = $allHosts ? ' --all-hosts' : '';
|
||||
set_time_limit(60);
|
||||
$output = []; $exit = 0;
|
||||
exec('bash ' . escapeshellarg($script) . ' 2>&1', $output, $exit);
|
||||
exec('bash ' . escapeshellarg($script) . $flags . ' 2>&1', $output, $exit);
|
||||
echo json_encode(['ok' => $exit === 0, 'exit' => $exit, 'output' => implode("\n", $output)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -145,28 +145,23 @@ $_apiPreview = $_apiKey ? substr($_apiKey, 0, 8) . '...' . substr($_apiKey, -4)
|
||||
<!-- Unraid API Key card -->
|
||||
<div class="vv-set-card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<span class="vv-set-hdr" style="margin-bottom:0;">Unraid API Key</span>
|
||||
<span id="vv-api-badge" class="vv-set-badge"><?= $_apiKey ? 'present' : 'missing' ?></span>
|
||||
<span class="vv-set-hdr" style="margin-bottom:0;">Unraid API Keys</span>
|
||||
<span id="vv-api-badge" class="vv-set-badge">…</span>
|
||||
</div>
|
||||
|
||||
<div id="vv-api-info" style="font-size:11px;color:#3a3a3a;margin-bottom:12px;">
|
||||
<?php if ($_apiPreview): ?>
|
||||
Key: <code style="color:#4a6a4a;font-size:10px;"><?= htmlspecialchars($_apiPreview) ?></code>
|
||||
· Var: <code style="color:#3a3a3a;font-size:10px;"><?= htmlspecialchars($_apiKeyVar) ?></code>
|
||||
<?php else: ?>
|
||||
No key found in <?= htmlspecialchars($_myHost) ?>.conf — click Renew to register one.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div id="vv-api-hosts" style="margin-bottom:12px;"></div>
|
||||
|
||||
<div style="font-size:11px;color:#444;margin-bottom:12px;line-height:1.6;">
|
||||
The Unraid API key is ephemeral — it clears on OS updates and service restarts.
|
||||
<code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">unraid_api_key_renew.sh</code>
|
||||
runs at array start to auto-recover, but if the monitor page shows API errors, renew here.
|
||||
Each host registers a key named <code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">Varaverk_HOST1</code>,
|
||||
<code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">Varaverk_HOST2</code>, etc.
|
||||
Keys are stored in <code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">master.conf</code>
|
||||
so every host can query every other host's GraphQL API directly — no SSH needed for remote monitoring.
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<button class="vv-set-btn" onclick="vvApiCheck()" id="vv-api-check-btn">Check status</button>
|
||||
<button class="vv-set-btn primary" onclick="vvApiRenew()" id="vv-api-renew-btn">Renew key</button>
|
||||
<button class="vv-set-btn" onclick="vvApiCheck()" id="vv-api-check-btn">↻ Check status</button>
|
||||
<button class="vv-set-btn" onclick="vvApiRenew(false)" id="vv-api-local-btn">Renew local</button>
|
||||
<button class="vv-set-btn primary" onclick="vvApiRenew(true)" id="vv-api-all-btn">Setup all host keys</button>
|
||||
<span id="vv-api-fb" style="font-size:11px;color:#444;"></span>
|
||||
</div>
|
||||
<pre class="vv-set-out" id="vv-api-out"></pre>
|
||||
@@ -337,46 +332,74 @@ function vvNtfSaveWebhook() {
|
||||
.catch(() => { btn.disabled = false; btn.textContent = 'Save'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
||||
}
|
||||
|
||||
// ── Unraid API Key ────────────────────────────────────────────────────────────
|
||||
// ── Unraid API Keys ───────────────────────────────────────────────────────────
|
||||
function vvApiCheck() {
|
||||
const btn = document.getElementById('vv-api-check-btn');
|
||||
const fb = document.getElementById('vv-api-fb');
|
||||
const badge= document.getElementById('vv-api-badge');
|
||||
btn.disabled = true; btn.textContent = 'Checking…'; fb.textContent = '';
|
||||
const btn = document.getElementById('vv-api-check-btn');
|
||||
const badge = document.getElementById('vv-api-badge');
|
||||
const hosts = document.getElementById('vv-api-hosts');
|
||||
btn.disabled = true; btn.textContent = 'Checking…';
|
||||
fetch('/plugins/varaverk/api/storage.php?action=api_status&_=' + Date.now())
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false; btn.textContent = 'Check status';
|
||||
if (!d.ok) { fb.style.color='#ef5350'; fb.textContent='Request failed'; return; }
|
||||
badge.textContent = d.api_ok ? 'ok' : d.key_missing ? 'missing' : 'error';
|
||||
badge.className = 'vv-set-badge ' + (d.api_ok ? 'internal' : 'flash');
|
||||
fb.style.color = d.api_ok ? '#4caf50' : '#ef5350';
|
||||
fb.textContent = d.api_ok ? 'API responding ✓'
|
||||
: d.key_missing ? 'Key missing from conf — click Renew'
|
||||
: 'API unreachable — ' + (d.fallbacks?.length ? d.fallbacks.join(', ') + ' using fallback' : 'check Unraid API service');
|
||||
btn.disabled = false; btn.textContent = '↻ Check status';
|
||||
if (!d.ok) return;
|
||||
const allOk = d.hosts.every(h => h.api_ok);
|
||||
const anyMissing = d.hosts.some(h => !h.key_present);
|
||||
badge.textContent = allOk ? 'all ok' : anyMissing ? 'keys missing' : 'partial';
|
||||
badge.className = 'vv-set-badge ' + (allOk ? 'internal' : 'flash');
|
||||
|
||||
if (hosts) hosts.innerHTML = d.hosts.map(h => {
|
||||
const local = h.is_local ? ' <span style="color:#2a3a2a;font-size:9px;">local</span>' : '';
|
||||
const keyBit = h.key_present
|
||||
? `<code style="font-size:9px;color:#4a6a4a;">${h.key_preview}</code>`
|
||||
: `<span style="color:#8b2a2a;font-size:10px;">not set</span>`;
|
||||
const dot = h.api_ok ? '#4caf50' : '#555';
|
||||
const name = `Varaverk_${h.host_id}`;
|
||||
return `<div style="display:flex;align-items:baseline;gap:8px;padding:3px 0;border-bottom:1px solid #111;">
|
||||
<span style="width:6px;height:6px;border-radius:50%;background:${dot};flex-shrink:0;margin-top:3px;display:inline-block;"></span>
|
||||
<span style="font-size:11px;color:#666;width:60px;flex-shrink:0;">${h.host_id}${local}</span>
|
||||
<code style="font-size:9px;color:#2a3a2a;flex-shrink:0;">${name}</code>
|
||||
<span style="flex:1;text-align:right;">${keyBit}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
})
|
||||
.catch(() => { btn.disabled = false; btn.textContent = 'Check status'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
||||
.catch(() => { btn.disabled = false; btn.textContent = '↻ Check status'; });
|
||||
}
|
||||
|
||||
function vvApiRenew() {
|
||||
const btn = document.getElementById('vv-api-renew-btn');
|
||||
const out = document.getElementById('vv-api-out');
|
||||
const fb = document.getElementById('vv-api-fb');
|
||||
const badge= document.getElementById('vv-api-badge');
|
||||
btn.disabled = true; btn.textContent = 'Renewing…'; fb.textContent = '';
|
||||
out.textContent = ''; out.style.display = '';
|
||||
const fd = new FormData(); fd.append('action', 'renew_apikey');
|
||||
function vvApiRenew(allHosts) {
|
||||
const localBtn = document.getElementById('vv-api-local-btn');
|
||||
const allBtn = document.getElementById('vv-api-all-btn');
|
||||
const out = document.getElementById('vv-api-out');
|
||||
const fb = document.getElementById('vv-api-fb');
|
||||
const badge = document.getElementById('vv-api-badge');
|
||||
const activeBtn = allHosts ? allBtn : localBtn;
|
||||
|
||||
[localBtn, allBtn].forEach(b => { if(b) b.disabled = true; });
|
||||
activeBtn.textContent = allHosts ? 'Setting up…' : 'Renewing…';
|
||||
fb.textContent = ''; out.textContent = ''; out.style.display = '';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('action', 'setup_apikeys');
|
||||
fd.append('all_hosts', allHosts ? '1' : '0');
|
||||
|
||||
fetch('/plugins/varaverk/api/storage.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false; btn.textContent = 'Renew key';
|
||||
out.textContent = d.output || '(no output)';
|
||||
out.scrollTop = out.scrollHeight;
|
||||
badge.textContent = d.ok ? 'ok' : 'error';
|
||||
badge.className = 'vv-set-badge ' + (d.ok ? 'internal' : 'flash');
|
||||
localBtn.disabled = false; localBtn.textContent = 'Renew local';
|
||||
allBtn.disabled = false; allBtn.textContent = 'Setup all host keys';
|
||||
out.textContent = d.output || '(no output)';
|
||||
out.scrollTop = out.scrollHeight;
|
||||
fb.style.color = d.ok ? '#4caf50' : '#ef5350';
|
||||
fb.textContent = d.ok ? 'Key renewed ✓' : 'Renewal failed — see output';
|
||||
fb.textContent = d.ok ? '✓ Done' : 'Failed — see output';
|
||||
if (d.ok) { badge.textContent = ''; vvApiCheck(); }
|
||||
})
|
||||
.catch(() => { btn.disabled = false; btn.textContent = 'Renew key'; fb.style.color='#ef5350'; fb.textContent='Request failed'; });
|
||||
.catch(() => {
|
||||
localBtn.disabled = false; localBtn.textContent = 'Renew local';
|
||||
allBtn.disabled = false; allBtn.textContent = 'Setup all host keys';
|
||||
fb.style.color = '#ef5350'; fb.textContent = 'Request failed';
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-check on load
|
||||
vvApiCheck();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user