From a1948d93fea126ad4da38797636b469e2b294787 Mon Sep 17 00:00:00 2001 From: Jino Jose Date: Wed, 24 Jun 2026 12:05:32 +0530 Subject: [PATCH] Add first-boot TUI wizard: network, tier, tool selection + LVM auto-extend --- autoinstall/firstboot-setup.sh | 188 +++++++++++++++++++++++++++++++++ autoinstall/user-data | 83 ++++++++------- 2 files changed, 234 insertions(+), 37 deletions(-) create mode 100644 autoinstall/firstboot-setup.sh diff --git a/autoinstall/firstboot-setup.sh b/autoinstall/firstboot-setup.sh new file mode 100644 index 0000000..a520749 --- /dev/null +++ b/autoinstall/firstboot-setup.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────── +# Cezen AI Suite — First Boot Setup Wizard +# Runs on first boot after OS install via systemd service. +# Uses whiptail for the TUI. +# ───────────────────────────────────────────────────────────── +set -e + +AIPACKAGE_DIR="/opt/aipackage" +LOG_FILE="/var/log/cezen-setup.log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +# ── Colors / terminal setup ──────────────────────────────── +export NEWT_COLORS=' +root=,black +window=white,navy +border=white,navy +title=white,navy +button=black,cyan +actbutton=white,red +checkbox=white,navy +actcheckbox=black,cyan +entry=white,navy +label=white,navy +listbox=white,navy +actlistbox=black,cyan +textbox=white,navy +acttextbox=black,cyan +' + +TITLE=" Cezen AI Suite — Server Setup " +H=20 +W=70 + +# ── Welcome ──────────────────────────────────────────────── +whiptail --title "$TITLE" \ + --msgbox "\nWelcome to the Cezen AI Suite installer.\n\nThis wizard will configure your network and install the AI stack.\n\nMake sure this server is connected to the internet before continuing." \ + $H $W + +# ════════════════════════════════════════════════════════════ +# STEP 1: NETWORK CONFIGURATION +# ════════════════════════════════════════════════════════════ + +NET_MODE=$(whiptail --title "$TITLE" \ + --menu "\nStep 1 of 3: Network Configuration\n\nHow should this server get its IP address?" \ + $H $W 2 \ + "DHCP" "Automatic (get IP from network)" \ + "Static" "Manual (enter IP address)" \ + 3>&1 1>&2 2>&3) + +if [ "$NET_MODE" = "Static" ]; then + + # Detect first ethernet interface + IFACE=$(ip -o link show | awk -F': ' '$2 !~ /lo|docker|br-|veth/ {print $2; exit}') + + IP_ADDR=$(whiptail --title "$TITLE" \ + --inputbox "\nEnter static IP address:\n(Example: 192.168.1.100)" \ + $H $W "" 3>&1 1>&2 2>&3) + + NETMASK=$(whiptail --title "$TITLE" \ + --inputbox "\nEnter subnet mask (CIDR prefix):\n(Example: 24 means 255.255.255.0)" \ + $H $W "24" 3>&1 1>&2 2>&3) + + GATEWAY=$(whiptail --title "$TITLE" \ + --inputbox "\nEnter default gateway:\n(Example: 192.168.1.1)" \ + $H $W "" 3>&1 1>&2 2>&3) + + DNS=$(whiptail --title "$TITLE" \ + --inputbox "\nEnter DNS server:\n(Example: 8.8.8.8)" \ + $H $W "8.8.8.8" 3>&1 1>&2 2>&3) + + # Write netplan static config + cat > /etc/netplan/99-cezen-static.yaml << EOF +network: + version: 2 + ethernets: + ${IFACE}: + dhcp4: false + addresses: + - ${IP_ADDR}/${NETMASK} + routes: + - to: default + via: ${GATEWAY} + nameservers: + addresses: [${DNS}] +EOF + + netplan apply 2>/dev/null || true + sleep 3 + + # Verify connectivity + if ! ping -c 2 -W 3 8.8.8.8 &>/dev/null; then + whiptail --title "$TITLE" \ + --msgbox "\nWARNING: Cannot reach internet with these settings.\n\nIP: ${IP_ADDR}/${NETMASK}\nGateway: ${GATEWAY}\nDNS: ${DNS}\n\nPlease verify your network settings. The install requires internet access." \ + $H $W + else + whiptail --title "$TITLE" \ + --msgbox "\nNetwork configured successfully!\n\nIP: ${IP_ADDR}/${NETMASK}\nGateway: ${GATEWAY}\nDNS: ${DNS}" \ + $H $W + fi + +else + # DHCP — just confirm it's working + sleep 2 + MY_IP=$(hostname -I | awk '{print $1}') + if [ -n "$MY_IP" ]; then + whiptail --title "$TITLE" \ + --msgbox "\nDHCP configured successfully!\n\nServer IP: ${MY_IP}\n\nYou can set a static IP later via:\n sudo nano /etc/netplan/50-cloud-init.yaml" \ + $H $W + else + whiptail --title "$TITLE" \ + --msgbox "\nWARNING: No IP address obtained via DHCP.\n\nMake sure the network cable is connected and try rebooting." \ + $H $W + fi +fi + +# ════════════════════════════════════════════════════════════ +# STEP 2: SELECT TIER +# ════════════════════════════════════════════════════════════ + +TIER=$(whiptail --title "$TITLE" \ + --menu "\nStep 2 of 3: Select AI Package Tier\n\nChoose the tier that matches your hardware:" \ + $H $W 3 \ + "entry" "Entry — 3× NVIDIA L40S (48GB each) · Up to 20 users" \ + "mid" "Mid — RTX Pro 6000 BW (96GB each) · Up to 50 users" \ + "advanced" "Advanced — HGX H200 (141GB each) · 200+ users" \ + 3>&1 1>&2 2>&3) + +# ════════════════════════════════════════════════════════════ +# STEP 3: SELECT AI TOOLS +# ════════════════════════════════════════════════════════════ + +TOOLS=$(whiptail --title "$TITLE" \ + --checklist "\nStep 3 of 3: Select AI Tools to Install\n\nSpace to toggle, Enter to confirm:" \ + $H $W 8 \ + "ollama" "Ollama + Open WebUI (LLM inference + chat UI)" ON \ + "jupyterlab" "JupyterLab (Notebook environment)" ON \ + "chromadb" "ChromaDB (Vector DB for RAG)" ON \ + "vllm" "vLLM (OpenAI-compatible API)" ON \ + "mlflow" "MLflow (Experiment tracking)" ON \ + "minio" "MinIO (S3 model storage)" ON \ + "monitoring" "Grafana + Prometheus (GPU & system monitoring)" ON \ + "k3s" "K3s (Lightweight Kubernetes)" ON \ + 3>&1 1>&2 2>&3) + +# ════════════════════════════════════════════════════════════ +# CONFIRM +# ════════════════════════════════════════════════════════════ + +# Format tools list for display +TOOLS_DISPLAY=$(echo "$TOOLS" | tr -d '"' | tr ' ' '\n' | sed 's/^/ · /' | tr '\n' '\n') +MY_IP=$(hostname -I | awk '{print $1}') + +whiptail --title "$TITLE" \ + --yesno "\nReady to install. Please confirm:\n\nNetwork: ${NET_MODE} (${MY_IP})\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?" \ + $H $W + +# ════════════════════════════════════════════════════════════ +# RUN INSTALLER +# ════════════════════════════════════════════════════════════ + +clear +echo "" +echo "╔══════════════════════════════════════════╗" +echo "║ Cezen AI Suite — Installing... ║" +echo "║ Check progress: journalctl -f ║" +echo "╚══════════════════════════════════════════╝" +echo "" + +# Write selected tools to a config file so install.sh can read it +mkdir -p /opt/cezen +cat > /opt/cezen/install.conf << EOF +TIER=${TIER} +SKIP_ROLES="" +EOF + +# Determine which roles to skip based on tool selection +for role in ollama jupyterlab chromadb vllm mlflow minio monitoring k3s; do + if ! echo "$TOOLS" | grep -q "$role"; then + sed -i "s/SKIP_ROLES=\"\"/SKIP_ROLES=\"${role}\"/" /opt/cezen/install.conf + fi +done + +# Mark as configured so this wizard doesn't run again +touch /opt/cezen/.setup-done + +# Run the installer +bash "$AIPACKAGE_DIR/install.sh" --tier="$TIER" diff --git a/autoinstall/user-data b/autoinstall/user-data index 6730297..1a9da85 100644 --- a/autoinstall/user-data +++ b/autoinstall/user-data @@ -8,6 +8,7 @@ autoinstall: layout: us # ── Network: DHCP on first ethernet ─────────── + # Final network config is set by firstboot-setup.sh wizard network: network: version: 2 @@ -21,18 +22,18 @@ autoinstall: match: name: "eth*" - # ── Storage: LVM on largest disk ────────────── + # ── Storage: LVM using ALL disk space ───────── storage: layout: name: lvm match: size: largest - # ── Identity: cezen user ────────────────────── + # ── Identity ────────────────────────────────── identity: hostname: aiserver username: cezen - # Password: Cezen@2024! (SHA-512 hash below) + # Password: Cezen@2024! password: "$6$rounds=4096$cezentech$9pHVUFCvB7mHrblqn0qXJaFWxMkmepNM4T8Q5Fx8piVXuGDq.MLk/RH4nRMWtluLMpPXaZQAGFOD4xtjW1wC1" # ── SSH ─────────────────────────────────────── @@ -40,49 +41,57 @@ autoinstall: install-server: true allow-pw: true - # ── Packages installed during setup ─────────── + # ── Base packages ───────────────────────────── packages: - git - curl - wget + - whiptail + - net-tools - # ── Late commands: run after OS install ─────── - # These run with target mounted at /target + # ── Late commands ───────────────────────────── late-commands: - # Clone the Cezen AI installer - - git clone https://cgit.cezentech.com/jinojose/aipackage.git /target/opt/aipackage + # Extend LVM to use 100% of available disk space + - lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv || true + - resize2fs /dev/ubuntu-vg/ubuntu-lv || true - # Set ownership - - curtin in-target -- chown -R cezen:cezen /opt/aipackage - - # Create a firstboot systemd service that runs install.sh on first boot - - | - cat > /target/etc/systemd/system/cezen-firstboot.service << 'SVCEOF' - [Unit] - Description=Cezen AI Suite First Boot Installer - After=network-online.target - Wants=network-online.target - ConditionPathExists=!/opt/cezen/.installed - - [Service] - Type=oneshot - ExecStart=/bin/bash /opt/aipackage/install.sh - ExecStartPost=/bin/touch /opt/cezen/.installed - RemainAfterExit=yes - StandardOutput=journal+console - StandardError=journal+console - - [Install] - WantedBy=multi-user.target - SVCEOF - - # Enable the firstboot service - - curtin in-target -- systemctl enable cezen-firstboot.service - - # Allow cezen user to run sudo without password (needed for install.sh) + # Allow cezen passwordless sudo (needed for install.sh) - echo "cezen ALL=(ALL) NOPASSWD:ALL" > /target/etc/sudoers.d/cezen - chmod 440 /target/etc/sudoers.d/cezen - # ── Skip confirmations ──────────────────────── + # Clone the Cezen AI installer + - git clone https://cgit.cezentech.com/jinojose/aipackage.git /target/opt/aipackage || true + + # Deploy the firstboot setup wizard + - cp /target/opt/aipackage/autoinstall/firstboot-setup.sh /target/opt/cezen-setup.sh + - chmod +x /target/opt/cezen-setup.sh + + # Create firstboot systemd service (runs the TUI wizard on first login) + - | + cat > /target/etc/systemd/system/cezen-setup.service << 'EOF' + [Unit] + Description=Cezen AI Suite Setup Wizard + After=network-online.target getty@tty1.service + Wants=network-online.target + ConditionPathExists=!/opt/cezen/.setup-done + + [Service] + Type=oneshot + ExecStart=/bin/bash /opt/cezen-setup.sh + StandardInput=tty + StandardOutput=tty + StandardError=tty + TTYPath=/dev/tty1 + TTYReset=yes + TTYVHangup=yes + RemainAfterExit=yes + + [Install] + WantedBy=multi-user.target + EOF + + - curtin in-target -- systemctl enable cezen-setup.service + + # ── Skip confirmations ───────────────────────── user-data: disable_root: false