Update installer and runtime setup flow
This commit is contained in:
1 parent
8603fb2bc2
commit
7540da04a8
5 files changed
+385
-54
No files matched your search
@@ -260,6 +260,23 @@ d=json.load(open("/tmp/cezen-license-check.json"))
|
||||
print((d.get("license") or {}).get("status","missing"))
|
||||
PY
|
||||
)
|
||||
# Human-readable label for display only — the raw code above (e.g.
|
||||
# "invalid_signature", "machine_mismatch") is what gets persisted to
|
||||
# install-record.json and is meant for machines/support, not end users.
|
||||
LICENSE_STATUS_LABEL=$(LICENSE_STATUS="$LICENSE_STATUS" python3 - <<'PY'
|
||||
import os
|
||||
labels = {
|
||||
"valid": "Valid",
|
||||
"missing": "No license installed (field-staging mode)",
|
||||
"expired": "Expired — contact support@cezentech.com",
|
||||
"invalid_signature": "Invalid signature — re-upload a valid signed license",
|
||||
"not_yet_valid": "Not yet in its valid date range",
|
||||
"machine_mismatch": "Bound to different hardware — contact support@cezentech.com",
|
||||
}
|
||||
status = os.environ.get("LICENSE_STATUS", "missing")
|
||||
print(labels.get(status, status))
|
||||
PY
|
||||
)
|
||||
LICENSE_ALLOWED_TIER=$(python3 - <<'PY'
|
||||
import json
|
||||
d=json.load(open("/tmp/cezen-license-check.json"))
|
||||
@@ -285,7 +302,7 @@ if [ "$WORKSTATION_MODE" = true ]; then
|
||||
# /opt/cezen/tier and the install-record below is what
|
||||
# actually drives Workstation branding/entitlement.
|
||||
whiptail --title "$TITLE" \
|
||||
--msgbox "\nNexus One AI Workstation\n\nLicense status: ${LICENSE_STATUS}\nHardware recommendation: ${HARDWARE_TIER}\n\nThis image installs the personal-appliance stack (local chat, personal RAG, document intelligence, prompt studio) — it is not a Server tier and will not be offered a Server S/M/L/Max upgrade path." \
|
||||
--msgbox "\nNexus One AI Workstation\n\nLicense status: ${LICENSE_STATUS_LABEL}\nHardware recommendation: ${HARDWARE_TIER}\n\nThis image installs the personal-appliance stack (local chat, personal RAG, document intelligence, prompt studio) — it is not a Server tier and will not be offered a Server S/M/L/Max upgrade path." \
|
||||
$H $W
|
||||
else
|
||||
mapfile -t TIER_MENU < <(python3 - <<'PY'
|
||||
@@ -306,13 +323,13 @@ PY
|
||||
|
||||
if [ "${#TIER_MENU[@]}" -eq 0 ]; then
|
||||
whiptail --title "$TITLE" \
|
||||
--msgbox "\nNo installable tiers are available.\n\nLicense status: ${LICENSE_STATUS}\nHardware recommendation: ${HARDWARE_TIER}\n\nCheck the signed license or contact Cezen support." \
|
||||
--msgbox "\nNo installable tiers are available.\n\nLicense status: ${LICENSE_STATUS_LABEL}\nHardware recommendation: ${HARDWARE_TIER}\n\nCheck the signed license or contact Cezen support." \
|
||||
$H $W
|
||||
exit 1
|
||||
fi
|
||||
|
||||
whiptail --title "$TITLE" \
|
||||
--msgbox "\nLicense status: ${LICENSE_STATUS}\nAllowed tier: ${LICENSE_ALLOWED_TIER}\nHardware recommendation: ${HARDWARE_TIER}\n\nOnly valid tiers will be shown next." \
|
||||
--msgbox "\nLicense status: ${LICENSE_STATUS_LABEL}\nAllowed tier: ${LICENSE_ALLOWED_TIER}\nHardware recommendation: ${HARDWARE_TIER}\n\nOnly valid tiers will be shown next." \
|
||||
$H $W
|
||||
|
||||
TIER=$(whiptail --title "$TITLE" \
|
||||
@@ -357,7 +374,7 @@ TOOLS_DISPLAY=$(echo "$TOOLS" | tr -d '"' | tr ' ' '\n' | sed 's/^/ · /' | tr
|
||||
MY_IP=$(hostname -I | awk '{print $1}')
|
||||
LICENSE_DISPLAY="Field staging / evaluation"
|
||||
if [ -n "$LICENSE_PATH" ]; then
|
||||
LICENSE_DISPLAY="Signed file (${LICENSE_STATUS})"
|
||||
LICENSE_DISPLAY="Signed file (${LICENSE_STATUS_LABEL})"
|
||||
fi
|
||||
|
||||
whiptail --title "$TITLE" \
|
||||
@@ -433,7 +450,7 @@ if bash "$AIPACKAGE_DIR/install.sh" --tier="$TIER" >> "$INSTALL_LOG_FILE" 2>&1;
|
||||
# Mark as configured only after the installer finishes successfully.
|
||||
touch /opt/cezen/.setup-done
|
||||
whiptail --title "$TITLE" \
|
||||
--msgbox "\nInstaller command finished successfully.\n\nPortal:\n http://localhost\n\nFor detailed logs, run:\n sudo tail -f $INSTALL_LOG_FILE" \
|
||||
--msgbox "\nInstaller command finished successfully.\n\nPortal -> http://localhost\nOllama API -> http://localhost:11434\nGrafana -> http://localhost:3000\n\nAdmin login -> admin / Cezen@2024!\n(you will be required to change this on first login)\n\nFor detailed logs, run:\n sudo tail -f $INSTALL_LOG_FILE" \
|
||||
$H $W
|
||||
else
|
||||
whiptail --title "$TITLE" \
|
||||
|
||||
@@ -325,6 +325,7 @@ HTML = r"""<!DOCTYPE html>
|
||||
.alert-info { background: #EFF6FF; color: #1D4ED8; border: 1px solid #BFDBFE; }
|
||||
.alert-success { background: #ECFDF5; color: #065F46; border: 1px solid #A7F3D0; }
|
||||
.alert-error { background: #FEF2F2; color: #991B1B; border: 1px solid #FECACA; }
|
||||
.alert-warning { background: #FFFBEB; color: #92400E; border: 1px solid #FDE68A; }
|
||||
|
||||
.hidden { display: none !important; }
|
||||
#done-screen { text-align: center; padding: 48px 0; }
|
||||
@@ -336,10 +337,12 @@ HTML = r"""<!DOCTYPE html>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="wizard-banner" class="alert hidden" style="position:fixed;top:20px;right:20px;max-width:380px;z-index:1000;box-shadow:0 8px 24px rgba(0,0,0,.12)"></div>
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<div class="logo">CEZEN AI SUITE</div>
|
||||
<div class="sub">Server Setup Wizard</div>
|
||||
<div class="logo">NEXUS ONE AI</div>
|
||||
<div class="sub">Powered by Cezen · Server Setup Wizard</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -448,25 +451,25 @@ HTML = r"""<!DOCTYPE html>
|
||||
<p class="desc">Choose the tier that matches your GPU hardware.</p>
|
||||
<div class="tier-grid">
|
||||
<div class="tier-card" id="tier-starter" onclick="selectTier('starter')">
|
||||
<div class="tier-name">Starter</div>
|
||||
<div class="tier-name">Server S</div>
|
||||
<div class="tier-gpu">1× RTX 5090 / 32GB VRAM</div>
|
||||
<div class="tier-users">Small team deployment</div>
|
||||
<div class="tier-note" id="tier-note-starter"></div>
|
||||
</div>
|
||||
<div class="tier-card" id="tier-basic" onclick="selectTier('basic')">
|
||||
<div class="tier-name">Entry</div>
|
||||
<div class="tier-name">Server M</div>
|
||||
<div class="tier-gpu">1× NVIDIA RTX Pro 6000 (96GB)</div>
|
||||
<div class="tier-users">Up to 20 concurrent users</div>
|
||||
<div class="tier-note" id="tier-note-basic"></div>
|
||||
</div>
|
||||
<div class="tier-card" id="tier-pro" onclick="selectTier('pro')">
|
||||
<div class="tier-name">Pro</div>
|
||||
<div class="tier-name">Server L</div>
|
||||
<div class="tier-gpu">2× RTX 5090 / RTX Pro class</div>
|
||||
<div class="tier-users">Up to 100 concurrent users</div>
|
||||
<div class="tier-note" id="tier-note-pro"></div>
|
||||
</div>
|
||||
<div class="tier-card" id="tier-max" onclick="selectTier('max')">
|
||||
<div class="tier-name">Max</div>
|
||||
<div class="tier-name">Server Max</div>
|
||||
<div class="tier-gpu">4–8× H100/H200/A100 class</div>
|
||||
<div class="tier-users">200+ concurrent users</div>
|
||||
<div class="tier-note" id="tier-note-max"></div>
|
||||
@@ -516,31 +519,64 @@ HTML = r"""<!DOCTYPE html>
|
||||
</div>
|
||||
|
||||
<div id="done-screen" class="hidden">
|
||||
<div class="done-icon">✅</div>
|
||||
<div class="done-icon">✓</div>
|
||||
<h2>Installation Complete!</h2>
|
||||
<p>Your Nexus One AI is ready.</p>
|
||||
<div class="services card" style="margin-top:24px;text-align:left">
|
||||
<div class="summary-row"><span class="key">Portal</span><span class="val badge">http://localhost</span></div>
|
||||
<div class="summary-row"><span class="key">Open WebUI</span><span class="val badge">:3001</span></div>
|
||||
<div class="summary-row"><span class="key">JupyterLab</span><span class="val badge">:8888</span></div>
|
||||
<div class="summary-row"><span class="key">MLflow</span><span class="val badge">:5000</span></div>
|
||||
<div class="summary-row"><span class="key">MinIO</span><span class="val badge">:9000</span></div>
|
||||
<div class="summary-row"><span class="key">Grafana</span><span class="val badge">:3000</span></div>
|
||||
</div>
|
||||
<div class="alert alert-info" style="max-width:420px;margin:20px auto 0;text-align:left">
|
||||
<strong>Admin login:</strong> admin / Cezen@2024!<br>
|
||||
You will be required to change this password on first login.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row" id="install-btn-row">
|
||||
<button class="btn btn-secondary" onclick="goStep(4)">← Back</button>
|
||||
<button class="btn btn-primary" id="install-btn" onclick="startInstall()">🚀 Start Installation</button>
|
||||
<button class="btn btn-primary" id="install-btn" onclick="startInstall()">Start Installation</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /container -->
|
||||
|
||||
<script>
|
||||
// ── Shared banner (replaces native alert() for validation/errors) ──
|
||||
let _bannerTimer = null;
|
||||
function showBanner(msg, type) {
|
||||
type = type || 'error';
|
||||
const el = document.getElementById('wizard-banner');
|
||||
el.className = 'alert alert-' + type;
|
||||
el.textContent = msg;
|
||||
el.classList.remove('hidden');
|
||||
clearTimeout(_bannerTimer);
|
||||
_bannerTimer = setTimeout(() => el.classList.add('hidden'), 6000);
|
||||
}
|
||||
|
||||
// ── State ──────────────────────────────────────────────────
|
||||
let netMode = 'dhcp';
|
||||
let selectedTier = 'basic';
|
||||
let tierPreview = null;
|
||||
const TIER_LABELS = { starter: 'Server S', basic: 'Server M', pro: 'Server L', max: 'Server Max' };
|
||||
function tierLabel(slug) { return TIER_LABELS[slug] || (slug ? slug.charAt(0).toUpperCase()+slug.slice(1) : 'Unknown'); }
|
||||
|
||||
// Raw codes from cezen_license.evaluate_license() (valid/missing/expired/
|
||||
// invalid_signature/not_yet_valid/machine_mismatch) aren't meant for an
|
||||
// end-user installer screen — translate them the same way the portal's
|
||||
// /api/health does, so a customer never sees a bare internal status code.
|
||||
const LICENSE_STATUS_LABELS = {
|
||||
valid: 'Valid',
|
||||
missing: 'No license installed (field-staging mode)',
|
||||
expired: 'Expired — contact support@cezentech.com',
|
||||
invalid_signature: 'Invalid signature — re-upload a valid signed license',
|
||||
not_yet_valid: 'Not yet in its valid date range',
|
||||
machine_mismatch: 'Bound to different hardware — contact support@cezentech.com',
|
||||
};
|
||||
function licenseStatusLabel(status) { return LICENSE_STATUS_LABELS[status] || (status || 'Unknown'); }
|
||||
let tools = {
|
||||
ollama: { name: 'Ollama + Open WebUI', desc: 'LLM inference & chat', icon: '🤖', on: true },
|
||||
jupyterlab: { name: 'JupyterLab', desc: 'Notebook environment', icon: '📓', on: true },
|
||||
@@ -563,7 +599,7 @@ window.onload = () => {
|
||||
|
||||
// ── Navigation ─────────────────────────────────────────────
|
||||
function goStep(n) {
|
||||
if (n === 4 && !selectedTier) { alert('Please select a tier.'); return; }
|
||||
if (n === 4 && !selectedTier) { showBanner('Please select a tier before continuing.', 'error'); return; }
|
||||
[1,2,3,4,5].forEach(i => {
|
||||
document.getElementById('step-'+i).classList.toggle('hidden', i !== n);
|
||||
const bar = document.getElementById('sbar-'+i);
|
||||
@@ -595,11 +631,11 @@ function applyStaticIP() {
|
||||
fetch('/api/network', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) })
|
||||
.then(r=>r.json()).then(d=>{
|
||||
if(!d.ok) {
|
||||
alert('Network config failed: ' + d.error);
|
||||
showBanner('Network config failed: ' + d.error, 'error');
|
||||
return;
|
||||
}
|
||||
if (d.warning) {
|
||||
alert(d.warning);
|
||||
showBanner(d.warning, 'warning');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -673,13 +709,13 @@ async function prepareTierStep() {
|
||||
});
|
||||
renderTierAvailability(res);
|
||||
document.getElementById('license-preview').innerHTML = `
|
||||
<strong>Status:</strong> ${esc(res.license?.status || 'missing')}<br>
|
||||
<strong>Status:</strong> ${esc(licenseStatusLabel(res.license?.status || 'missing'))}<br>
|
||||
<strong>Allowed tier:</strong> ${esc(res.license?.allowed_tier || 'basic')}<br>
|
||||
<strong>Hardware recommendation:</strong> ${esc(res.hardware?.recommended_tier || 'starter')}
|
||||
`;
|
||||
goStep(3);
|
||||
} catch (err) {
|
||||
alert('License preview failed: ' + err.message);
|
||||
showBanner('License preview failed: ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,10 +730,10 @@ function renderSummary() {
|
||||
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_json ? (tierPreview?.license?.status || 'Provided') : 'Field staging / evaluation'}</span></div>
|
||||
<div class="summary-row"><span class="key">Allowed Tier</span><span class="val">${tierPreview?.license?.allowed_tier || 'basic'}</span></div>
|
||||
<div class="summary-row"><span class="key">Hardware Tier</span><span class="val">${tierPreview?.hardware?.recommended_tier || 'starter'}</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">License</span><span class="val">${license.license_json ? licenseStatusLabel(tierPreview?.license?.status || 'missing') : 'Field staging / evaluation'}</span></div>
|
||||
<div class="summary-row"><span class="key">Allowed Tier</span><span class="val">${tierLabel(tierPreview?.license?.allowed_tier || 'basic')}</span></div>
|
||||
<div class="summary-row"><span class="key">Hardware Tier</span><span class="val">${tierLabel(tierPreview?.hardware?.recommended_tier || 'starter')}</span></div>
|
||||
<div class="summary-row"><span class="key">Tier</span><span class="val">${tierLabel(selectedTier)}</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>` : ''}
|
||||
`;
|
||||
@@ -718,7 +754,7 @@ function startInstall() {
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.ok) throw new Error(data.error || 'Install start failed');
|
||||
}).catch(err => {
|
||||
alert(err.message);
|
||||
showBanner(err.message, 'error');
|
||||
document.getElementById('install-btn-row').classList.remove('hidden');
|
||||
document.getElementById('summary-card').classList.remove('hidden');
|
||||
document.getElementById('progress-wrap').classList.remove('show');
|
||||
@@ -799,23 +835,25 @@ function showRebootNotice() {
|
||||
document.getElementById('progress-wrap').style.display = 'none';
|
||||
document.getElementById('done-screen').classList.remove('hidden');
|
||||
document.getElementById('done-screen').innerHTML = `
|
||||
<div class="done-icon">🔄</div>
|
||||
<div class="done-icon">↻</div>
|
||||
<h2>Server is Rebooting</h2>
|
||||
<p style="margin-bottom:16px">NVIDIA drivers installed. Phase 2 (AI stack) is installing automatically after reboot.</p>
|
||||
<div class="alert alert-info" style="max-width:500px;margin:0 auto 24px">
|
||||
⏱ Phase 2 takes <strong>20–30 more minutes</strong>. You can monitor it via:<br>
|
||||
Phase 2 takes <strong>20–30 more minutes</strong>. You can monitor it via:<br>
|
||||
<code style="background:#E0F2FE;padding:2px 6px;border-radius:4px">ssh cezen@<server-ip></code>
|
||||
then
|
||||
<code style="background:#E0F2FE;padding:2px 6px;border-radius:4px">journalctl -fu cezen-phase2</code>
|
||||
</div>
|
||||
<div class="services card" style="margin:0 auto;max-width:400px;text-align:left">
|
||||
<p style="font-size:13px;color:var(--muted);margin-bottom:12px">Services will be available at:</p>
|
||||
<div class="summary-row"><span class="key">Portal</span><span class="val badge">http://localhost</span></div>
|
||||
<div class="summary-row"><span class="key">Open WebUI</span><span class="val badge">:3001</span></div>
|
||||
<div class="summary-row"><span class="key">JupyterLab</span><span class="val badge">:8888</span></div>
|
||||
<div class="summary-row"><span class="key">MLflow</span><span class="val badge">:5000</span></div>
|
||||
<div class="summary-row"><span class="key">MinIO</span><span class="val badge">:9000</span></div>
|
||||
<div class="summary-row"><span class="key">Grafana</span><span class="val badge">:3000</span></div>
|
||||
</div>
|
||||
<p style="font-size:13px;color:var(--muted);margin-top:16px">Admin login (admin / Cezen@2024!, change required on first login) will be ready once Phase 2 completes.</p>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
@@ -950,7 +988,7 @@ def show_console_banner(ip):
|
||||
|
||||
\033[1;36m╔══════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ CEZEN AI SUITE — SERVER SETUP ║
|
||||
║ NEXUS ONE AI — SERVER SETUP ║
|
||||
║ ║
|
||||
║ Open a browser on any computer on this network: ║
|
||||
║ ║
|
||||
|
||||
Reference in new issue
Block a user