GL-7 Part 2: install-command generator on the customer page
Replaces the static Option-1/2 Setup Command blocks with an interactive, client-side builder: mode (required radio), cores/memory (required for byo, optional for appliance), vmid, node, acl-storages (quote-wrapped), operator-pubkey-file, preserve-state-from, and --dry-run/--preflight-only/ --skip-provision/--allow-new-leaf checkboxes. genFlags()/genUpdate() assemble a live-updating download-then-run command (never curl|bash) + a local-run variant, enforcing the script's own rules client-side (mode required; byo requires caps → shows a warning + no runnable command; appliance hides the caps requirement; allow-new-leaf shows its leaf-regen warning). Emits ONLY real host-install v1.12.0 flags; the dangerous/operator-only set (--force/--rotate-recovery/--enable-oob/--remove-golden/--uninstall/ --adopt-pool/--rescope-acl) is never offered. Graceful static fallback: the server-rendered Option-1/2 commands keep --customer-id + a --mode placeholder when JS is off. No framework/CDN/network; ScriptVersion (const, in sync with SCRIPT_VERSION) drives the header. Render/structure test covers the control ids, version, fallback, and the excluded-flag absence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -387,27 +387,83 @@
|
||||
<section class="card">
|
||||
<h2>Setup Command</h2>
|
||||
<p class="text-muted" style="margin-bottom: 1rem; font-size: 0.85rem;">
|
||||
Day-0 host bootstrap. Run on a freshly-PVE-installed Proxmox <strong>host</strong> as root
|
||||
(create the customer in the hub first). It enrolls the host, installs + verifies the agent,
|
||||
and provisions the guest; the in-guest controller then pulls its own <code>controller.yaml</code>.
|
||||
The retrieval passphrase is entered at the no-echo prompt — never on the command line.
|
||||
Day-0 host bootstrap for host-install <strong>{{.ScriptVersion}}</strong>. Run on a
|
||||
freshly-PVE-installed Proxmox <strong>host</strong> as root (create the customer in the
|
||||
hub first). It enrolls the host, installs + verifies the agent, and provisions the guest;
|
||||
the in-guest controller then pulls its own <code>controller.yaml</code>. The retrieval
|
||||
passphrase is entered at the no-echo prompt — never on the command line.
|
||||
</p>
|
||||
|
||||
<h3>Option 1: Online install (recommended)</h3>
|
||||
<!-- Generator controls (client-side only; nothing is submitted) -->
|
||||
<div class="gen-controls" id="gen-controls" data-customer-id="{{.CustomerID}}">
|
||||
<div class="form-group">
|
||||
<label>Mode <span style="color:var(--red,#E5534B)">*</span></label>
|
||||
<div class="gen-radios">
|
||||
<label class="gen-radio"><input type="radio" name="gen-mode" value="appliance" onchange="genUpdate()"> appliance <span class="form-hint">(Felhom-owned box)</span></label>
|
||||
<label class="gen-radio"><input type="radio" name="gen-mode" value="byo" onchange="genUpdate()"> byo <span class="form-hint">(a host you do not own)</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid" style="margin-top:0.75rem;">
|
||||
<div class="form-group" id="gen-cores-grp">
|
||||
<label>Cores <span class="gen-req" style="display:none;color:var(--red,#E5534B)">*</span></label>
|
||||
<input type="number" id="gen-cores" min="1" placeholder="e.g. 12" oninput="genUpdate()">
|
||||
<span class="form-hint">byo: required — a conservative slice of the host</span>
|
||||
</div>
|
||||
<div class="form-group" id="gen-memory-grp">
|
||||
<label>Memory (MiB) <span class="gen-req" style="display:none;color:var(--red,#E5534B)">*</span></label>
|
||||
<input type="number" id="gen-memory" min="256" placeholder="e.g. 32768 (= 32 GB)" oninput="genUpdate()">
|
||||
<span class="form-hint">byo: required — MiB (32 GB → 32768)</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>VMID <span class="form-hint">(optional)</span></label>
|
||||
<input type="number" id="gen-vmid" min="100" placeholder="e.g. 9201" oninput="genUpdate()">
|
||||
<span class="form-hint">pick from <code>pct list</code>+<code>qm list</code>; blank = default/auto</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Node <span class="form-hint">(optional)</span></label>
|
||||
<input type="text" id="gen-node" placeholder="e.g. pve1" oninput="genUpdate()">
|
||||
<span class="form-hint">required only on a multi-node cluster</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>ACL storages <span class="form-hint">(optional)</span></label>
|
||||
<input type="text" id="gen-acl" placeholder='e.g. local local-lvm' oninput="genUpdate()">
|
||||
<span class="form-hint">grant the token write access on exactly these storages</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Operator pubkey file <span class="form-hint">(optional)</span></label>
|
||||
<input type="text" id="gen-pubkey" placeholder="/path/to/operator-keys" oninput="genUpdate()">
|
||||
<span class="form-hint">arm self-update from day-0</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Preserve state from <span class="form-hint">(optional)</span></label>
|
||||
<input type="text" id="gen-preserve" placeholder="/var/lib/felhom-agent.old" oninput="genUpdate()">
|
||||
<span class="form-hint">reinstall keeping the leaf pin stable</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gen-checks">
|
||||
<label class="gen-check"><input type="checkbox" id="gen-dry" onchange="genUpdate()"> <code>--dry-run</code> <span class="form-hint">print, don't execute</span></label>
|
||||
<label class="gen-check"><input type="checkbox" id="gen-preflight" onchange="genUpdate()"> <code>--preflight-only</code> <span class="form-hint">checks + verdict, no install</span></label>
|
||||
<label class="gen-check"><input type="checkbox" id="gen-skip" onchange="genUpdate()"> <code>--skip-provision</code> <span class="form-hint">agent only, no guest</span></label>
|
||||
<label class="gen-check"><input type="checkbox" id="gen-leaf" onchange="genUpdate()"> <code>--allow-new-leaf</code> <span class="form-hint" id="gen-leaf-warn"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="gen-msg" id="gen-msg" style="display:none;"></p>
|
||||
|
||||
<h3 style="margin-top:1rem;">Option 1: Online install (recommended)</h3>
|
||||
<p class="text-muted" style="margin: 0 0 0.4rem; font-size: 0.8rem;">
|
||||
Fetches the script over TLS from felhom.eu, then runs it. Download-then-run (not
|
||||
<code>curl | sudo bash</code>) so you can inspect the script before executing it — the
|
||||
right default for a sovereignty product. The passphrase is entered at the no-echo prompt.
|
||||
Download-then-run (not <code>curl | sudo bash</code>) so you can inspect the script first —
|
||||
the right default for a sovereignty product. The passphrase is entered at the no-echo prompt.
|
||||
</p>
|
||||
<div class="credential-box">
|
||||
<code id="cmd-online">curl -fsSL https://felhom.eu/scripts/felhom-host-install.sh -o felhom-host-install.sh \
|
||||
&& sudo bash felhom-host-install.sh --customer-id {{.CustomerID}}</code>
|
||||
&& sudo bash felhom-host-install.sh --customer-id {{.CustomerID}} --mode <appliance|byo></code>
|
||||
<button type="button" class="copy-btn" onclick="copyText('cmd-online')" title="Copy">⎘</button>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 1rem;">Option 2: Local install (script already on host)</h3>
|
||||
<div class="credential-box">
|
||||
<code id="cmd-setup">sudo ./felhom-host-install.sh --customer-id {{.CustomerID}}</code>
|
||||
<code id="cmd-setup">sudo ./felhom-host-install.sh --customer-id {{.CustomerID}} --mode <appliance|byo></code>
|
||||
<button type="button" class="copy-btn" onclick="copyText('cmd-setup')" title="Copy">⎘</button>
|
||||
</div>
|
||||
|
||||
@@ -722,6 +778,75 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── Install-command generator (GL-7) — client-side only; assembles a command from the controls
|
||||
// and only ever emits real host-install flags. Nothing is submitted.
|
||||
function genFlags() {
|
||||
// returns {flags: "<assembled>", err: "<message or empty>"}
|
||||
var mode = (document.querySelector('input[name="gen-mode"]:checked') || {}).value || '';
|
||||
if (!mode) return { flags: '', err: 'Pick a mode (appliance or byo) to generate the command.' };
|
||||
var f = ['--mode ' + mode];
|
||||
var cores = (document.getElementById('gen-cores').value || '').trim();
|
||||
var memory = (document.getElementById('gen-memory').value || '').trim();
|
||||
var isInt = function (v) { return v !== '' && /^[0-9]+$/.test(v) && parseInt(v, 10) > 0; };
|
||||
if (mode === 'byo') {
|
||||
if (!isInt(cores) || !isInt(memory)) {
|
||||
return { flags: '', err: 'byo mode requires --cores and --memory (noisy-neighbor caps on a host you do not own).' };
|
||||
}
|
||||
f.push('--cores ' + cores, '--memory ' + memory);
|
||||
} else {
|
||||
// appliance: caps optional; emit only if both are valid positive integers
|
||||
if (isInt(cores)) f.push('--cores ' + cores);
|
||||
if (isInt(memory)) f.push('--memory ' + memory);
|
||||
}
|
||||
var vmid = (document.getElementById('gen-vmid').value || '').trim();
|
||||
if (vmid !== '' && isInt(vmid)) f.push('--vmid ' + vmid);
|
||||
var node = (document.getElementById('gen-node').value || '').trim();
|
||||
if (node) f.push('--node ' + node);
|
||||
var acl = (document.getElementById('gen-acl').value || '').trim().replace(/\s+/g, ' ');
|
||||
if (acl) f.push('--acl-storages "' + acl + '"');
|
||||
var pub = (document.getElementById('gen-pubkey').value || '').trim();
|
||||
if (pub) f.push('--operator-pubkey-file ' + pub);
|
||||
var pre = (document.getElementById('gen-preserve').value || '').trim();
|
||||
if (pre) f.push('--preserve-state-from ' + pre);
|
||||
if (document.getElementById('gen-skip').checked) f.push('--skip-provision');
|
||||
if (document.getElementById('gen-dry').checked) f.push('--dry-run');
|
||||
if (document.getElementById('gen-preflight').checked) f.push('--preflight-only');
|
||||
if (document.getElementById('gen-leaf').checked) f.push('--allow-new-leaf');
|
||||
return { flags: f.join(' '), err: '' };
|
||||
}
|
||||
|
||||
function genUpdate() {
|
||||
var ctrl = document.getElementById('gen-controls');
|
||||
if (!ctrl) return;
|
||||
var cid = ctrl.getAttribute('data-customer-id') || '';
|
||||
var mode = (document.querySelector('input[name="gen-mode"]:checked') || {}).value || '';
|
||||
// byo requires caps → show the required markers only in byo
|
||||
var reqOn = (mode === 'byo');
|
||||
document.querySelectorAll('.gen-req').forEach(function (e) { e.style.display = reqOn ? 'inline' : 'none'; });
|
||||
// allow-new-leaf inline warning
|
||||
var lw = document.getElementById('gen-leaf-warn');
|
||||
lw.textContent = document.getElementById('gen-leaf').checked
|
||||
? '⚠ regenerates the agent leaf — every provisioned guest must then be re-bootstrapped' : '';
|
||||
|
||||
var r = genFlags();
|
||||
var msg = document.getElementById('gen-msg');
|
||||
var online = document.getElementById('cmd-online');
|
||||
var local = document.getElementById('cmd-setup');
|
||||
var base = 'felhom-host-install.sh --customer-id ' + cid;
|
||||
if (r.err) {
|
||||
msg.textContent = r.err; msg.style.display = 'block';
|
||||
// Do NOT emit a runnable command — show the incomplete shape with a placeholder.
|
||||
online.textContent = 'curl -fsSL https://felhom.eu/scripts/felhom-host-install.sh -o felhom-host-install.sh \\\n && sudo bash ' + base + ' --mode <appliance|byo>';
|
||||
local.textContent = 'sudo ./' + base + ' --mode <appliance|byo>';
|
||||
return;
|
||||
}
|
||||
msg.style.display = 'none';
|
||||
online.textContent = 'curl -fsSL https://felhom.eu/scripts/felhom-host-install.sh -o felhom-host-install.sh \\\n && sudo bash ' + base + ' ' + r.flags;
|
||||
local.textContent = 'sudo ./' + base + ' ' + r.flags;
|
||||
}
|
||||
// initialize on load (also gives JS-enabled users the "pick a mode" prompt state)
|
||||
if (document.getElementById('gen-controls')) { genUpdate(); }
|
||||
|
||||
function disableGeo(customerID) {
|
||||
if (!confirm('Összes geo-korlátozás eltávolítása?\n\nEz közvetlenül törli a Cloudflare WAF szabályokat és értesíti a controllert.')) return;
|
||||
var btn = document.getElementById('btn-geo-disable');
|
||||
|
||||
@@ -535,6 +535,64 @@ code {
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* Install-command generator (GL-7) */
|
||||
.gen-controls {
|
||||
background: var(--bg-0);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.gen-radios {
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.gen-radio, .gen-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-1);
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
}
|
||||
.gen-radio input, .gen-check input {
|
||||
accent-color: var(--blue-bright);
|
||||
cursor: pointer;
|
||||
}
|
||||
.gen-checks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.85rem;
|
||||
padding-top: 0.85rem;
|
||||
border-top: 1px solid var(--line-soft);
|
||||
}
|
||||
.gen-check code, .gen-radio code { color: var(--blue-bright); font-size: 0.8rem; }
|
||||
.gen-controls input[type="number"] {
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: var(--text-1);
|
||||
font-size: 0.9rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
.gen-controls input[type="number"]:focus,
|
||||
.gen-controls input[type="text"]:focus { outline: none; border-color: var(--blue-bright); }
|
||||
.gen-msg {
|
||||
color: #E5534B;
|
||||
font-size: 0.82rem;
|
||||
margin: 0 0 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: rgba(229,83,75,.10);
|
||||
border: 1px solid rgba(229,83,75,.35);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
/* Credentials */
|
||||
.credential-row {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
Reference in New Issue
Block a user