Add dedicated Nexus One AI Workstation ISO build path
- New build-iso-workstation.sh / autoinstall/user-data-workstation: writes the workstation tier marker (/opt/cezen/tier), reuses the Server S (starter) hardware profile and Ansible playbook under the hood - firstboot-setup.sh: additive WORKSTATION_MODE branch (gated on CEZEN_TIER=workstation) skips the Server tier-selection menu entirely — Workstation is never offered as a Server S/M/L/Max choice. Non-workstation installs are unaffected. Also relabels the Server tier menu to Server S/M/L/Max, matching the earlier portal rename - install.sh and the Ansible playbooks are unchanged; only the pre/post marker files differ between a Server S and Workstation install - README.md + MIGRATION_NOTES.md updated with the new build path and what is still manual (CEZEN_TIER isn't yet templated into the deployed cezen-api systemd unit — tracked as follow-up)
This commit is contained in:
1 parent
12d9ea84f6
commit
10b66569b6
4 files changed
+457
-31
No files matched your search
@@ -23,6 +23,24 @@ detect_iface() {
|
||||
IFACE="$(detect_iface)"
|
||||
IFACE="${IFACE:-$(ip -o link show | awk -F': ' '$2 !~ /lo|docker|br-|veth/ {print $2; exit}')}"
|
||||
|
||||
# ── Nexus One AI Workstation detection ─────────────────────
|
||||
# Workstation is a standalone product category, not a rung on the Server
|
||||
# S/M/L/Max ladder (see cezen_license.WORKSTATION_TIER). It must never be
|
||||
# offered as a choice in the Server tier-selection menu below (Step 3).
|
||||
# When this ISO was built as a Workstation image (CEZEN_TIER=workstation,
|
||||
# or the /opt/cezen/tier marker says "workstation"), Step 3 is skipped
|
||||
# entirely and the existing "starter" Ansible profile is reused for
|
||||
# provisioning — same compact single-GPU stack, different commercial
|
||||
# category. TIER="starter" here only selects which Ansible playbook runs;
|
||||
# it does not change the "workstation" marker written to /opt/cezen/tier
|
||||
# or the install-record tier fields below.
|
||||
WORKSTATION_MODE=false
|
||||
if [ "${CEZEN_TIER:-}" = "workstation" ]; then
|
||||
WORKSTATION_MODE=true
|
||||
elif [ -f /opt/cezen/tier ] && [ "$(tr -d '[:space:]' < /opt/cezen/tier)" = "workstation" ]; then
|
||||
WORKSTATION_MODE=true
|
||||
fi
|
||||
|
||||
netmask_to_prefix() {
|
||||
case "$1" in
|
||||
32|31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1) echo "$1" ;;
|
||||
@@ -257,40 +275,61 @@ PY
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# STEP 3: SELECT TIER
|
||||
# (skipped entirely for Nexus One AI Workstation — it is not a Server
|
||||
# tier and must never appear in, or be selected from, this menu)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
mapfile -t TIER_MENU < <(python3 - <<'PY'
|
||||
if [ "$WORKSTATION_MODE" = true ]; then
|
||||
TIER="starter" # Ansible/install.sh routing only — reuses the compact
|
||||
# single-GPU profile. The "workstation" marker in
|
||||
# /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." \
|
||||
$H $W
|
||||
else
|
||||
mapfile -t TIER_MENU < <(python3 - <<'PY'
|
||||
import json
|
||||
d=json.load(open("/tmp/cezen-license-check.json"))
|
||||
labels = {
|
||||
"starter": "Starter — 1x RTX 5090 / 32GB VRAM · Small team",
|
||||
"basic": "Entry — 1x NVIDIA RTX Pro 6000 (96GB) · Up to 20 users",
|
||||
"pro": "Pro — 2x RTX 5090 / RTX Pro class · Up to 100 users",
|
||||
"max": "Max — 4-8x H100/H200/A100 class · 100+ users",
|
||||
"starter": "Server S — 1x RTX 5090 / 32GB VRAM · Small team",
|
||||
"basic": "Server M — 1x NVIDIA RTX Pro 6000 (96GB) · Up to 20 users",
|
||||
"pro": "Server L — 2x RTX 5090 / RTX Pro class · Up to 100 users",
|
||||
"max": "Server Max — 4-8x H100/H200/A100 class · 100+ users",
|
||||
}
|
||||
for opt in d.get("tier_options", []):
|
||||
if opt.get("selectable"):
|
||||
print(opt["tier"])
|
||||
print(labels.get(opt["tier"], opt["tier"]))
|
||||
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." \
|
||||
$H $W
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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 "\nLicense status: ${LICENSE_STATUS}\nAllowed tier: ${LICENSE_ALLOWED_TIER}\nHardware recommendation: ${HARDWARE_TIER}\n\nOnly valid tiers will be shown next." \
|
||||
$H $W
|
||||
exit 1
|
||||
|
||||
TIER=$(whiptail --title "$TITLE" \
|
||||
--menu "\nStep 3 of 4: Select AI Package Tier\n\nChoose the tier allowed by your license and hardware:" \
|
||||
$H $W 4 \
|
||||
"${TIER_MENU[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
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." \
|
||||
$H $W
|
||||
|
||||
TIER=$(whiptail --title "$TITLE" \
|
||||
--menu "\nStep 3 of 4: Select AI Package Tier\n\nChoose the tier allowed by your license and hardware:" \
|
||||
$H $W 4 \
|
||||
"${TIER_MENU[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
# Display-only label — $TIER itself stays the Ansible/install.sh routing
|
||||
# value (e.g. "starter"); TIER_DISPLAY is what the operator sees and what
|
||||
# gets recorded as the product category for Workstation installs.
|
||||
if [ "$WORKSTATION_MODE" = true ]; then
|
||||
TIER_DISPLAY="workstation"
|
||||
else
|
||||
TIER_DISPLAY="$TIER"
|
||||
fi
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# STEP 4: SELECT AI TOOLS
|
||||
@@ -322,7 +361,7 @@ if [ -n "$LICENSE_PATH" ]; then
|
||||
fi
|
||||
|
||||
whiptail --title "$TITLE" \
|
||||
--yesno "\nReady to install. Please confirm:\n\nNetwork: ${NET_MODE} (${MY_IP})\nCustomer: ${CUSTOMER_NAME:-Not entered}\nLicense: ${LICENSE_DISPLAY}\nAllowed: ${LICENSE_ALLOWED_TIER}\nHardware: ${HARDWARE_TIER}\nTier: ${TIER}\n\nTools:\n${TOOLS_DISPLAY}\n\nThis will take 20–40 minutes.\nThe server will reboot once during install (NVIDIA drivers).\n\nContinue?" \
|
||||
--yesno "\nReady to install. Please confirm:\n\nNetwork: ${NET_MODE} (${MY_IP})\nCustomer: ${CUSTOMER_NAME:-Not entered}\nLicense: ${LICENSE_DISPLAY}\nAllowed: ${LICENSE_ALLOWED_TIER}\nHardware: ${HARDWARE_TIER}\nTier: ${TIER_DISPLAY}\n\nTools:\n${TOOLS_DISPLAY}\n\nThis will take 20–40 minutes.\nThe server will reboot once during install (NVIDIA drivers).\n\nContinue?" \
|
||||
$H $W
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
@@ -355,17 +394,23 @@ TIER=${TIER}
|
||||
SKIP_ROLES=${SKIP_ROLES}
|
||||
EOF
|
||||
|
||||
export CUSTOMER_NAME CUSTOMER_ID CONTACT_EMAIL SUPPORT_UNTIL TIER LICENSE_STATUS LICENSE_ALLOWED_TIER HARDWARE_TIER
|
||||
export CUSTOMER_NAME CUSTOMER_ID CONTACT_EMAIL SUPPORT_UNTIL TIER TIER_DISPLAY LICENSE_STATUS LICENSE_ALLOWED_TIER HARDWARE_TIER WORKSTATION_MODE
|
||||
python3 - <<'PY'
|
||||
import json, os, time
|
||||
|
||||
# NOTE: install.sh (Phase 2) rewrites /opt/cezen/install-record.json after
|
||||
# this, using its own $TIER routing value ("starter" for Workstation) — this
|
||||
# is expected: provisioned_tier there reflects the Ansible profile actually
|
||||
# installed. The persistent Workstation marker is /opt/cezen/tier, not this
|
||||
# file, and install.sh never touches that marker.
|
||||
payload = {
|
||||
"schema": "cezen.install_record.v1",
|
||||
"customer_name": os.environ.get("CUSTOMER_NAME", "").strip(),
|
||||
"customer_id": os.environ.get("CUSTOMER_ID", "").strip(),
|
||||
"contact_email": os.environ.get("CONTACT_EMAIL", "").strip(),
|
||||
"selected_tier": os.environ.get("TIER", "basic").strip(),
|
||||
"provisioned_tier": os.environ.get("TIER", "basic").strip(),
|
||||
"selected_tier": os.environ.get("TIER_DISPLAY", "basic").strip(),
|
||||
"provisioned_tier": os.environ.get("TIER_DISPLAY", "basic").strip(),
|
||||
"category": "workstation" if os.environ.get("WORKSTATION_MODE") == "true" else "server",
|
||||
"support_until": os.environ.get("SUPPORT_UNTIL", "").strip(),
|
||||
"install_type": "licensed" if os.path.exists("/opt/cezen/license.json") else "field-staging",
|
||||
"license_status": os.environ.get("LICENSE_STATUS", "missing").strip(),
|
||||
|
||||
Reference in new issue
Block a user