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:
parent
12d9ea84f6
commit
10b66569b6
35
README.md
35
README.md
@ -263,11 +263,22 @@ kept outside the Server ladder — see [Product Structure](#product-structure)):
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Workstation (`workstation`) | NVIDIA DGX Spark, RTX 5090 Workstation, or RTX PRO 6000 Workstation | Local chat, personal RAG, prompt studio, document intelligence | 1-3 active, 5 max |
|
| Workstation (`workstation`) | NVIDIA DGX Spark, RTX 5090 Workstation, or RTX PRO 6000 Workstation | Local chat, personal RAG, prompt studio, document intelligence | 1-3 active, 5 max |
|
||||||
|
|
||||||
The compact single-GPU ISO build path (`build-iso-starter.sh` /
|
Build a dedicated Workstation ISO with:
|
||||||
`user-data-starter`) is the closest existing hardware profile to Workstation
|
|
||||||
today. It still writes the `starter` (Server S) tier marker — repointing it to
|
```bash
|
||||||
build a dedicated Workstation ISO is tracked as follow-up work, not done in
|
bash autoinstall/build-iso-workstation.sh
|
||||||
this pass (see [Migration Notes](../MIGRATION_NOTES.md)).
|
```
|
||||||
|
|
||||||
|
This produces `autoinstall/cezen-ai-workstation-ubuntu2204.iso`, using
|
||||||
|
`user-data-workstation` — same compact single-GPU hardware profile and
|
||||||
|
Ansible playbook (`ansible/starter.yml`) as Server S under the hood, but a
|
||||||
|
distinct `workstation` tier marker (`/opt/cezen/tier`) so the installed
|
||||||
|
system brands and entitles itself as Nexus One AI Workstation. The first-boot
|
||||||
|
wizard (`firstboot-setup.sh`) detects `CEZEN_TIER=workstation` and skips the
|
||||||
|
Server tier-selection step entirely — Workstation is never offered as a
|
||||||
|
Server S/M/L/Max choice. See [Migration Notes](../MIGRATION_NOTES.md) for
|
||||||
|
what is and isn't wired up yet (live portal branding still needs a manual
|
||||||
|
`CEZEN_TIER` override on the deployed service — see below).
|
||||||
|
|
||||||
Large models can be pulled later. The ISO does not need to contain them.
|
Large models can be pulled later. The ISO does not need to contain them.
|
||||||
|
|
||||||
@ -449,14 +460,20 @@ codebase does and does not already provide (confirmed by direct inspection of
|
|||||||
cgit/
|
cgit/
|
||||||
├── install.sh # Main installer entry point
|
├── install.sh # Main installer entry point
|
||||||
├── autoinstall/ # ISO first-boot setup and web setup
|
├── autoinstall/ # ISO first-boot setup and web setup
|
||||||
|
│ ├── build-iso.sh # General multi-tier Server ISO builder
|
||||||
|
│ ├── build-iso-starter.sh # Server S compact single-GPU ISO builder
|
||||||
|
│ ├── build-iso-workstation.sh # Nexus One AI Workstation ISO builder
|
||||||
|
│ ├── user-data-starter # Server S autoinstall config
|
||||||
|
│ ├── user-data-workstation # Workstation autoinstall config
|
||||||
|
│ └── firstboot-setup.sh # TTY setup wizard (skips tier menu for Workstation)
|
||||||
├── scripts/cezen-feasibility.sh # Existing-server feasibility checker
|
├── scripts/cezen-feasibility.sh # Existing-server feasibility checker
|
||||||
├── scripts/cezen-backup.sh # Backup/restore helper
|
├── scripts/cezen-backup.sh # Backup/restore helper
|
||||||
├── ansible/
|
├── ansible/
|
||||||
│ ├── phase1_nvidia.yml # NVIDIA/CUDA phase
|
│ ├── phase1_nvidia.yml # NVIDIA/CUDA phase
|
||||||
│ ├── starter.yml # Starter tier
|
│ ├── starter.yml # Server S tier (also used by Workstation)
|
||||||
│ ├── entry.yml # Entry/Basic tier
|
│ ├── entry.yml # Server M tier
|
||||||
│ ├── pro.yml # Pro tier
|
│ ├── pro.yml # Server L tier
|
||||||
│ ├── max.yml # Max tier
|
│ ├── max.yml # Server Max tier
|
||||||
│ └── roles/
|
│ └── roles/
|
||||||
│ ├── cezen-backend/ # FastAPI backend, cezen-api service
|
│ ├── cezen-backend/ # FastAPI backend, cezen-api service
|
||||||
│ ├── cezen-nginx/ # Portal/nginx deployment
|
│ ├── cezen-nginx/ # Portal/nginx deployment
|
||||||
|
|||||||
208
autoinstall/build-iso-workstation.sh
Executable file
208
autoinstall/build-iso-workstation.sh
Executable file
@ -0,0 +1,208 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# Nexus One AI — Workstation ISO Builder
|
||||||
|
# Hardware target: personal AI appliance (NVIDIA DGX Spark, RTX 5090
|
||||||
|
# Workstation, or RTX PRO 6000 Workstation; 64 GB RAM, 2 TB NVMe)
|
||||||
|
#
|
||||||
|
# Nexus One AI Workstation is a standalone product category (see
|
||||||
|
# ../README.md → "Product Structure"), not a Server tier. This build
|
||||||
|
# shares the Server S ("starter") hardware profile and Ansible playbook
|
||||||
|
# under the hood, but writes a distinct "workstation" tier marker so the
|
||||||
|
# installed system identifies and brands itself as Nexus One AI
|
||||||
|
# Workstation rather than a Server S install.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# cd ~/aipackage
|
||||||
|
# bash autoinstall/build-iso-workstation.sh
|
||||||
|
#
|
||||||
|
# Output: autoinstall/cezen-ai-workstation-ubuntu2204.iso
|
||||||
|
# Flash to USB:
|
||||||
|
# diskutil unmountDisk /dev/diskN
|
||||||
|
# sudo dd if=cezen-ai-workstation-ubuntu2204.iso of=/dev/diskN bs=4m status=progress
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PACKAGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
WORK_DIR="/tmp/cezen-iso-workstation-work"
|
||||||
|
ORIGINAL_ISO="/tmp/ubuntu-22.04.5-live-server-amd64.iso"
|
||||||
|
OUTPUT_ISO="$SCRIPT_DIR/cezen-ai-workstation-ubuntu2204.iso"
|
||||||
|
UBUNTU_URL="https://releases.ubuntu.com/22.04.5/ubuntu-22.04.5-live-server-amd64.iso"
|
||||||
|
TIER="workstation"
|
||||||
|
|
||||||
|
echo "╔══════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Nexus One AI — ISO Builder [WORKSTATION] ║"
|
||||||
|
echo "║ DGX Spark / RTX 5090 / RTX PRO 6000 · 1-3 users ║"
|
||||||
|
echo "╚══════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Install build tools ────────────────────────
|
||||||
|
echo "→ Installing build tools..."
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq xorriso wget isolinux rsync
|
||||||
|
echo "✓ Tools ready"
|
||||||
|
|
||||||
|
# ── Download Ubuntu ISO ────────────────────────
|
||||||
|
if [ -f "$ORIGINAL_ISO" ]; then
|
||||||
|
echo "✓ Ubuntu ISO already downloaded"
|
||||||
|
else
|
||||||
|
echo "→ Downloading Ubuntu 22.04.5 Server ISO (~1.8 GB)..."
|
||||||
|
wget --show-progress -O "$ORIGINAL_ISO" "$UBUNTU_URL"
|
||||||
|
echo "✓ Downloaded"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Extract ISO ────────────────────────────────
|
||||||
|
echo "→ Extracting ISO..."
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
xorriso -osirrox on \
|
||||||
|
-indev "$ORIGINAL_ISO" \
|
||||||
|
-extract / "$WORK_DIR" 2>/dev/null
|
||||||
|
chmod -R u+w "$WORK_DIR"
|
||||||
|
echo "✓ Extracted"
|
||||||
|
|
||||||
|
# ── Inject Workstation autoinstall files ──────
|
||||||
|
echo "→ Injecting Workstation autoinstall config..."
|
||||||
|
mkdir -p "$WORK_DIR/nocloud"
|
||||||
|
cp "$SCRIPT_DIR/user-data-workstation" "$WORK_DIR/nocloud/user-data"
|
||||||
|
cp "$SCRIPT_DIR/meta-data" "$WORK_DIR/nocloud/meta-data"
|
||||||
|
echo "✓ user-data-workstation and meta-data injected"
|
||||||
|
|
||||||
|
# ── Online installer mode ──────────────────────
|
||||||
|
# The installed system pulls the current package from cgit on first boot. This
|
||||||
|
# keeps the ISO small and avoids shipping stale backend/portal code.
|
||||||
|
echo "✓ Online installer mode: package will be pulled from cgit on first boot"
|
||||||
|
|
||||||
|
# ── Patch GRUB ────────────────────────────────
|
||||||
|
echo "→ Patching GRUB config..."
|
||||||
|
GRUB_CFG="$WORK_DIR/boot/grub/grub.cfg"
|
||||||
|
cp "$GRUB_CFG" "$GRUB_CFG.orig"
|
||||||
|
|
||||||
|
sed -i "s/set timeout=.*/set timeout=5/" "$GRUB_CFG"
|
||||||
|
sed -i "s/set timeout_style=.*/set timeout_style=countdown/" "$GRUB_CFG"
|
||||||
|
sed -i '/^\s*linux.*vmlinuz/s|---|autoinstall ds=nocloud\\;s=/cdrom/nocloud/ ---|' "$GRUB_CFG"
|
||||||
|
|
||||||
|
# Update GRUB title to reflect the Workstation category
|
||||||
|
sed -i 's/Install Ubuntu Server/Install Nexus One AI — Workstation/' "$GRUB_CFG" || true
|
||||||
|
echo "✓ GRUB patched"
|
||||||
|
|
||||||
|
# ── Extract MBR and EFI boot data ─────────────
|
||||||
|
echo "→ Extracting boot data from original ISO..."
|
||||||
|
MBR_TEMPLATE=$(mktemp)
|
||||||
|
EFI_IMG=$(mktemp)
|
||||||
|
dd if="$ORIGINAL_ISO" bs=1 count=432 of="$MBR_TEMPLATE" 2>/dev/null
|
||||||
|
|
||||||
|
EFI_LINE=$(fdisk -l "$ORIGINAL_ISO" 2>/dev/null | grep "EFI")
|
||||||
|
echo " EFI partition info: $EFI_LINE"
|
||||||
|
EFI_START=$(echo "$EFI_LINE" | awk '{print $2}')
|
||||||
|
EFI_SIZE=$(echo "$EFI_LINE" | awk '{print $4}')
|
||||||
|
|
||||||
|
if [ -z "$EFI_START" ] || [ -z "$EFI_SIZE" ]; then
|
||||||
|
echo "ERROR: Could not detect EFI partition in ISO."
|
||||||
|
echo "Run: fdisk -l $ORIGINAL_ISO"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dd if="$ORIGINAL_ISO" bs=512 skip="$EFI_START" count="$EFI_SIZE" \
|
||||||
|
of="$EFI_IMG" 2>/dev/null
|
||||||
|
echo "✓ EFI partition extracted (start=$EFI_START, size=$EFI_SIZE)"
|
||||||
|
|
||||||
|
# ── Repack ISO (pass 1) ────────────────────────
|
||||||
|
echo "→ Repacking ISO (pass 1)..."
|
||||||
|
xorriso -as mkisofs \
|
||||||
|
-r \
|
||||||
|
-V "CezenAI_Workstation_2204" \
|
||||||
|
-o "$OUTPUT_ISO" \
|
||||||
|
--grub2-mbr "$MBR_TEMPLATE" \
|
||||||
|
-partition_offset 16 \
|
||||||
|
--mbr-force-bootable \
|
||||||
|
-append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b "$EFI_IMG" \
|
||||||
|
-appended_part_as_gpt \
|
||||||
|
-iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7 \
|
||||||
|
-c "/boot.catalog" \
|
||||||
|
-b "/boot/grub/i386-pc/eltorito.img" \
|
||||||
|
-no-emul-boot \
|
||||||
|
-boot-load-size 4 \
|
||||||
|
-boot-info-table \
|
||||||
|
--grub2-boot-info \
|
||||||
|
-eltorito-alt-boot \
|
||||||
|
-e "--interval:appended_partition_2:::" \
|
||||||
|
-no-emul-boot \
|
||||||
|
"$WORK_DIR"
|
||||||
|
|
||||||
|
# ── Refresh md5sum.txt and repack (pass 2) ────
|
||||||
|
echo "→ Refreshing md5sum.txt..."
|
||||||
|
FINAL_DIR=$(mktemp -d)
|
||||||
|
VERIFY_DIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$WORK_DIR" "$MBR_TEMPLATE" "$EFI_IMG" "$FINAL_DIR" "$VERIFY_DIR"' EXIT
|
||||||
|
|
||||||
|
xorriso -osirrox on -indev "$OUTPUT_ISO" -extract / "$FINAL_DIR" >/dev/null 2>&1
|
||||||
|
chmod -R u+w "$FINAL_DIR"
|
||||||
|
(
|
||||||
|
cd "$FINAL_DIR"
|
||||||
|
rm -f md5sum.txt
|
||||||
|
find . -type f \
|
||||||
|
! -path './md5sum.txt' \
|
||||||
|
! -path './boot.catalog' \
|
||||||
|
-print0 \
|
||||||
|
| sort -z \
|
||||||
|
| xargs -0 md5sum > md5sum.txt
|
||||||
|
)
|
||||||
|
echo "✓ md5sum.txt refreshed"
|
||||||
|
|
||||||
|
echo "→ Repacking ISO (pass 2)..."
|
||||||
|
xorriso -as mkisofs \
|
||||||
|
-r \
|
||||||
|
-V "CezenAI_Workstation_2204" \
|
||||||
|
-o "$OUTPUT_ISO" \
|
||||||
|
--grub2-mbr "$MBR_TEMPLATE" \
|
||||||
|
-partition_offset 16 \
|
||||||
|
--mbr-force-bootable \
|
||||||
|
-append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b "$EFI_IMG" \
|
||||||
|
-appended_part_as_gpt \
|
||||||
|
-iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7 \
|
||||||
|
-c "/boot.catalog" \
|
||||||
|
-b "/boot/grub/i386-pc/eltorito.img" \
|
||||||
|
-no-emul-boot \
|
||||||
|
-boot-load-size 4 \
|
||||||
|
-boot-info-table \
|
||||||
|
--grub2-boot-info \
|
||||||
|
-eltorito-alt-boot \
|
||||||
|
-e "--interval:appended_partition_2:::" \
|
||||||
|
-no-emul-boot \
|
||||||
|
"$FINAL_DIR"
|
||||||
|
|
||||||
|
# ── Verify output ISO ──────────────────────────
|
||||||
|
echo "→ Verifying rebuilt ISO manifest..."
|
||||||
|
xorriso -osirrox on -indev "$OUTPUT_ISO" -extract / "$VERIFY_DIR" >/dev/null 2>&1
|
||||||
|
chmod -R u+w "$VERIFY_DIR"
|
||||||
|
(
|
||||||
|
cd "$VERIFY_DIR"
|
||||||
|
md5sum -c md5sum.txt >/tmp/cezen-iso-md5check-workstation.log 2>&1 || {
|
||||||
|
echo "ERROR: Rebuilt ISO failed its own md5sum.txt verification."
|
||||||
|
sed -n '1,40p' /tmp/cezen-iso-md5check-workstation.log
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
echo "✓ Output ISO manifest verified"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Done! Workstation ISO ready. ║"
|
||||||
|
echo "╚══════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
ls -lh "$OUTPUT_ISO"
|
||||||
|
echo ""
|
||||||
|
echo "→ Transfer to MacBook:"
|
||||||
|
echo " scp user@server:~/aipackage/autoinstall/cezen-ai-workstation-ubuntu2204.iso ."
|
||||||
|
echo ""
|
||||||
|
echo "→ Flash to USB (macOS):"
|
||||||
|
echo " diskutil list # find USB e.g. /dev/disk4"
|
||||||
|
echo " diskutil unmountDisk /dev/disk4"
|
||||||
|
echo " sudo dd if=cezen-ai-workstation-ubuntu2204.iso of=/dev/disk4 bs=4m status=progress"
|
||||||
|
echo ""
|
||||||
|
echo "→ Post-flash: boot the workstation from USB."
|
||||||
|
echo " Unattended install completes in ~10 min."
|
||||||
|
echo " First-boot wizard runs on tty1 — set IP, org name, admin password."
|
||||||
|
echo " Tier selection (Step 3) is skipped automatically — this image"
|
||||||
|
echo " installs as Nexus One AI Workstation, not a Server tier."
|
||||||
@ -23,6 +23,24 @@ detect_iface() {
|
|||||||
IFACE="$(detect_iface)"
|
IFACE="$(detect_iface)"
|
||||||
IFACE="${IFACE:-$(ip -o link show | awk -F': ' '$2 !~ /lo|docker|br-|veth/ {print $2; exit}')}"
|
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() {
|
netmask_to_prefix() {
|
||||||
case "$1" in
|
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" ;;
|
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
|
# 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
|
import json
|
||||||
d=json.load(open("/tmp/cezen-license-check.json"))
|
d=json.load(open("/tmp/cezen-license-check.json"))
|
||||||
labels = {
|
labels = {
|
||||||
"starter": "Starter — 1x RTX 5090 / 32GB VRAM · Small team",
|
"starter": "Server S — 1x RTX 5090 / 32GB VRAM · Small team",
|
||||||
"basic": "Entry — 1x NVIDIA RTX Pro 6000 (96GB) · Up to 20 users",
|
"basic": "Server M — 1x NVIDIA RTX Pro 6000 (96GB) · Up to 20 users",
|
||||||
"pro": "Pro — 2x RTX 5090 / RTX Pro class · Up to 100 users",
|
"pro": "Server L — 2x RTX 5090 / RTX Pro class · Up to 100 users",
|
||||||
"max": "Max — 4-8x H100/H200/A100 class · 100+ users",
|
"max": "Server Max — 4-8x H100/H200/A100 class · 100+ users",
|
||||||
}
|
}
|
||||||
for opt in d.get("tier_options", []):
|
for opt in d.get("tier_options", []):
|
||||||
if opt.get("selectable"):
|
if opt.get("selectable"):
|
||||||
print(opt["tier"])
|
print(opt["tier"])
|
||||||
print(labels.get(opt["tier"], opt["tier"]))
|
print(labels.get(opt["tier"], opt["tier"]))
|
||||||
PY
|
PY
|
||||||
)
|
)
|
||||||
|
|
||||||
if [ "${#TIER_MENU[@]}" -eq 0 ]; then
|
if [ "${#TIER_MENU[@]}" -eq 0 ]; then
|
||||||
whiptail --title "$TITLE" \
|
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}\nHardware recommendation: ${HARDWARE_TIER}\n\nCheck the signed license or contact Cezen support." \
|
||||||
$H $W
|
$H $W
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
whiptail --title "$TITLE" \
|
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}\nAllowed tier: ${LICENSE_ALLOWED_TIER}\nHardware recommendation: ${HARDWARE_TIER}\n\nOnly valid tiers will be shown next." \
|
||||||
$H $W
|
$H $W
|
||||||
|
|
||||||
TIER=$(whiptail --title "$TITLE" \
|
TIER=$(whiptail --title "$TITLE" \
|
||||||
--menu "\nStep 3 of 4: Select AI Package Tier\n\nChoose the tier allowed by your license and hardware:" \
|
--menu "\nStep 3 of 4: Select AI Package Tier\n\nChoose the tier allowed by your license and hardware:" \
|
||||||
$H $W 4 \
|
$H $W 4 \
|
||||||
"${TIER_MENU[@]}" \
|
"${TIER_MENU[@]}" \
|
||||||
3>&1 1>&2 2>&3)
|
3>&1 1>&2 2>&3)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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
|
# STEP 4: SELECT AI TOOLS
|
||||||
@ -322,7 +361,7 @@ if [ -n "$LICENSE_PATH" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
whiptail --title "$TITLE" \
|
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
|
$H $W
|
||||||
|
|
||||||
# ════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════
|
||||||
@ -355,17 +394,23 @@ TIER=${TIER}
|
|||||||
SKIP_ROLES=${SKIP_ROLES}
|
SKIP_ROLES=${SKIP_ROLES}
|
||||||
EOF
|
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'
|
python3 - <<'PY'
|
||||||
import json, os, time
|
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 = {
|
payload = {
|
||||||
"schema": "cezen.install_record.v1",
|
"schema": "cezen.install_record.v1",
|
||||||
"customer_name": os.environ.get("CUSTOMER_NAME", "").strip(),
|
"customer_name": os.environ.get("CUSTOMER_NAME", "").strip(),
|
||||||
"customer_id": os.environ.get("CUSTOMER_ID", "").strip(),
|
"customer_id": os.environ.get("CUSTOMER_ID", "").strip(),
|
||||||
"contact_email": os.environ.get("CONTACT_EMAIL", "").strip(),
|
"contact_email": os.environ.get("CONTACT_EMAIL", "").strip(),
|
||||||
"selected_tier": os.environ.get("TIER", "basic").strip(),
|
"selected_tier": os.environ.get("TIER_DISPLAY", "basic").strip(),
|
||||||
"provisioned_tier": os.environ.get("TIER", "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(),
|
"support_until": os.environ.get("SUPPORT_UNTIL", "").strip(),
|
||||||
"install_type": "licensed" if os.path.exists("/opt/cezen/license.json") else "field-staging",
|
"install_type": "licensed" if os.path.exists("/opt/cezen/license.json") else "field-staging",
|
||||||
"license_status": os.environ.get("LICENSE_STATUS", "missing").strip(),
|
"license_status": os.environ.get("LICENSE_STATUS", "missing").strip(),
|
||||||
|
|||||||
156
autoinstall/user-data-workstation
Normal file
156
autoinstall/user-data-workstation
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
#cloud-config
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
# Nexus One AI — Workstation Autoinstall
|
||||||
|
# Hardware target: personal AI appliance (Mini-ITX / SFF / DGX Spark class)
|
||||||
|
# GPU: NVIDIA DGX Spark, RTX 5090 Workstation, or RTX PRO 6000
|
||||||
|
# Workstation (1 GPU)
|
||||||
|
# RAM: 64 GB DDR5
|
||||||
|
# Storage: 1× 2 TB NVMe SSD (single drive — simple LVM)
|
||||||
|
# Network: 2.5 GbE (single interface)
|
||||||
|
# Users: 1-3 active, 5 max — not a shared server, no HA
|
||||||
|
#
|
||||||
|
# This reuses the same compact single-GPU install profile as the Server S
|
||||||
|
# ("starter") image below — same hardware class, same Ansible playbook
|
||||||
|
# (ansible/starter.yml) — but is a distinct, standalone product category
|
||||||
|
# (Nexus One AI Workstation), not part of the Server S/M/L/Max ladder.
|
||||||
|
# See cezen_license.WORKSTATION_TIER / TIER_MATRIX["workstation"].
|
||||||
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
autoinstall:
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
# ── Locale & keyboard ──────────────────────────
|
||||||
|
locale: en_IN.UTF-8
|
||||||
|
keyboard:
|
||||||
|
layout: us
|
||||||
|
|
||||||
|
# ── Network: DHCP during install; static config applied post-install ──
|
||||||
|
network:
|
||||||
|
network:
|
||||||
|
version: 2
|
||||||
|
ethernets:
|
||||||
|
any-en:
|
||||||
|
dhcp4: true
|
||||||
|
match:
|
||||||
|
name: "en*"
|
||||||
|
any-eth:
|
||||||
|
dhcp4: true
|
||||||
|
match:
|
||||||
|
name: "eth*"
|
||||||
|
|
||||||
|
# ── Storage: single 2 TB NVMe, simple LVM ─────
|
||||||
|
# Workstations have one drive — no RAID needed.
|
||||||
|
storage:
|
||||||
|
layout:
|
||||||
|
name: lvm
|
||||||
|
match:
|
||||||
|
size: largest
|
||||||
|
|
||||||
|
# ── Identity ──────────────────────────────────
|
||||||
|
identity:
|
||||||
|
hostname: cezenai-workstation
|
||||||
|
username: cezen
|
||||||
|
# Default password: cezen@123 (change via first-boot wizard)
|
||||||
|
password: "$6$I5VA.42G1xTeVhCv$KCLzqIKg/kbNHZyiTEMAY4FZsJMDDwoS90k6Ffb9VEwmcK.wuzlJNe3ceiEfLrzYzXEvqjYsLc7klAbeGPGab."
|
||||||
|
|
||||||
|
# ── SSH ───────────────────────────────────────
|
||||||
|
ssh:
|
||||||
|
install-server: true
|
||||||
|
allow-pw: true
|
||||||
|
|
||||||
|
# ── Base packages ─────────────────────────────
|
||||||
|
packages:
|
||||||
|
- git
|
||||||
|
- curl
|
||||||
|
- wget
|
||||||
|
- python3
|
||||||
|
- whiptail
|
||||||
|
- openssh-server
|
||||||
|
- nvme-cli # NVMe health / SMART monitoring
|
||||||
|
|
||||||
|
# ── Late commands ─────────────────────────────
|
||||||
|
late-commands:
|
||||||
|
# Expand LVM to fill the full 2 TB NVMe
|
||||||
|
- lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv || true
|
||||||
|
- resize2fs /dev/ubuntu-vg/ubuntu-lv || true
|
||||||
|
|
||||||
|
# Passwordless sudo for cezen (needed by install.sh + first-boot wizard)
|
||||||
|
- echo "cezen ALL=(ALL) NOPASSWD:ALL" > /target/etc/sudoers.d/cezen
|
||||||
|
- chmod 440 /target/etc/sudoers.d/cezen
|
||||||
|
|
||||||
|
# Replace installer netplan with simple DHCP target config.
|
||||||
|
# The first-boot wizard will switch to static if desired.
|
||||||
|
- rm -f /target/etc/netplan/50-cloud-init.yaml /target/etc/netplan/00-installer-config.yaml || true
|
||||||
|
- |
|
||||||
|
cat > /target/etc/netplan/99-cezen-dhcp.yaml << 'EOF'
|
||||||
|
network:
|
||||||
|
version: 2
|
||||||
|
ethernets:
|
||||||
|
any-en:
|
||||||
|
dhcp4: true
|
||||||
|
match:
|
||||||
|
name: "en*"
|
||||||
|
any-eth:
|
||||||
|
dhcp4: true
|
||||||
|
match:
|
||||||
|
name: "eth*"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Disable cdrom APT source
|
||||||
|
- sed -i 's/^deb cdrom:/# deb cdrom:/' /target/etc/apt/sources.list || true
|
||||||
|
|
||||||
|
# Pull the Nexus One AI installer from cgit. The ISO intentionally does not
|
||||||
|
# bundle the full package, keeping the image small and the installed code
|
||||||
|
# current at deployment time.
|
||||||
|
- mkdir -p /target/opt/aipackage
|
||||||
|
- git clone https://cgit.cezentech.com/jinojose/aipackage.git /target/opt/aipackage
|
||||||
|
|
||||||
|
# Write tier marker — used by install.sh (as a fallback default only;
|
||||||
|
# the first-boot wizard always passes --tier=starter explicitly for
|
||||||
|
# Ansible routing) and by the portal branding/entitlement system, which
|
||||||
|
# reads "workstation" here to show Nexus One AI Workstation branding and
|
||||||
|
# the workstation feature set (TIER_MATRIX["workstation"]) instead of a
|
||||||
|
# Server tier. install.sh never writes to this file, so it persists
|
||||||
|
# unchanged through Phase 1 and Phase 2.
|
||||||
|
- mkdir -p /target/opt/cezen
|
||||||
|
- echo "workstation" > /target/opt/cezen/tier
|
||||||
|
|
||||||
|
# Deploy first-boot TUI wizard
|
||||||
|
- cp /target/opt/aipackage/autoinstall/firstboot-setup.sh /target/opt/cezen/firstboot-setup.sh
|
||||||
|
- chmod +x /target/opt/cezen/firstboot-setup.sh
|
||||||
|
|
||||||
|
# Set hostname
|
||||||
|
- echo "cezenai-workstation" > /target/etc/hostname
|
||||||
|
- sed -i 's/aiserver/cezenai-workstation/g' /target/etc/hosts || true
|
||||||
|
|
||||||
|
# Systemd service: run first-boot wizard on tty1 once.
|
||||||
|
# CEZEN_TIER=workstation tells firstboot-setup.sh to skip the Server
|
||||||
|
# tier-selection menu (Step 3) — Workstation is never offered there.
|
||||||
|
- |
|
||||||
|
cat > /target/etc/systemd/system/cezen-setup.service << 'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=Nexus One AI — Console Setup Wizard (Workstation)
|
||||||
|
After=cloud-final.service cloud-init.target network-online.target
|
||||||
|
Wants=cloud-init.target network-online.target
|
||||||
|
ConditionPathExists=!/opt/cezen/.setup-done
|
||||||
|
OnFailure=getty@tty1.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
WorkingDirectory=/opt/cezen
|
||||||
|
ExecStartPre=-/bin/systemctl stop getty@tty1.service
|
||||||
|
ExecStartPre=-/usr/bin/chvt 1
|
||||||
|
ExecStart=/bin/bash -lc 'clear >/dev/tty1 2>/dev/null || true; /usr/bin/openvt -c 1 -f -w -- env TERM=linux CEZEN_TIER=workstation /opt/cezen/firstboot-setup.sh'
|
||||||
|
ExecStartPost=-/bin/systemctl start getty@tty1.service
|
||||||
|
StandardOutput=journal+console
|
||||||
|
StandardError=journal+console
|
||||||
|
Restart=no
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=cloud-init.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- curtin in-target -- systemctl enable ssh
|
||||||
|
- curtin in-target -- systemctl enable cezen-setup.service
|
||||||
|
|
||||||
|
user-data:
|
||||||
|
disable_root: false
|
||||||
Loading…
Reference in New Issue
Block a user