Add first-run wizard, host.conf template, fix GitHub URL
Plugin/varaverk.plg: URL updated to github.com/FailedProxy/Varaverk Configurations/host.conf.template: Full host conf structure with HOSTN/hostn placeholders. All personal values blank, all sections documented. Covers identity, rsync, docker, fallback, media, monitors, transcodes, arrs, system watchdog, resource manager. Varaverk.page: checks if HOST1 is blank before rendering tabs. If blank → shows setup wizard, returns early (tabs never render). pages/setup.php: first-run wizard UI. Auto-populates hostname from hostname -s. Role selection: primary (HOST1) or partner (HOST2+). Partner slot selector for HOST3+. api/setup.php: handles wizard form POST. Writes HOST1/HOST2 (and HOST3+) into master.conf preserving all other content. Creates host*.conf from template with HOSTN/hostn replaced and SSH key path pre-filled from hostname convention. Never overwrites an existing host*.conf.
This commit is contained in:
@@ -7,6 +7,17 @@ $plugin = 'varaverk';
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
$pluginDir = "$docroot/plugins/$plugin";
|
||||
|
||||
require_once "$pluginDir/include/config.php";
|
||||
|
||||
// First-run check — show setup wizard if HOST1 is not configured
|
||||
$_master = vv_read_conf_raw('master.conf');
|
||||
preg_match('/^\s*HOST1\s*=\s*"([^"]*)"/m', $_master, $_h1m);
|
||||
if (empty(trim($_h1m[1] ?? ''))) {
|
||||
include "$pluginDir/pages/setup.php";
|
||||
return;
|
||||
}
|
||||
unset($_master, $_h1m);
|
||||
|
||||
// Determine active tab
|
||||
$tab = $_GET['tab'] ?? 'monitor';
|
||||
$validTabs = ['monitor', 'scheduler', 'docker', 'watchdog', 'partnership', 'fallback', 'arrs'];
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$host1 = trim($_POST['host1'] ?? '');
|
||||
$host2 = trim($_POST['host2'] ?? '');
|
||||
$mySlot = trim($_POST['my_slot'] ?? 'host1'); // 'host1', 'host2', 'host3', ...
|
||||
$myHostname = trim($_POST['my_hostname'] ?? '');
|
||||
|
||||
if (empty($host1)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'HOST1 hostname is required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate slot — must be host\d+
|
||||
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;
|
||||
}
|
||||
|
||||
// Replace HOST1 and HOST2 lines (preserving indentation)
|
||||
$master = preg_replace(
|
||||
'/^(\s*HOST1\s*=\s*).*$/m',
|
||||
'${1}"' . addslashes($host1) . '"',
|
||||
$master
|
||||
);
|
||||
$master = preg_replace(
|
||||
'/^(\s*HOST2\s*=\s*).*$/m',
|
||||
'${1}"' . addslashes($host2) . '"',
|
||||
$master
|
||||
);
|
||||
|
||||
// For partner slots beyond HOST2 — ensure the slot line exists in master.conf
|
||||
$slotNum = (int) preg_replace('/\D/', '', $mySlot);
|
||||
if ($slotNum > 2 && !empty($myHostname)) {
|
||||
$hostKey = 'HOST' . $slotNum;
|
||||
if (!preg_match('/^\s*' . $hostKey . '\s*=/m', $master)) {
|
||||
// Append after HOST2 line
|
||||
$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); // HOST1, HOST2, HOST3 ...
|
||||
$hostIdLow = strtolower($mySlot); // host1, host2, host3 ...
|
||||
$confFile = $hostIdLow . '.conf';
|
||||
|
||||
// Only create if it doesn't already exist — never overwrite an existing conf
|
||||
if (!file_exists(CONF_DIR . '/' . $confFile)) {
|
||||
$template = @file_get_contents(CONF_DIR . '/host.conf.template');
|
||||
if ($template === false) {
|
||||
$template = @file_get_contents(SCRIPTS_DIR . '/Configurations/host.conf.template');
|
||||
}
|
||||
if ($template !== false) {
|
||||
// Derive SSH key path from hostname convention
|
||||
// unRAID-MyServer → /root/.ssh/myserver_rsync_automation
|
||||
$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);
|
||||
// Pre-fill the SSH key path
|
||||
$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;
|
||||
}
|
||||
}
|
||||
// Template missing is non-fatal — user can create the conf manually
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => true, 'host_id' => $hostId, 'conf_file' => $confFile]);
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
// First-run setup wizard — shown when HOST1 is blank in master.conf.
|
||||
$detectedHostname = trim(shell_exec('hostname -s') ?: '');
|
||||
?>
|
||||
<link rel="stylesheet" href="/plugins/varaverk/css/varaverk.css">
|
||||
|
||||
<style>
|
||||
#vv-setup {
|
||||
max-width: 560px;
|
||||
margin: 48px auto 0;
|
||||
background: #141414;
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 6px;
|
||||
padding: 36px 40px 40px;
|
||||
font-family: monospace;
|
||||
color: #ccc;
|
||||
}
|
||||
#vv-setup h1 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 18px;
|
||||
color: #e0e0e0;
|
||||
font-weight: normal;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
#vv-setup .vv-setup-sub {
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
#vv-setup .vv-setup-field {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#vv-setup label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
#vv-setup input[type=text] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #0d0d0d;
|
||||
border: 1px solid #333;
|
||||
color: #ddd;
|
||||
padding: 7px 10px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
#vv-setup input[type=text]:focus {
|
||||
outline: none;
|
||||
border-color: #555;
|
||||
}
|
||||
#vv-setup .vv-setup-hint {
|
||||
font-size: 11px;
|
||||
color: #555;
|
||||
margin-top: 5px;
|
||||
}
|
||||
#vv-setup .vv-setup-role {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
#vv-setup .vv-setup-role-btn {
|
||||
flex: 1;
|
||||
padding: 10px 0;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px;
|
||||
color: #888;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
transition: border-color .15s, color .15s;
|
||||
}
|
||||
#vv-setup .vv-setup-role-btn.active {
|
||||
border-color: #555;
|
||||
color: #ccc;
|
||||
background: #222;
|
||||
}
|
||||
#vv-setup .vv-setup-conditional {
|
||||
display: none;
|
||||
}
|
||||
#vv-setup .vv-setup-conditional.visible {
|
||||
display: block;
|
||||
}
|
||||
#vv-setup .vv-setup-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #222;
|
||||
margin: 24px 0;
|
||||
}
|
||||
#vv-setup-btn {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #444;
|
||||
color: #ccc;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
letter-spacing: .03em;
|
||||
}
|
||||
#vv-setup-btn:hover { border-color: #666; color: #eee; }
|
||||
#vv-setup-btn:disabled { opacity: .45; cursor: default; }
|
||||
#vv-setup-status {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
min-height: 16px;
|
||||
}
|
||||
#vv-setup-status.ok { color: #4a8; }
|
||||
#vv-setup-status.err { color: #a44; }
|
||||
</style>
|
||||
|
||||
<div id="vv-setup">
|
||||
<h1>⬡ Varaverk — First Run</h1>
|
||||
<div class="vv-setup-sub">Set up your server identity before the plugin can start.</div>
|
||||
|
||||
<div class="vv-setup-field">
|
||||
<label>This server's hostname</label>
|
||||
<input type="text" id="vv-hostname" value="<?= htmlspecialchars($detectedHostname) ?>"
|
||||
placeholder="unRAID-MyServer" autocomplete="off" spellcheck="false">
|
||||
<div class="vv-setup-hint">Must match Settings → Identification exactly (case-sensitive)</div>
|
||||
</div>
|
||||
|
||||
<hr class="vv-setup-divider">
|
||||
|
||||
<label style="margin-bottom:10px;display:block;">Server role</label>
|
||||
<div class="vv-setup-role">
|
||||
<div class="vv-setup-role-btn active" id="vv-role-primary" onclick="vvSetRole('primary')">
|
||||
Primary<br><span style="color:#555;font-size:10px;">HOST1 · first to be set up</span>
|
||||
</div>
|
||||
<div class="vv-setup-role-btn" id="vv-role-partner" onclick="vvSetRole('partner')">
|
||||
Partner<br><span style="color:#555;font-size:10px;">HOST2+ · joining an existing primary</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-setup-conditional" id="vv-cond-primary">
|
||||
<div class="vv-setup-field">
|
||||
<label>Partner's hostname <span style="color:#444">(optional — can fill in later)</span></label>
|
||||
<input type="text" id="vv-partner-hostname" value=""
|
||||
placeholder="unRAID-PartnerServer" autocomplete="off" spellcheck="false">
|
||||
<div class="vv-setup-hint">Leave blank if setting up standalone or partner isn't ready yet</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-setup-conditional" id="vv-cond-partner">
|
||||
<div class="vv-setup-field">
|
||||
<label>Primary server's hostname <span style="color:#a44">*required</span></label>
|
||||
<input type="text" id="vv-primary-hostname" value=""
|
||||
placeholder="unRAID-PrimaryServer" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="vv-setup-field">
|
||||
<label>Your slot</label>
|
||||
<select id="vv-partner-slot" style="width:100%;box-sizing:border-box;background:#0d0d0d;
|
||||
border:1px solid #333;color:#ddd;padding:7px 10px;border-radius:3px;
|
||||
font-family:monospace;font-size:13px;">
|
||||
<option value="host2">HOST2</option>
|
||||
<option value="host3">HOST3</option>
|
||||
<option value="host4">HOST4</option>
|
||||
</select>
|
||||
<div class="vv-setup-hint">Which slot are you? Ask your primary server admin if unsure — usually HOST2.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="vv-setup-btn" onclick="vvDoSetup()">Save and continue →</button>
|
||||
<div id="vv-setup-status"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let vvRole = 'primary';
|
||||
|
||||
function vvSetRole(role) {
|
||||
vvRole = role;
|
||||
document.getElementById('vv-role-primary').classList.toggle('active', role === 'primary');
|
||||
document.getElementById('vv-role-partner').classList.toggle('active', role === 'partner');
|
||||
document.getElementById('vv-cond-primary').classList.toggle('visible', role === 'primary');
|
||||
document.getElementById('vv-cond-partner').classList.toggle('visible', role === 'partner');
|
||||
}
|
||||
|
||||
function vvDoSetup() {
|
||||
const hostname = document.getElementById('vv-hostname').value.trim();
|
||||
const status = document.getElementById('vv-setup-status');
|
||||
const btn = document.getElementById('vv-setup-btn');
|
||||
|
||||
if (!hostname) {
|
||||
status.textContent = '✗ Hostname is required';
|
||||
status.className = 'err';
|
||||
return;
|
||||
}
|
||||
|
||||
let host1 = '', host2 = '', mySlot = 'host1';
|
||||
|
||||
if (vvRole === 'primary') {
|
||||
host1 = hostname;
|
||||
host2 = document.getElementById('vv-partner-hostname').value.trim();
|
||||
mySlot = 'host1';
|
||||
} else {
|
||||
const primary = document.getElementById('vv-primary-hostname').value.trim();
|
||||
if (!primary) {
|
||||
status.textContent = '✗ Primary hostname is required for partner setup';
|
||||
status.className = 'err';
|
||||
return;
|
||||
}
|
||||
mySlot = document.getElementById('vv-partner-slot').value;
|
||||
host1 = primary;
|
||||
// Place this server in the correct slot
|
||||
if (mySlot === 'host2') host2 = hostname;
|
||||
// host3/host4 handled server-side
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Saving…';
|
||||
status.textContent = '';
|
||||
status.className = '';
|
||||
|
||||
const params = new URLSearchParams({
|
||||
csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '',
|
||||
host1, host2, my_slot: mySlot,
|
||||
my_hostname: hostname,
|
||||
});
|
||||
|
||||
fetch('/plugins/varaverk/api/setup.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: params,
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (d.ok) {
|
||||
status.textContent = '✓ Saved — loading plugin…';
|
||||
status.className = 'ok';
|
||||
setTimeout(() => { window.location.href = '?tab=monitor'; }, 800);
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Save and continue →';
|
||||
status.textContent = '✗ ' + (d.error ?? 'Unknown error');
|
||||
status.className = 'err';
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Save and continue →';
|
||||
status.textContent = '✗ Request failed';
|
||||
status.className = 'err';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user