Add offline license capture to setup
This commit is contained in:
1 parent
9ce9efc82e
commit
f407a9331e
4 files changed
+198
-29
No files matched your search
@@ -91,13 +91,35 @@ def apply_static_ip(iface, ip, prefix, gateway, dns):
|
||||
subprocess.run(["netplan", "apply"], capture_output=True)
|
||||
time.sleep(3)
|
||||
|
||||
def run_install(tier, skip_tools):
|
||||
def write_license_file(license_data, tier):
|
||||
os.makedirs("/opt/cezen", exist_ok=True)
|
||||
payload = {
|
||||
"schema": "cezen.license.v1",
|
||||
"customer_name": (license_data.get("customer_name") or "").strip(),
|
||||
"customer_id": (license_data.get("customer_id") or "").strip(),
|
||||
"contact_email": (license_data.get("contact_email") or "").strip(),
|
||||
"license_key": (license_data.get("license_key") or "").strip(),
|
||||
"tier": tier,
|
||||
"support_until": (license_data.get("support_until") or "").strip(),
|
||||
"install_type": (license_data.get("install_type") or "customer").strip(),
|
||||
"issued_by": "Cezen",
|
||||
"captured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
"signature_status": "unsigned-field-install",
|
||||
}
|
||||
with open("/opt/cezen/license.json", "w") as f:
|
||||
json.dump(payload, f, indent=2)
|
||||
subprocess.run(["chown", "root:cezen", "/opt/cezen/license.json"], check=False)
|
||||
os.chmod("/opt/cezen/license.json", 0o640)
|
||||
return payload
|
||||
|
||||
def run_install(tier, skip_tools, license_data=None):
|
||||
global install_status
|
||||
install_status = {"running": True, "done": False, "error": None}
|
||||
try:
|
||||
# Write config so phase 2 (post-reboot) knows what to skip
|
||||
os.makedirs("/opt/cezen", exist_ok=True)
|
||||
skip_str = ",".join(skip_tools) if skip_tools else ""
|
||||
write_license_file(license_data or {}, tier)
|
||||
with open("/opt/cezen/install.conf", "w") as f:
|
||||
f.write(f"TIER={tier}\nSKIP_ROLES={skip_str}\n")
|
||||
|
||||
@@ -258,9 +280,10 @@ HTML = r"""<!DOCTYPE html>
|
||||
|
||||
<div class="steps" id="step-bar">
|
||||
<div class="step active" id="sbar-1">1 · Network</div>
|
||||
<div class="step" id="sbar-2">2 · Select Tier</div>
|
||||
<div class="step" id="sbar-3">3 · AI Tools</div>
|
||||
<div class="step" id="sbar-4">4 · Install</div>
|
||||
<div class="step" id="sbar-2">2 · License</div>
|
||||
<div class="step" id="sbar-3">3 · Select Tier</div>
|
||||
<div class="step" id="sbar-4">4 · AI Tools</div>
|
||||
<div class="step" id="sbar-5">5 · Install</div>
|
||||
</div>
|
||||
|
||||
<!-- ── STEP 1: NETWORK ── -->
|
||||
@@ -311,8 +334,40 @@ HTML = r"""<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── STEP 2: TIER ── -->
|
||||
<!-- ── STEP 2: LICENSE ── -->
|
||||
<div id="step-2" class="hidden">
|
||||
<div class="card">
|
||||
<h2>License & Customer Details</h2>
|
||||
<p class="desc">Enter the customer/license details for this installation. Leave the license key blank for evaluation or PSU field staging.</p>
|
||||
<div class="form-group">
|
||||
<label>Customer / Organisation Name</label>
|
||||
<input type="text" id="lic-customer-name" placeholder="e.g. Bharat Electronics Limited">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Customer ID / Project Code</label>
|
||||
<input type="text" id="lic-customer-id" placeholder="e.g. BEL-PSU-2026">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Contact Email</label>
|
||||
<input type="email" id="lic-contact-email" placeholder="admin@example.gov.in">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>License Key / Activation Code</label>
|
||||
<input type="text" id="lic-license-key" placeholder="Optional for offline field install">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Support Until</label>
|
||||
<input type="date" id="lic-support-until">
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-secondary" onclick="goStep(1)">← Back</button>
|
||||
<button class="btn btn-primary" onclick="goStep(3)">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── STEP 3: TIER ── -->
|
||||
<div id="step-3" class="hidden">
|
||||
<div class="card">
|
||||
<h2>Select AI Package Tier</h2>
|
||||
<p class="desc">Choose the tier that matches your GPU hardware.</p>
|
||||
@@ -340,13 +395,13 @@ HTML = r"""<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-secondary" onclick="goStep(1)">← Back</button>
|
||||
<button class="btn btn-primary" onclick="goStep(3)">Next →</button>
|
||||
<button class="btn btn-secondary" onclick="goStep(2)">← Back</button>
|
||||
<button class="btn btn-primary" onclick="goStep(4)">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── STEP 3: TOOLS ── -->
|
||||
<div id="step-3" class="hidden">
|
||||
<!-- ── STEP 4: TOOLS ── -->
|
||||
<div id="step-4" class="hidden">
|
||||
<div class="card">
|
||||
<h2>Select AI Tools</h2>
|
||||
<p class="desc">Toggle the components you want installed. Recommended defaults are pre-selected.</p>
|
||||
@@ -355,13 +410,13 @@ HTML = r"""<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-secondary" onclick="goStep(2)">← Back</button>
|
||||
<button class="btn btn-primary" onclick="goStep(4)">Review & Install →</button>
|
||||
<button class="btn btn-secondary" onclick="goStep(3)">← Back</button>
|
||||
<button class="btn btn-primary" onclick="goStep(5)">Review & Install →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── STEP 4: CONFIRM + INSTALL ── -->
|
||||
<div id="step-4" class="hidden">
|
||||
<!-- ── STEP 5: CONFIRM + INSTALL ── -->
|
||||
<div id="step-5" class="hidden">
|
||||
<div class="card" id="summary-card">
|
||||
<h2>Review Configuration</h2>
|
||||
<p class="desc">Confirm your settings before installation begins.</p>
|
||||
@@ -395,7 +450,7 @@ HTML = r"""<!DOCTYPE html>
|
||||
</div>
|
||||
|
||||
<div class="btn-row" id="install-btn-row">
|
||||
<button class="btn btn-secondary" onclick="goStep(3)">← Back</button>
|
||||
<button class="btn btn-secondary" onclick="goStep(4)">← Back</button>
|
||||
<button class="btn btn-primary" id="install-btn" onclick="startInstall()">🚀 Start Installation</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -428,15 +483,15 @@ window.onload = () => {
|
||||
|
||||
// ── Navigation ─────────────────────────────────────────────
|
||||
function goStep(n) {
|
||||
if (n === 2 && !selectedTier) { alert('Please select a tier.'); return; }
|
||||
[1,2,3,4].forEach(i => {
|
||||
if (n === 4 && !selectedTier) { alert('Please select a tier.'); return; }
|
||||
[1,2,3,4,5].forEach(i => {
|
||||
document.getElementById('step-'+i).classList.toggle('hidden', i !== n);
|
||||
const bar = document.getElementById('sbar-'+i);
|
||||
bar.classList.remove('active','done');
|
||||
if (i === n) bar.classList.add('active');
|
||||
if (i < n) bar.classList.add('done');
|
||||
});
|
||||
if (n === 4) renderSummary();
|
||||
if (n === 5) renderSummary();
|
||||
if (n === 2 && netMode === 'static') applyStaticIP();
|
||||
}
|
||||
|
||||
@@ -495,15 +550,30 @@ function toggleTool(k) {
|
||||
document.getElementById('tool-'+k).classList.toggle('selected', tools[k].on);
|
||||
}
|
||||
|
||||
// ── License ────────────────────────────────────────────────
|
||||
function collectLicense() {
|
||||
return {
|
||||
customer_name: document.getElementById('lic-customer-name').value.trim(),
|
||||
customer_id: document.getElementById('lic-customer-id').value.trim(),
|
||||
contact_email: document.getElementById('lic-contact-email').value.trim(),
|
||||
license_key: document.getElementById('lic-license-key').value.trim(),
|
||||
support_until: document.getElementById('lic-support-until').value.trim(),
|
||||
install_type: document.getElementById('lic-license-key').value.trim() ? 'licensed' : 'field-staging'
|
||||
};
|
||||
}
|
||||
|
||||
// ── Summary ────────────────────────────────────────────────
|
||||
function renderSummary() {
|
||||
const ip = netMode === 'dhcp'
|
||||
? document.getElementById('current-ip').textContent + ' (DHCP)'
|
||||
: document.getElementById('ip-addr').value + '/' + document.getElementById('ip-prefix').value;
|
||||
const license = collectLicense();
|
||||
const onTools = Object.entries(tools).filter(([,v])=>v.on).map(([,v])=>v.name).join(', ');
|
||||
const offTools = Object.entries(tools).filter(([,v])=>!v.on);
|
||||
document.getElementById('summary-rows').innerHTML = `
|
||||
<div class="summary-row"><span class="key">Network</span><span class="val">${ip}</span></div>
|
||||
<div class="summary-row"><span class="key">Customer</span><span class="val">${license.customer_name || 'Not entered'}</span></div>
|
||||
<div class="summary-row"><span class="key">License</span><span class="val">${license.license_key ? 'Provided' : 'Field staging / evaluation'}</span></div>
|
||||
<div class="summary-row"><span class="key">Tier</span><span class="val">${selectedTier.charAt(0).toUpperCase()+selectedTier.slice(1)}</span></div>
|
||||
<div class="summary-row"><span class="key">Tools</span><span class="val" style="font-size:13px;text-align:right;max-width:60%">${onTools}</span></div>
|
||||
${offTools.length ? `<div class="summary-row"><span class="key">Skipped</span><span class="val" style="color:var(--muted);font-size:13px">${offTools.map(([,v])=>v.name).join(', ')}</span></div>` : ''}
|
||||
@@ -520,7 +590,7 @@ function startInstall() {
|
||||
fetch('/api/install', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ tier: selectedTier, skip_tools: skip })
|
||||
body: JSON.stringify({ tier: selectedTier, skip_tools: skip, license: collectLicense() })
|
||||
});
|
||||
|
||||
streamLog();
|
||||
@@ -714,8 +784,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
global install_proc
|
||||
tier = body.get("tier", "basic")
|
||||
skip = body.get("skip_tools", [])
|
||||
license_data = body.get("license", {})
|
||||
if not install_status["running"]:
|
||||
t = threading.Thread(target=run_install, args=(tier, skip), daemon=True)
|
||||
t = threading.Thread(target=run_install, args=(tier, skip, license_data), daemon=True)
|
||||
t.start()
|
||||
self.send_json({"ok": True})
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user